Files
heros/assets/script/game/skill/SMoveComp.ts
2025-11-01 15:02:31 +08:00

474 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Vec3, v3, Node } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { RType, EType, SkillSet } from "../common/config/SkillSet";
import { BoxSet } from "../common/config/BoxSet";
import { SkillView } from "./SkillView";
import { smc } from "../common/SingletonModuleComp";
/**
* ==================== 技能移动数据组件 ====================
*
* 用途:
* - 存储技能实体的移动相关数据
* - 管理移动状态和参数
* - 支持多种移动类型(线性、贝塞尔、固定位置等)
*/
@ecs.register('SMoveDataComp')
export class SMoveDataComp extends ecs.Comp {
/** 起始位置 */
startPos: Vec3 = v3(0, 0, 0);
/** 目标位置 */
targetPos: Vec3 = v3(0, 0, 0);
/** 当前位置 */
currentPos: Vec3 = v3(0, 0, 0);
/** 移动速度 */
speed: number = 500;
/** 移动进度 (0-1) */
progress: number = 0;
/** 移动方向缩放 */
scale: number = 1;
/** 技能UUID */
s_uuid: number = 0;
/** 运行类型 */
runType: RType = RType.linear;
/** 结束类型 */
endType: EType = EType.collision;
/** 攻击偏移X - 从视图层传递 */
atk_x: number = 0;
/** 攻击偏移Y - 从视图层传递 */
atk_y: number = 0;
/** 是否正在移动 */
isMoving: boolean = false;
/** 是否已完成移动 */
isCompleted: boolean = false;
/** 移动开始时间 */
startTime: number = 0;
/** 移动总时间 */
totalTime: number = 0;
/** 贝塞尔曲线控制点 */
controlPoint: Vec3 = v3(0, 0, 0);
/** 是否自动销毁(到达目标后) */
autoDestroy: boolean = true;
reset() {
this.startPos.set(0, 0, 0);
this.targetPos.set(0, 0, 0);
this.currentPos.set(0, 0, 0);
this.controlPoint.set(0, 0, 0);
this.speed = 500;
this.progress = 0;
this.scale = 1;
this.s_uuid = 0;
this.runType = RType.linear;
this.endType = EType.collision;
this.atk_x = 0;
this.atk_y = 0;
this.isMoving = false;
this.isCompleted = false;
this.startTime = 0;
this.totalTime = 0;
this.autoDestroy = true;
}
/**
* 重新计算位置(用于线性移动的延长)
*/
rePos(originalStart: Vec3) {
if (!originalStart) {
return;
}
// 对于直线运动只在x轴方向延长y轴保持不变
const originalTarget = v3(this.targetPos.x, this.targetPos.y + BoxSet.ATK_Y);
// 计算x轴方向
const xDirection = originalTarget.x > originalStart.x ? 1 : -1;
// 延长720像素但只在x轴方向
const extendedTarget = v3(
originalTarget.x + (xDirection * 720),
originalStart.y, // y轴保持起始位置不变
originalStart.z // z轴保持起始位置不变
);
this.startPos.set(originalStart);
this.targetPos.set(extendedTarget);
}
/**
* 开始移动
*/
startMove() {
this.isMoving = true;
this.isCompleted = false;
this.progress = 0;
this.startTime = Date.now();
this.currentPos.set(this.startPos);
// 根据移动类型计算总时间和控制点
this.calculateMoveParameters();
}
/**
* 计算移动参数
*/
private calculateMoveParameters() {
const distance = Vec3.distance(this.startPos, this.targetPos);
this.totalTime = distance / this.speed;
// 为贝塞尔移动生成控制点
if (this.runType === RType.bezier) {
this.generateBezierControlPoint();
}
}
/**
* 生成贝塞尔曲线控制点
*/
private generateBezierControlPoint() {
const midPoint = new Vec3();
Vec3.lerp(midPoint, this.startPos, this.targetPos, 0.5);
// 计算垂直方向
const direction = new Vec3();
Vec3.subtract(direction, this.targetPos, this.startPos);
const perpendicular = v3(-direction.y, direction.x, 0);
perpendicular.normalize();
// 根据scale决定控制点方向
const offset = 100 * this.scale;
Vec3.scaleAndAdd(this.controlPoint, midPoint, perpendicular, offset);
}
/**
* 更新移动进度
*/
updateProgress(deltaTime: number): boolean {
if (!this.isMoving || this.isCompleted) {
return false;
}
this.progress += deltaTime / this.totalTime;
if (this.progress >= 1) {
this.progress = 1;
this.isCompleted = true;
this.isMoving = false;
}
// 根据移动类型计算当前位置
this.calculateCurrentPosition();
return true;
}
/**
* 根据移动类型计算当前位置
*/
private calculateCurrentPosition() {
switch (this.runType) {
case RType.linear:
// 直线运动只在x轴移动y轴保持不变
this.currentPos.x = this.startPos.x + (this.targetPos.x - this.startPos.x) * this.progress;
this.currentPos.y = this.startPos.y; // y轴保持不变
this.currentPos.z = this.startPos.z; // z轴保持不变
break;
case RType.bezier:
this.calculateBezierPosition(this.progress);
break;
case RType.fixed:
case RType.fixedEnd:
// 固定位置类型不需要移动
this.currentPos.set(this.startPos);
break;
default:
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, this.progress);
break;
}
}
/**
* 计算贝塞尔曲线位置
*/
private calculateBezierPosition(t: number) {
// 二次贝塞尔曲线公式: B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2
const oneMinusT = 1 - t;
const oneMinusTSquared = oneMinusT * oneMinusT;
const tSquared = t * t;
const twoOneMinusTt = 2 * oneMinusT * t;
this.currentPos.x = oneMinusTSquared * this.startPos.x +
twoOneMinusTt * this.controlPoint.x +
tSquared * this.targetPos.x;
this.currentPos.y = oneMinusTSquared * this.startPos.y +
twoOneMinusTt * this.controlPoint.y +
tSquared * this.targetPos.y;
this.currentPos.z = oneMinusTSquared * this.startPos.z +
twoOneMinusTt * this.controlPoint.z +
tSquared * this.targetPos.z;
}
/**
* 停止移动
*/
stopMove() {
this.isMoving = false;
this.isCompleted = true;
}
/**
* 获取移动统计信息(用于调试)
*/
getMoveStats(): {
isMoving: boolean;
isCompleted: boolean;
progress: number;
runType: RType;
endType: EType;
totalTime: number;
elapsedTime: number;
} {
const currentTime = Date.now();
return {
isMoving: this.isMoving,
isCompleted: this.isCompleted,
progress: this.progress,
runType: this.runType,
endType: this.endType,
totalTime: this.totalTime,
elapsedTime: (currentTime - this.startTime) / 1000
};
}
}
/**
* ==================== 技能移动系统 ====================
*
* 职责:
* 1. 处理技能实体的移动逻辑
* 2. 更新技能位置
* 3. 管理移动生命周期
* 4. 支持多种移动类型
*
* 设计理念:
* - 参考DamageQueueComp的实现方式
* - 独立的移动处理系统
* - 支持从视图层获取的参数
*/
@ecs.register('SMoveSystem')
export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(SMoveDataComp, SkillView);
}
entityEnter(entity: ecs.Entity): void {
if(!smc.mission.play || smc.mission.pause) return;
const moveComp = entity.get(SMoveDataComp);
const skillView = entity.get(SkillView);
if (!moveComp || !skillView || !skillView.node) return;
// 获取技能配置
const skillConfig = SkillSet[moveComp.s_uuid];
if (!skillConfig) {
console.warn(`[SMoveSystem] 技能配置不存在: ${moveComp.s_uuid}`);
return;
}
// 根据配置设置移动速度
if (skillConfig.speed > 0) {
moveComp.speed = skillConfig.speed;
}
// 根据runType设置初始位置
this.initializePosition(moveComp, skillView);
// 开始移动(除了固定位置类型)
if (moveComp.runType !== RType.fixed && moveComp.runType !== RType.fixedEnd) {
moveComp.startMove();
}
// console.log(`[SMoveSystem] 技能 ${skillConfig.name} 开始移动,类型: ${moveComp.runType}`);
}
/**
* 根据runType初始化技能位置
*/
private initializePosition(moveComp: SMoveDataComp, skillView: SkillView): void {
const node = skillView.node;
switch(moveComp.runType) {
case RType.linear:
// linear类型根据atk_x和atk_y调整位置
const linearPos = v3(
node.position.x + moveComp.atk_x,
node.position.y + moveComp.atk_y,
node.position.z
);
moveComp.rePos(linearPos);
break;
case RType.fixed:
// 固定起始位置
node.setPosition(moveComp.startPos.x, node.position.y, 0);
break;
case RType.fixedEnd:
// 固定结束位置
node.setPosition(moveComp.targetPos.x > 360 ? 300 : moveComp.targetPos.x, node.position.y, 0);
break;
default:
// 其他类型包括bezier根据atk_x和atk_y调整起始位置
if (moveComp.atk_x !== 0 || moveComp.atk_y !== 0) {
const adjustedStartPos = v3(
moveComp.startPos.x + moveComp.atk_x * moveComp.scale,
moveComp.startPos.y + moveComp.atk_y,
moveComp.startPos.z
);
moveComp.startPos.set(adjustedStartPos);
node.setPosition(adjustedStartPos);
}
break;
}
}
update(entity: ecs.Entity): void {
if(!smc.mission.play || smc.mission.pause) return;
const moveComp = entity.get(SMoveDataComp);
const skillView = entity.get(SkillView);
if (!moveComp || !skillView || !skillView.node) return;
// 更新移动进度
const isMoving = moveComp.updateProgress(this.dt);
if (isMoving) {
// 更新节点位置
skillView.node.setPosition(moveComp.currentPos);
// 处理自动旋转(贝塞尔移动)
if (moveComp.runType === RType.bezier) {
this.updateRotation(skillView.node, moveComp);
}
}
// 检查移动完成
if (moveComp.isCompleted && moveComp.autoDestroy) {
// 根据结束类型决定是否销毁
if (moveComp.endType === EType.timeEnd || moveComp.endType === EType.collision) {
entity.destroy();
}
}
}
/**
* 更新节点旋转(用于贝塞尔移动)
*/
private updateRotation(node: Node, moveComp: SMoveDataComp) {
if (moveComp.progress < 1) {
// 计算下一帧的位置来确定方向
const nextProgress = Math.min(moveComp.progress + 0.01, 1);
const nextPos = v3(0, 0, 0);
// 计算下一个位置
const t = nextProgress;
const oneMinusT = 1 - t;
const oneMinusTSquared = oneMinusT * oneMinusT;
const tSquared = t * t;
const twoOneMinusTt = 2 * oneMinusT * t;
nextPos.x = oneMinusTSquared * moveComp.startPos.x +
twoOneMinusTt * moveComp.controlPoint.x +
tSquared * moveComp.targetPos.x;
nextPos.y = oneMinusTSquared * moveComp.startPos.y +
twoOneMinusTt * moveComp.controlPoint.y +
tSquared * moveComp.targetPos.y;
// 计算方向角度
const direction = new Vec3();
Vec3.subtract(direction, nextPos, moveComp.currentPos);
if (direction.length() > 0.01) {
const angle = Math.atan2(direction.y, direction.x) * (180 / Math.PI);
node.angle = angle;
}
}
}
}
/**
* ==================== 技能移动辅助工具 ====================
*/
export class SMoveHelper {
/**
* 为技能实体设置移动参数
*/
static setupMoveForSkill(entity: ecs.Entity, startPos: Vec3, targetPos: Vec3, s_uuid: number): void {
let moveComp = entity.get(SMoveDataComp);
if (!moveComp) {
moveComp = entity.add(SMoveDataComp);
}
moveComp.startPos.set(startPos);
moveComp.targetPos.set(targetPos);
moveComp.s_uuid = s_uuid;
// 从技能配置获取默认参数
const skillConfig = SkillSet[s_uuid];
if (skillConfig) {
moveComp.runType = skillConfig.RType || RType.linear;
moveComp.speed = skillConfig.speed || 500;
}
}
/**
* 检查技能是否正在移动
*/
static isSkillMoving(entity: ecs.Entity): boolean {
const moveComp = entity.get(SMoveDataComp);
return moveComp ? moveComp.isMoving : false;
}
/**
* 获取技能移动统计信息
*/
static getSkillMoveStats(entity: ecs.Entity): any {
const moveComp = entity.get(SMoveDataComp);
return moveComp ? moveComp.getMoveStats() : null;
}
/**
* 停止技能移动
*/
static stopSkillMove(entity: ecs.Entity): void {
const moveComp = entity.get(SMoveDataComp);
if (moveComp) {
moveComp.stopMove();
}
}
}