Files
pixelheros/assets/script/game/hero/HeroMove.ts
walkpan 343e14b56c fix(英雄移动): 添加暂停怪物行动时英雄也停止移动的逻辑
当开启四选一界面时暂停怪物行动,现在英雄移动也会同步暂停。同时在MissionCardComp中添加对stop_mon_action状态的管理,确保界面开关时游戏状态正确切换。
2026-01-06 19:50:20 +08:00

294 lines
10 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 { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroViewComp } from "./HeroViewComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroSkillsComp } from "./HeroSkills";
import { smc } from "../common/SingletonModuleComp";
import { FacSet } from "../common/config/GameSet";
import { HType } from "../common/config/heroSet";
import { Attrs } from "../common/config/HeroAttrs";
import { SkillRange } from "../common/config/SkillSet";
/** 英雄移动组件 */
@ecs.register('HeroMove')
export class HeroMoveComp extends ecs.Comp {
/** 移动方向1向右-1向左 */
direction: number = 1;
/** 目标x坐标阵型位置 */
targetX: number = 0;
/** 是否处于移动状态 */
moving: boolean = true;
reset() {
this.direction = 1;
this.targetX = 0;
this.moving = true;
}
}
/** 英雄移动系统 - 智能战斗移动逻辑 */
@ecs.register('HeroMoveSystem')
export class HeroMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(HeroMoveComp, HeroViewComp, HeroAttrsComp, HeroSkillsComp);
}
update(e: ecs.Entity) {
// 1. 全局状态检查
if (!smc.mission.play || smc.mission.pause) return;
// 如果开启了暂停怪物行动(通常用于四选一界面),玩家角色也应该停止移动
if (smc.mission.stop_mon_action) return;
const model = e.get(HeroAttrsComp);
const move = e.get(HeroMoveComp);
const view = e.get(HeroViewComp);
// 只处理己方英雄且处于可移动状态
if (model.fac !== FacSet.HERO) return;
if (!move.moving) return;
// 2. 异常状态检查 (死亡/复活/眩晕/冰冻)
if (model.is_stop || model.is_dead || model.is_reviving || model.isStun() || model.isFrost()) {
if (!model.is_reviving) view.status_change("idle");
return;
}
this.updateRenderOrder(e);
// 3. 核心移动逻辑分发
const nearestEnemy = this.findNearestEnemy(e);
if (nearestEnemy) {
// 战斗状态根据职业类型和rangeType执行智能战术
this.processCombatLogic(e, move, view, model, nearestEnemy);
} else {
// 非战斗状态:回归阵型
this.processReturnFormation(e, move, view, model);
model.is_atking = false;
}
}
/**
* 战斗移动逻辑分发
* 根据 rangeType 决定走位策略
*/
private processCombatLogic(e: ecs.Entity, move: HeroMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
// 优先使用 rangeType 判断,如果没有则回退到 type 判断
let rangeType = model.rangeType;
// 兼容性处理:如果数据未配置 rangeType根据旧的职业类型推断
if (rangeType === undefined) {
if (model.type === HType.warrior || model.type === HType.assassin) {
rangeType = SkillRange.Melee;
} else if (model.type === HType.remote) {
rangeType = SkillRange.Long;
} else {
rangeType = SkillRange.Mid;
}
}
switch (rangeType) {
case SkillRange.Melee:
this.processMeleeLogic(e, move, view, model, enemy);
break;
case SkillRange.Mid:
this.processMidLogic(e, move, view, model, enemy);
break;
case SkillRange.Long:
this.processLongLogic(e, move, view, model, enemy);
break;
default:
this.processMidLogic(e, move, view, model, enemy); // 默认中程
break;
}
}
/**
* 近战逻辑 (Melee)
* 策略:无脑突进,贴脸输出
* 范围:< 75 (攻击距离)
*/
private processMeleeLogic(e: ecs.Entity, move: HeroMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
const currentX = view.node.position.x;
const enemyX = enemy.node.position.x;
const dist = Math.abs(currentX - enemyX);
const attackRange = 75; // 保持原有的近战判定
move.direction = enemyX > currentX ? 1 : -1;
if (dist <= attackRange) {
view.status_change("idle");
model.is_atking = true;
} else {
const speed = model.Attrs[Attrs.SPEED] / 3;
this.moveEntity(view, move.direction, speed);
model.is_atking = false;
}
}
/**
* 中程逻辑 (Mid)
* 策略:保持在中距离,灵活输出
* 范围120 - 360
*/
private processMidLogic(e: ecs.Entity, move: HeroMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
const currentX = view.node.position.x;
const enemyX = enemy.node.position.x;
const dist = Math.abs(currentX - enemyX);
const minRange = 120;
const maxRange = 360;
move.direction = enemyX > currentX ? 1 : -1;
if (dist < minRange) {
// 太近了,后撤
this.performRetreat(view, move, model, currentX);
} else if (dist > maxRange) {
// 太远了,追击
const speed = model.Attrs[Attrs.SPEED] / 3;
this.moveEntity(view, move.direction, speed);
model.is_atking = false;
} else {
// 距离合适,站桩输出
view.status_change("idle");
model.is_atking = true;
}
}
/**
* 远程逻辑 (Long)
* 策略:保持在远距离,最大化生存
* 范围360 - 720
*/
private processLongLogic(e: ecs.Entity, move: HeroMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
const currentX = view.node.position.x;
const enemyX = enemy.node.position.x;
const dist = Math.abs(currentX - enemyX);
const minRange = 360;
const maxRange = 720;
move.direction = enemyX > currentX ? 1 : -1;
if (dist < minRange) {
// 太近了,后撤 (远程单位对距离更敏感)
this.performRetreat(view, move, model, currentX);
} else if (dist > maxRange) {
// 太远了,追击
const speed = model.Attrs[Attrs.SPEED] / 3;
this.moveEntity(view, move.direction, speed);
model.is_atking = false;
} else {
// 距离合适,站桩输出
view.status_change("idle");
model.is_atking = true;
}
}
/** 执行后撤逻辑 */
private performRetreat(view: HeroViewComp, move: HeroMoveComp, model: HeroAttrsComp, currentX: number) {
const safeRetreatX = currentX - move.direction * 50;
if (safeRetreatX >= -300 && safeRetreatX <= 300) {
const retreatSpeed = (model.Attrs[Attrs.SPEED] / 3) * 0.8;
this.moveEntity(view, -move.direction, retreatSpeed);
model.is_atking = false;
} else {
// 退无可退,被迫反击
view.status_change("idle");
model.is_atking = true;
}
}
/**
* 回归阵型逻辑
* 策略:无敌人时回到预设的 targetX
*/
private processReturnFormation(e: ecs.Entity, move: HeroMoveComp, view: HeroViewComp, model: HeroAttrsComp) {
const currentX = view.node.position.x;
let targetX = move.targetX;
// 简单的防重叠偏移
if (this.isPositionOccupied(targetX, e)) {
targetX -= 50;
}
if (Math.abs(currentX - targetX) > 5) {
const dir = targetX > currentX ? 1 : -1;
const speed = model.Attrs[Attrs.SPEED] / 3;
// 修正朝向:回正
move.direction = 1;
this.moveEntity(view, dir, speed);
// 防止过冲
const newX = view.node.position.x;
if ((dir === 1 && newX > targetX) || (dir === -1 && newX < targetX)) {
view.node.setPosition(targetX, view.node.position.y, 0);
}
} else {
view.status_change("idle");
move.direction = 1; // 归位后默认朝右
}
}
/** 通用移动执行 */
private moveEntity(view: HeroViewComp, direction: number, speed: number) {
const delta = speed * this.dt * direction;
const newX = view.node.position.x + delta;
// 地图边界限制 (硬限制)
if (newX >= -320 && newX <= 320) {
view.node.setPosition(newX, view.node.position.y, 0);
view.status_change("move");
} else {
view.status_change("idle");
}
}
// --- 辅助方法 ---
private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null {
const currentView = entity.get(HeroViewComp);
if (!currentView?.node) return null;
const currentPos = currentView.node.position;
const myFac = entity.get(HeroAttrsComp).fac;
let nearest: HeroViewComp | null = null;
let minDis = Infinity;
// 优化查询:一次遍历
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const m = e.get(HeroAttrsComp);
if (m.fac !== myFac && !m.is_dead) {
const v = e.get(HeroViewComp);
if (v?.node) {
const d = Math.abs(currentPos.x - v.node.position.x);
if (d < minDis) {
minDis = d;
nearest = v;
}
}
}
});
return nearest;
}
private isPositionOccupied(targetX: number, self: ecs.Entity): boolean {
const myFac = self.get(HeroAttrsComp).fac;
return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
if (e === self) return false;
const m = e.get(HeroAttrsComp);
if (m.fac !== myFac || m.is_dead) return false;
const v = e.get(HeroViewComp);
return Math.abs(v.node.position.x - targetX) < 30;
});
}
private updateRenderOrder(entity: ecs.Entity) {
// 渲染层级逻辑...
}
}