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"; /** * ==================== 技能移动数据组件 ==================== * * 用途: * - 存储技能实体的移动相关数据 * - 管理移动状态和参数 * - 支持多种移动类型(线性、贝塞尔、固定位置等) */ @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 { 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 { 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(); } } }