重构移动系统,将 HeroMoveComp 和 MonMoveComp 合并为通用的 MoveComp 组件,统一移动逻辑。 - 移除 HeroMasterComp 相关代码,简化实体查询 - 统一战斗范围计算和阵型回归逻辑 - 调整移动边界和撤退范围配置 - 优化敌人查找算法,提高性能
292 lines
10 KiB
TypeScript
292 lines
10 KiB
TypeScript
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
|
import { HeroViewComp } from "./HeroViewComp";
|
|
import { HeroAttrsComp } from "./HeroAttrsComp";
|
|
import { smc } from "../common/SingletonModuleComp";
|
|
import { FacSet } from "../common/config/GameSet";
|
|
import { HType } from "../common/config/heroSet";
|
|
import { SkillRange } from "../common/config/SkillSet";
|
|
|
|
@ecs.register('MoveComp')
|
|
export class MoveComp extends ecs.Comp {
|
|
direction: number = 1;
|
|
targetX: number = 0;
|
|
moving: boolean = true;
|
|
targetY: number = 0;
|
|
baseY: number = 0;
|
|
lane: number = 0;
|
|
spawnOrder: number = 0;
|
|
|
|
reset() {
|
|
this.direction = 1;
|
|
this.targetX = 0;
|
|
this.moving = true;
|
|
this.targetY = 0;
|
|
this.baseY = 0;
|
|
this.lane = 0;
|
|
this.spawnOrder = 0;
|
|
}
|
|
}
|
|
|
|
interface MoveFacConfig {
|
|
moveMinX: number;
|
|
moveMaxX: number;
|
|
retreatMinX: number;
|
|
retreatMaxX: number;
|
|
}
|
|
|
|
@ecs.register('MoveSystem')
|
|
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
|
private readonly facConfigs: Record<number, MoveFacConfig> = {
|
|
[FacSet.HERO]: {
|
|
moveMinX: -320,
|
|
moveMaxX: 320,
|
|
retreatMinX: -300,
|
|
retreatMaxX: 300,
|
|
},
|
|
[FacSet.MON]: {
|
|
moveMinX: -320,
|
|
moveMaxX: 320,
|
|
retreatMinX: -300,
|
|
retreatMaxX: 300,
|
|
}
|
|
};
|
|
|
|
filter(): ecs.IMatcher {
|
|
return ecs.allOf(MoveComp, HeroViewComp, HeroAttrsComp);
|
|
}
|
|
|
|
update(e: ecs.Entity) {
|
|
if (!smc.mission.play || smc.mission.pause) return;
|
|
const model = e.get(HeroAttrsComp);
|
|
const move = e.get(MoveComp);
|
|
const view = e.get(HeroViewComp);
|
|
if (!model || !move || !view || !view.node) return;
|
|
if (model.fac !== FacSet.HERO && model.fac !== FacSet.MON) return;
|
|
if (!move.moving) return;
|
|
if (model.fac === FacSet.MON && smc.mission.stop_mon_action) {
|
|
view.status_change("idle");
|
|
return;
|
|
}
|
|
if (model.is_stop || model.is_dead || model.is_reviving || model.in_stun || model.in_frost) {
|
|
if (!model.is_reviving) view.status_change("idle");
|
|
return;
|
|
}
|
|
|
|
this.updateRenderOrder(e);
|
|
const nearestEnemy = this.findNearestEnemy(e);
|
|
if (nearestEnemy) {
|
|
this.processCombatLogic(e, move, view, model, nearestEnemy);
|
|
} else {
|
|
move.targetY = 0;
|
|
this.processReturnFormation(e, move, view, model);
|
|
model.is_atking = false;
|
|
}
|
|
}
|
|
|
|
private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
|
let rangeType = model.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;
|
|
}
|
|
}
|
|
|
|
private processMeleeLogic(e: ecs.Entity, move: MoveComp, 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, maxRange] = this.resolveCombatRange(model, 0, 75);
|
|
|
|
move.direction = enemyX > currentX ? 1 : -1;
|
|
|
|
if (dist < minRange) {
|
|
this.performRetreat(view, move, model, currentX);
|
|
} else if (dist <= maxRange) {
|
|
view.status_change("idle");
|
|
model.is_atking = true;
|
|
} else {
|
|
const speed = model.speed / 3;
|
|
this.moveEntity(view, move.direction, speed);
|
|
model.is_atking = false;
|
|
}
|
|
}
|
|
|
|
private processMidLogic(e: ecs.Entity, move: MoveComp, 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, maxRange] = this.resolveCombatRange(model, 120, 360);
|
|
|
|
move.direction = enemyX > currentX ? 1 : -1;
|
|
|
|
if (dist < minRange) {
|
|
// 太近了,后撤
|
|
this.performRetreat(view, move, model, currentX);
|
|
} else if (dist > maxRange) {
|
|
const speed = model.speed / 3;
|
|
this.moveEntity(view, move.direction, speed);
|
|
model.is_atking = false;
|
|
} else {
|
|
view.status_change("idle");
|
|
model.is_atking = true;
|
|
}
|
|
}
|
|
|
|
private processLongLogic(e: ecs.Entity, move: MoveComp, 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, maxRange] = this.resolveCombatRange(model, 360, 720);
|
|
|
|
move.direction = enemyX > currentX ? 1 : -1;
|
|
|
|
if (dist < minRange) {
|
|
// 太近了,后撤 (远程单位对距离更敏感)
|
|
this.performRetreat(view, move, model, currentX);
|
|
} else if (dist > maxRange) {
|
|
const speed = model.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: MoveComp, model: HeroAttrsComp, currentX: number) {
|
|
const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO];
|
|
const safeRetreatX = currentX - move.direction * 50;
|
|
if (safeRetreatX >= cfg.retreatMinX && safeRetreatX <= cfg.retreatMaxX) {
|
|
const retreatSpeed = (model.speed / 3) * 0.8;
|
|
this.moveEntity(view, -move.direction, retreatSpeed);
|
|
model.is_atking = false;
|
|
} else {
|
|
view.status_change("idle");
|
|
model.is_atking = true;
|
|
}
|
|
}
|
|
|
|
private processReturnFormation(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
|
|
const currentX = view.node.position.x;
|
|
const targetX = this.getFixedFormationX(model);
|
|
move.targetX = targetX;
|
|
|
|
if (Math.abs(currentX - targetX) > 5) {
|
|
const dir = targetX > currentX ? 1 : -1;
|
|
const speed = model.speed / 3;
|
|
move.direction = dir;
|
|
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");
|
|
}
|
|
}
|
|
|
|
private getFixedFormationX(model: HeroAttrsComp): number {
|
|
let rangeType = model.rangeType;
|
|
if (rangeType === undefined || rangeType === null) {
|
|
if (model.type === HType.remote) {
|
|
rangeType = SkillRange.Long;
|
|
} else if (model.type === HType.mage || model.type === HType.support) {
|
|
rangeType = SkillRange.Mid;
|
|
} else {
|
|
rangeType = SkillRange.Melee;
|
|
}
|
|
}
|
|
const side = model.fac === FacSet.MON ? 1 : -1;
|
|
if (rangeType === SkillRange.Long) {
|
|
return 240 * side;
|
|
}
|
|
if (rangeType === SkillRange.Mid) {
|
|
return 200 * side;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private moveEntity(view: HeroViewComp, direction: number, speed: number) {
|
|
const model = view.ent.get(HeroAttrsComp);
|
|
const move = view.ent.get(MoveComp);
|
|
if (!model || !move) return;
|
|
const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO];
|
|
const currentX = view.node.position.x;
|
|
const delta = speed * this.dt * direction;
|
|
let newX = view.node.position.x + delta;
|
|
if (currentX < cfg.moveMinX && direction < 0) {
|
|
view.status_change("idle");
|
|
return;
|
|
}
|
|
if (currentX > cfg.moveMaxX && direction > 0) {
|
|
view.status_change("idle");
|
|
return;
|
|
}
|
|
newX = Math.max(cfg.moveMinX, Math.min(cfg.moveMaxX, newX));
|
|
const newY = view.node.position.y;
|
|
view.node.setPosition(newX, newY, 0);
|
|
view.status_change("move");
|
|
}
|
|
|
|
private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] {
|
|
const minRange = model.getCachedMinSkillDistance();
|
|
const maxRange = model.getCachedMaxSkillDistance();
|
|
if (maxRange <= 0) return [defaultMin, defaultMax];
|
|
const safeMin = Math.max(0, Math.min(minRange, maxRange - 20));
|
|
return [safeMin, maxRange];
|
|
}
|
|
|
|
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 updateRenderOrder(entity: ecs.Entity) {
|
|
return;
|
|
}
|
|
}
|