Files
pixelheros/assets/script/game/hero/HeroMove.ts
walkpan bb28492550 feat(英雄AI): 重构英雄移动系统,基于攻击距离类型实现智能战术走位
1. 新增SkillRange枚举定义近/中/远程攻击类型
2. 在HeroAttrsComp和hero配置中添加rangeType字段
3. 重写HeroMoveSystem,根据rangeType实现差异化移动策略
4. 移除技能施放的攻击状态限制,优化AI决策逻辑
2026-01-06 18:26:18 +08:00

292 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;
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) {
// 渲染层级逻辑...
}
}