|
|
@@ -14,6 +14,13 @@ export interface TrajectoryState {
|
|
|
targetPosition?: Vec3; // 目标位置(追踪用)
|
|
|
elapsedTime: number; // 经过时间
|
|
|
phase: 'launch' | 'homing' | 'return'; // 运动阶段
|
|
|
+ // 路径记录相关
|
|
|
+ flightPath: Vec3[]; // 飞行路径记录
|
|
|
+ pathRecordInterval: number; // 路径记录间隔(毫秒)
|
|
|
+ lastRecordTime: number; // 上次记录时间
|
|
|
+ returnPathIndex: number; // 返回路径索引
|
|
|
+ // 发射源方块节点
|
|
|
+ sourceBlock?: Node; // 发射源方块节点(用于动态获取返回位置)
|
|
|
}
|
|
|
|
|
|
@ccclass('BulletTrajectory')
|
|
|
@@ -31,7 +38,7 @@ export class BulletTrajectory extends Component {
|
|
|
/**
|
|
|
* 初始化弹道
|
|
|
*/
|
|
|
- public init(config: BulletTrajectoryConfig, direction: Vec3, startPos: Vec3) {
|
|
|
+ public init(config: BulletTrajectoryConfig, direction: Vec3, startPos: Vec3, sourceBlock?: Node) {
|
|
|
this.config = { ...config };
|
|
|
this.rigidBody = this.getComponent(RigidBody2D);
|
|
|
|
|
|
@@ -45,7 +52,14 @@ export class BulletTrajectory extends Component {
|
|
|
currentVelocity: direction.clone().multiplyScalar(config.speed),
|
|
|
startPosition: startPos.clone(),
|
|
|
elapsedTime: 0,
|
|
|
- phase: 'launch'
|
|
|
+ phase: 'launch',
|
|
|
+ // 路径记录初始化
|
|
|
+ flightPath: [startPos.clone()],
|
|
|
+ pathRecordInterval: 50, // 每50毫秒记录一次位置
|
|
|
+ lastRecordTime: 0,
|
|
|
+ returnPathIndex: -1,
|
|
|
+ // 存储发射源方块节点
|
|
|
+ sourceBlock: sourceBlock
|
|
|
};
|
|
|
|
|
|
this.homingTimer = config.homingDelay;
|
|
|
@@ -203,6 +217,9 @@ export class BulletTrajectory extends Component {
|
|
|
|
|
|
this.state.elapsedTime += dt;
|
|
|
|
|
|
+ // 记录路径(仅对回旋镖)
|
|
|
+ this.recordFlightPath(dt);
|
|
|
+
|
|
|
switch (this.config.type) {
|
|
|
case 'straight':
|
|
|
this.updateStraightTrajectory(dt);
|
|
|
@@ -312,46 +329,82 @@ export class BulletTrajectory extends Component {
|
|
|
this.findTarget();
|
|
|
}
|
|
|
|
|
|
- // 检查是否到达目标位置(仅用于西瓜炸弹等爆炸武器,回旋镖不适用)
|
|
|
+ // 检查是否到达目标位置(爆炸武器和回旋镖都适用,但处理方式不同)
|
|
|
const lifecycle = this.getComponent('BulletLifecycle') as any;
|
|
|
const isBoomerang = lifecycle && lifecycle.getState && lifecycle.getState().phase !== undefined;
|
|
|
|
|
|
- if (this.state.targetPosition && !isBoomerang) {
|
|
|
+ if (this.state.targetPosition) {
|
|
|
const currentPos = this.node.worldPosition;
|
|
|
const distanceToTarget = Vec3.distance(currentPos, this.state.targetPosition);
|
|
|
|
|
|
- // 如果到达目标位置附近(50像素内),触发爆炸
|
|
|
+ // 如果到达目标位置附近(50像素内)
|
|
|
if (distanceToTarget <= 50) {
|
|
|
- // 停止运动
|
|
|
- if (this.rigidBody) {
|
|
|
- this.rigidBody.linearVelocity = new Vec2(0, 0);
|
|
|
- }
|
|
|
-
|
|
|
- // 先触发命中效果(包括音效播放)
|
|
|
- const hitEffect = this.getComponent('BulletHitEffect') as any;
|
|
|
- if (hitEffect && typeof hitEffect.processHit === 'function') {
|
|
|
- // 使用当前位置作为命中位置,传入子弹节点作为命中目标
|
|
|
- hitEffect.processHit(this.node, currentPos);
|
|
|
- }
|
|
|
-
|
|
|
- // 然后通知生命周期组件触发爆炸
|
|
|
- if (lifecycle && typeof lifecycle.onHit === 'function') {
|
|
|
- lifecycle.onHit(this.node); // 触发爆炸逻辑
|
|
|
+ if (isBoomerang) {
|
|
|
+ // 回旋镖:触发命中效果但不爆炸,开始返回
|
|
|
+ const hitEffect = this.getComponent('BulletHitEffect') as any;
|
|
|
+ if (hitEffect && typeof hitEffect.processHit === 'function') {
|
|
|
+ // 对目标位置附近的敌人造成伤害
|
|
|
+ const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
|
|
|
+ if (enemyContainer) {
|
|
|
+ const enemies = enemyContainer.children.filter(child =>
|
|
|
+ child.active && this.isEnemyNode(child)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 寻找目标位置附近的敌人
|
|
|
+ for (const enemy of enemies) {
|
|
|
+ const distanceToEnemy = Vec3.distance(currentPos, enemy.worldPosition);
|
|
|
+ if (distanceToEnemy <= 50) {
|
|
|
+ hitEffect.processHit(enemy, currentPos);
|
|
|
+ break; // 只命中一个敌人
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 通知生命周期组件处理命中(回旋镖会开始返回而不是销毁)
|
|
|
+ if (lifecycle && typeof lifecycle.onHit === 'function') {
|
|
|
+ lifecycle.onHit(this.targetNode || this.node);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 爆炸武器:停止运动并触发爆炸
|
|
|
+ if (this.rigidBody) {
|
|
|
+ this.rigidBody.linearVelocity = new Vec2(0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先触发命中效果(包括音效播放)
|
|
|
+ const hitEffect = this.getComponent('BulletHitEffect') as any;
|
|
|
+ if (hitEffect && typeof hitEffect.processHit === 'function') {
|
|
|
+ // 使用当前位置作为命中位置,传入子弹节点作为命中目标
|
|
|
+ hitEffect.processHit(this.node, currentPos);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 然后通知生命周期组件触发爆炸
|
|
|
+ if (lifecycle && typeof lifecycle.onHit === 'function') {
|
|
|
+ lifecycle.onHit(this.node); // 触发爆炸逻辑
|
|
|
+ }
|
|
|
+ return;
|
|
|
}
|
|
|
- return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 根据回旋镖状态决定目标方向
|
|
|
if (isBoomerang && lifecycle.getState().phase === 'returning') {
|
|
|
- // 返回阶段:朝向起始位置
|
|
|
- const pos = this.node.worldPosition;
|
|
|
- const startPos = this.state.startPosition || pos; // 防止空指针
|
|
|
- const dirToHome = startPos.clone().subtract(pos).normalize();
|
|
|
- this.arcTargetDir.set(dirToHome);
|
|
|
-
|
|
|
- // 更新目标位置为起始位置
|
|
|
- this.state.targetPosition = startPos.clone();
|
|
|
+ // 返回阶段:直接朝向目标位置(方块当前位置)
|
|
|
+ if (this.state.targetPosition) {
|
|
|
+ const pos = this.node.worldPosition;
|
|
|
+ const dirToTarget = this.state.targetPosition.clone().subtract(pos).normalize();
|
|
|
+ this.arcTargetDir.set(dirToTarget);
|
|
|
+
|
|
|
+ // 检查是否到达目标位置
|
|
|
+ const distanceToTarget = Vec3.distance(pos, this.state.targetPosition);
|
|
|
+ if (distanceToTarget <= 30) {
|
|
|
+ // 到达目标,销毁子弹
|
|
|
+ if (lifecycle && typeof lifecycle.destroy === 'function') {
|
|
|
+ lifecycle.destroy();
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
} else {
|
|
|
// 主动追踪阶段:朝向敌人
|
|
|
if (this.targetNode && this.targetNode.isValid) {
|
|
|
@@ -389,8 +442,21 @@ export class BulletTrajectory extends Component {
|
|
|
let rotateFactor = (this.config.rotateSpeed ?? 0.5) * dt;
|
|
|
|
|
|
if (isBoomerang && lifecycle.getState().phase === 'returning') {
|
|
|
- // 回旋镖返回阶段:使用固定的转向速率,确保平滑返回
|
|
|
- rotateFactor *= 1.5; // 返回时适中的转向速率
|
|
|
+ // 回旋镖返回阶段:根据距离调整转向速率,确保平滑返回
|
|
|
+ if (this.state.targetPosition) {
|
|
|
+ const distanceToTarget = Vec3.distance(this.node.worldPosition, this.state.targetPosition);
|
|
|
+
|
|
|
+ // 根据距离动态调整转向速率,距离越近转向越快
|
|
|
+ if (distanceToTarget < 50) {
|
|
|
+ rotateFactor *= 4.0; // 非常接近目标点时快速转向
|
|
|
+ } else if (distanceToTarget < 100) {
|
|
|
+ rotateFactor *= 2.5; // 接近目标点时加快转向
|
|
|
+ } else if (distanceToTarget < 200) {
|
|
|
+ rotateFactor *= 1.8; // 中等距离时适度加快转向
|
|
|
+ } else {
|
|
|
+ rotateFactor *= 1.2; // 远距离时使用基础转向速率
|
|
|
+ }
|
|
|
+ }
|
|
|
} else if (this.targetNode && this.targetNode.isValid) {
|
|
|
// 主动追踪阶段:根据距离调整转向速率
|
|
|
const distance = Vec3.distance(this.node.worldPosition, this.targetNode.worldPosition);
|
|
|
@@ -477,12 +543,144 @@ export class BulletTrajectory extends Component {
|
|
|
this.config.gravity = gravity;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 记录飞行路径(仅对回旋镖)
|
|
|
+ */
|
|
|
+ private recordFlightPath(dt: number) {
|
|
|
+ // 检查是否为回旋镖
|
|
|
+ const lifecycle = this.getComponent('BulletLifecycle') as any;
|
|
|
+ const isBoomerang = lifecycle && lifecycle.getState && lifecycle.getState().phase !== undefined;
|
|
|
+
|
|
|
+ if (!isBoomerang) return;
|
|
|
+
|
|
|
+ // 只在非返回阶段记录路径
|
|
|
+ if (lifecycle.getState().phase === 'returning') return;
|
|
|
+
|
|
|
+ // 检查是否到了记录时间
|
|
|
+ this.state.lastRecordTime += dt * 1000; // 转换为毫秒
|
|
|
+ if (this.state.lastRecordTime >= this.state.pathRecordInterval) {
|
|
|
+ const currentPos = this.node.worldPosition.clone();
|
|
|
+
|
|
|
+ // 避免记录重复位置
|
|
|
+ const lastPos = this.state.flightPath[this.state.flightPath.length - 1];
|
|
|
+ const distance = Vec3.distance(currentPos, lastPos);
|
|
|
+
|
|
|
+ if (distance > 10) { // 只有移动距离超过10像素才记录
|
|
|
+ this.state.flightPath.push(currentPos);
|
|
|
+ this.state.lastRecordTime = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取返回路径中的下一个目标点
|
|
|
+ */
|
|
|
+ private getNextReturnTarget(): Vec3 | null {
|
|
|
+ if (this.state.flightPath.length === 0) return null;
|
|
|
+
|
|
|
+ // 如果还没有开始返回路径,初始化索引
|
|
|
+ if (this.state.returnPathIndex === -1) {
|
|
|
+ this.state.returnPathIndex = this.state.flightPath.length - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查当前位置是否接近目标点
|
|
|
+ if (this.state.returnPathIndex >= 0) {
|
|
|
+ const targetPos = this.state.flightPath[this.state.returnPathIndex];
|
|
|
+ const currentPos = this.node.worldPosition;
|
|
|
+ const distance = Vec3.distance(currentPos, targetPos);
|
|
|
+
|
|
|
+ // 动态调整接近距离:路径点越少,接近距离越大,避免过于严格的路径跟随
|
|
|
+ const approachDistance = Math.max(25, Math.min(50, 200 / this.state.flightPath.length));
|
|
|
+
|
|
|
+ // 如果接近当前目标点,移动到下一个点
|
|
|
+ if (distance <= approachDistance) {
|
|
|
+ this.state.returnPathIndex--;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回当前目标点,如果有多个路径点则进行平滑插值
|
|
|
+ if (this.state.returnPathIndex >= 0) {
|
|
|
+ const currentTarget = this.state.flightPath[this.state.returnPathIndex];
|
|
|
+
|
|
|
+ // 如果还有下一个路径点,进行平滑插值
|
|
|
+ if (this.state.returnPathIndex > 0) {
|
|
|
+ const nextTarget = this.state.flightPath[this.state.returnPathIndex - 1];
|
|
|
+ const progress = Math.min(1, (approachDistance - distance) / approachDistance);
|
|
|
+
|
|
|
+ if (progress > 0) {
|
|
|
+ // 在当前目标点和下一个目标点之间进行插值
|
|
|
+ const smoothTarget = new Vec3();
|
|
|
+ Vec3.lerp(smoothTarget, currentTarget, nextTarget, progress * 0.3);
|
|
|
+ return smoothTarget;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return currentTarget;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果已经遍历完所有路径点,返回发射源方块的当前位置
|
|
|
+ if (this.state.sourceBlock && this.state.sourceBlock.isValid) {
|
|
|
+ // 动态获取方块的当前世界位置
|
|
|
+ return this.state.sourceBlock.worldPosition.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果方块节点无效,回退到起始位置
|
|
|
+ return this.state.startPosition;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取动态返回目标位置(方块当前位置)
|
|
|
+ */
|
|
|
+ public getDynamicReturnTarget(): Vec3 | null {
|
|
|
+ if (this.state.sourceBlock && this.state.sourceBlock.isValid) {
|
|
|
+ return this.state.sourceBlock.worldPosition.clone();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 为返回阶段重新初始化轨道
|
|
|
+ * @param currentPos 当前位置(新的发射点)
|
|
|
+ * @param targetPos 目标位置(方块位置)
|
|
|
+ */
|
|
|
+ public reinitializeForReturn(currentPos: Vec3, targetPos: Vec3) {
|
|
|
+ if (!this.config || !this.state) return;
|
|
|
+
|
|
|
+ // 清除当前所有速度和运动
|
|
|
+ if (this.rigidBody) {
|
|
|
+ this.rigidBody.linearVelocity = new Vec2(0, 0);
|
|
|
+ this.rigidBody.angularVelocity = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算从当前位置到目标位置的方向
|
|
|
+ const direction = targetPos.clone().subtract(currentPos).normalize();
|
|
|
+
|
|
|
+ // 重新设置状态
|
|
|
+ this.state.startPosition = currentPos.clone();
|
|
|
+ this.state.targetPosition = targetPos.clone();
|
|
|
+ this.state.initialVelocity = direction.clone().multiplyScalar(this.config.speed);
|
|
|
+ this.state.currentVelocity = direction.clone().multiplyScalar(this.config.speed);
|
|
|
+ this.state.elapsedTime = 0;
|
|
|
+
|
|
|
+ // 重新初始化弧线轨道参数
|
|
|
+ if (this.config.type === 'arc') {
|
|
|
+ this.arcDir = direction.clone();
|
|
|
+ this.arcTargetDir = direction.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清除路径记录,开始新的返回轨道
|
|
|
+ this.state.flightPath = [currentPos.clone()];
|
|
|
+ this.state.returnPathIndex = -1;
|
|
|
+ this.state.lastRecordTime = 0;
|
|
|
+
|
|
|
+ console.log(`[BulletTrajectory] 重新初始化返回轨道: ${currentPos} -> ${targetPos}`);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 验证配置
|
|
|
*/
|
|
|
public static validateConfig(config: BulletTrajectoryConfig): boolean {
|
|
|
if (!config) return false;
|
|
|
-
|
|
|
if (config.speed <= 0) return false;
|
|
|
if (config.gravity < 0) return false;
|
|
|
if (config.homingStrength < 0 || config.homingStrength > 1) return false;
|