feat(技能系统): 实现技能移动系统并优化位置计算
- 新增SMoveSystem处理技能移动逻辑,支持多种移动类型 - 使用Vec3.lerp优化直线运动的位置计算 - 调整技能起始位置y轴偏移量 - 为线性移动类型添加方向旋转功能
This commit is contained in:
@@ -253,6 +253,7 @@ export class SACastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdat
|
||||
|
||||
// 获取施法者位置作为起始位置
|
||||
const startPos = caster.node.position.clone();
|
||||
startPos.y += 50;
|
||||
|
||||
const targetPos = targets[0]; // 使用第一个目标位置
|
||||
// console.log(`[SACastSystem]: ${s_uuid}, 起始位置: ${startPos}, 目标位置: ${targetPos}`);
|
||||
@@ -327,11 +328,12 @@ export class SACastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdat
|
||||
if (!model || !view || !view.node) return false;
|
||||
if (model.is_dead) return false;
|
||||
if (model.fac === fac) return false;
|
||||
const pos = view.node.position;
|
||||
const pos = view.node.position.clone();
|
||||
pos.y += 50;
|
||||
const dist = Math.abs(currentPos.x - pos.x);
|
||||
if (dist <= range) {
|
||||
const laneBias = Math.abs(currentPos.y - pos.y);
|
||||
results.push({ pos: pos.clone(), dist, laneBias });
|
||||
results.push({ pos: pos, dist, laneBias });
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -94,18 +94,14 @@ export class SMoveDataComp extends ecs.Comp {
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于直线运动,只在x轴方向延长,y轴保持不变
|
||||
const originalTarget = v3(this.targetPos.x, this.targetPos.y);
|
||||
// 计算方向向量
|
||||
const direction = new Vec3();
|
||||
Vec3.subtract(direction, this.targetPos, originalStart);
|
||||
direction.normalize();
|
||||
|
||||
// 计算x轴方向
|
||||
const xDirection = originalTarget.x > originalStart.x ? 1 : -1;
|
||||
|
||||
// 延长720像素,但只在x轴方向
|
||||
const extendedTarget = v3(
|
||||
originalTarget.x + (xDirection * 720),
|
||||
originalStart.y, // y轴保持起始位置不变
|
||||
originalStart.z // z轴保持起始位置不变
|
||||
);
|
||||
// 延长720像素
|
||||
const extendedTarget = new Vec3();
|
||||
Vec3.scaleAndAdd(extendedTarget, originalStart, direction, 720);
|
||||
|
||||
this.startPos.set(originalStart);
|
||||
this.targetPos.set(extendedTarget);
|
||||
@@ -184,10 +180,8 @@ export class SMoveDataComp extends ecs.Comp {
|
||||
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轴保持不变
|
||||
// 直线运动
|
||||
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, this.progress);
|
||||
break;
|
||||
|
||||
case RType.bezier:
|
||||
@@ -262,213 +256,3 @@ export class SMoveDataComp extends ecs.Comp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ==================== 技能移动系统 ====================
|
||||
*
|
||||
* 职责:
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
225
assets/script/game/skill/SMoveSystem.ts
Normal file
225
assets/script/game/skill/SMoveSystem.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { v3, Vec3 ,Node} from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { EType, RType, SkillSet } from "../common/config/SkillSet";
|
||||
import { SMoveDataComp } from "./SMoveComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { SkillView } from "./SkillView";
|
||||
|
||||
/**
|
||||
* ==================== 技能移动系统 ====================
|
||||
*
|
||||
* 职责:
|
||||
* 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);
|
||||
|
||||
// 设置旋转角度
|
||||
const direction = new Vec3();
|
||||
Vec3.subtract(direction, moveComp.targetPos, moveComp.startPos);
|
||||
if (direction.length() > 0.01) {
|
||||
const angle = Math.atan2(direction.y, direction.x) * (180 / Math.PI);
|
||||
node.angle = angle;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/script/game/skill/SMoveSystem.ts.meta
Normal file
9
assets/script/game/skill/SMoveSystem.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "5c973fdb-ae0b-45e9-86e6-c6d7d73bf429",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user