Files
heros/assets/script/game/hero/HeroMove.ts
panw 087f4010be refactor(渲染): 重构实体层级管理方式
- 移除通过 setSiblingIndex 手动设置层级的方式
- 新增 HERO、LINE1、LINE2、SKILL 等容器节点自动管理层级
- 调整英雄、怪物、技能等实体的父节点到对应容器
- 优化提示信息的位置偏移量
2025-11-04 14:23:07 +08:00

346 lines
14 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";
/** 英雄移动组件 */
@ecs.register('HeroMove')
export class HeroMoveComp extends ecs.Comp {
/** 移动方向1向右-1向左 */
direction: number = 1;
/** 目标x坐标 */
targetX: number = 0;
/** 是否处于移动状态 */
moving: boolean = true;
/** 当前朝向缓存避免频繁setScale */
currentFacing: number = 1;
reset() {
this.direction = 1;
this.targetX = 0;
this.moving = true;
this.currentFacing = 1;
}
}
/** 英雄移动系统 - 专门处理英雄的移动逻辑 */
@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) {
if (!smc.mission.play || smc.mission.pause) return;
const move = e.get(HeroMoveComp);
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
// 只处理英雄
if (model.fac !== FacSet.HERO) return;
if (!move.moving) return;
const shouldStopInFace = this.checkEnemiesInFace(e);
const shouldStopAtMinRange = this.shouldStopAtMinSkillRange(e);
const shouldStop = shouldStopInFace || shouldStopAtMinRange;
model.is_atking = this.checkEnemiesInSkillRange(e);
// 更新渲染层级
this.updateRenderOrder(e);
if (!shouldStop) {
if (model.is_stop || model.is_dead || model.isStun() || model.isFrost()) {
view.status_change("idle");
return;
}
// 英雄阵营特殊逻辑:根据职业区分行为
const hasEnemies = this.checkEnemiesExist(e);
const isWarrior = model.type === HType.warrior;
// 战士职业:有敌人就向敌人前进
if (isWarrior && hasEnemies) {
const nearestEnemy = this.findNearestEnemy(e);
if (nearestEnemy) {
const enemyX = nearestEnemy.node.position.x;
const currentX = view.node.position.x;
// 根据敌人位置调整移动方向和朝向
if (enemyX > currentX) {
move.direction = 1; // 向右移动
this.setFacing(view, move, 1); // 🔥 使用优化的转向方法
} else {
move.direction = -1; // 向左移动
this.setFacing(view, move, -1); // 🔥 使用优化的转向方法
}
// 继续向敌人方向移动
const delta = (model.Attrs[Attrs.SPEED]/3) * this.dt * move.direction;
const newX = view.node.position.x + delta;
// 对于战士,允许更自由的移动范围
if (newX >= -420 && newX <= 420) { // 使用地图边界
view.status_change("move");
view.node.setPosition(newX, view.node.position.y, 0);
} else {
view.status_change("idle");
}
}
return;
}
// 其他职业或战士无敌人时:回到预定点
const currentX = view.node.position.x;
let finalTargetX = move.targetX;
// 检查预定点是否已被占用
if (this.isPositionOccupied(finalTargetX, e)) {
finalTargetX = move.targetX - 50; // 往前50的位置
}
// 如果不在目标位置,移动到目标位置
if (Math.abs(currentX - finalTargetX) > 1) {
// 确定移动方向
const direction = currentX > finalTargetX ? -1 : 1;
const delta = (model.Attrs[Attrs.SPEED]/3) * this.dt * direction;
const newX = view.node.position.x + delta;
// 🔥 使用优化的转向方法
this.setFacing(view, move, direction);
// 确保不会超过目标位置
if (direction === 1 && newX > finalTargetX) {
view.node.setPosition(finalTargetX, view.node.position.y, 0);
} else if (direction === -1 && newX < finalTargetX) {
view.node.setPosition(finalTargetX, view.node.position.y, 0);
} else {
view.node.setPosition(newX, view.node.position.y, 0);
}
view.status_change("move");
} else {
view.status_change("idle");
// 到达目标位置后,面向右侧(敌人方向)
move.direction = 1;
this.setFacing(view, move, 1); // 🔥 使用优化的转向方法
}
} else {
view.status_change("idle");
// 因为敌人在面前而暂时停止不设置moving为false保持检查状态
}
}
/**
* 🔥 优化的转向方法只在真正需要改变朝向时才调用setScale
* 避免频繁的setScale调用导致碰撞器重新计算
*/
private setFacing(view: HeroViewComp, move: HeroMoveComp, newFacing: number) {
// 只有当朝向真正改变时才更新
if (move.currentFacing !== newFacing) {
move.currentFacing = newFacing;
view.node.setScale(newFacing*view.node.scale.x, 1*view.node.scale.y);
// 安全获取top节点
const topNode = view.node.getChildByName("top");
if (topNode) {
topNode.setScale(newFacing*topNode.scale.x, 1*topNode.scale.y);
}
}
}
/** 检查是否存在敌人 */
private checkEnemiesExist(entity: ecs.Entity): boolean {
const team = entity.get(HeroAttrsComp).fac;
let hasEnemies = false;
ecs.query(ecs.allOf(HeroAttrsComp)).some(e => {
const model = e.get(HeroAttrsComp);
if (model.fac !== team && !model.is_dead) {
hasEnemies = true;
return true;
}
});
return hasEnemies;
}
/** 找到最近的敌人 */
private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null {
const currentView = entity.get(HeroViewComp);
if (!currentView || !currentView.node) return null;
const currentPos = currentView.node.position;
const team = entity.get(HeroAttrsComp).fac;
let nearestEnemyView: HeroViewComp | null = null;
let minDistance = Infinity;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (model.fac !== team && !model.is_dead && view && view.node) {
const distance = Math.abs(currentPos.x - view.node.position.x);
if (distance < minDistance) {
minDistance = distance;
nearestEnemyView = view;
}
}
});
return nearestEnemyView;
}
/** 检测攻击范围内敌人 */
private checkEnemiesInRange(entity: ecs.Entity, range: number): boolean {
const currentView = entity.get(HeroViewComp);
if (!currentView || !currentView.node) return false;
const currentPos = currentView.node.position;
const team = entity.get(HeroAttrsComp).fac;
let found = false;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Math.abs(currentPos.x - view.node.position.x);
if (model.fac !== team && !model.is_dead) {
if (distance <= range) {
found = true;
return true;
}
}
});
return found;
}
/** 检测面前是否有敌人 */
private checkEnemiesInFace(entity: ecs.Entity): boolean {
const currentView = entity.get(HeroViewComp);
if (!currentView || !currentView.node) return false;
const currentPos = currentView.node.position;
const team = entity.get(HeroAttrsComp).fac;
let found = false;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Math.abs(currentPos.x - view.node.position.x);
if (model.fac !== team && !model.is_dead) {
if (distance <= 75) {
found = true;
return true;
}
}
});
return found;
}
/** 检测技能攻击范围内敌人 */
private checkEnemiesInSkillRange(entity: ecs.Entity): boolean {
const currentView = entity.get(HeroViewComp);
const heroAttrs = entity.get(HeroAttrsComp);
if (!currentView || !currentView.node || !heroAttrs) return false;
const currentPos = currentView.node.position;
const team = heroAttrs.fac;
// 使用缓存的最远技能攻击距离判断攻击时机
const maxSkillDistance = heroAttrs.getCachedMaxSkillDistance();
if (maxSkillDistance === 0) return false;
let found = false;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Math.abs(currentPos.x - view.node.position.x);
if (model.fac !== team && !model.is_dead) {
if (distance <= maxSkillDistance) {
found = true;
return true;
}
}
});
return found;
}
/** 更新渲染层级 */
private updateRenderOrder(entity: ecs.Entity) {
const currentView = entity.get(HeroViewComp);
const currentModel = entity.get(HeroAttrsComp);
// 查找所有英雄单位
const allUnits = ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp))
.filter(e => {
const otherModel = e.get(HeroAttrsComp);
return otherModel.fac === currentModel.fac; // 按阵营分组
})
.map(e => e);
// 按x坐标排序x坐标越大越前面的显示在上层
const sortedUnits = allUnits.sort((a, b) => {
const viewA = a.get(HeroViewComp);
const viewB = b.get(HeroViewComp);
if (!viewA || !viewA.node || !viewB || !viewB.node) return 0;
const posA = viewA.node.position.x;
const posB = viewB.node.position.x;
return posA - posB; // x坐标从小到大排序
});
// 设置渲染顺序x坐标越大的显示在上层index越大层级越高
// sortedUnits.forEach((unit, index) => {
// const model = unit.get(HeroViewComp);
// model.node.setSiblingIndex(index); // 直接使用indexx坐标大的index大层级高
// });
}
/** 检查指定位置是否已被占用 */
private isPositionOccupied(targetX: number, currentEntity: ecs.Entity): boolean {
const currentModel = currentEntity.get(HeroAttrsComp);
const occupationRange = 30; // 定义占用范围为30像素
return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
if (e === currentEntity) return false; // 排除自己
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (model.fac !== currentModel.fac) return false; // 只检查同阵营
if (model.is_dead) return false; // 排除死亡单位
const distance = Math.abs(view.node.position.x - targetX);
return distance < occupationRange; // 如果距离小于占用范围,认为被占用
});
}
/** 检查是否应该基于最近技能距离停止移动 */
private shouldStopAtMinSkillRange(entity: ecs.Entity): boolean {
const currentView = entity.get(HeroViewComp);
const heroAttrs = entity.get(HeroAttrsComp);
if (!currentView || !currentView.node || !heroAttrs) return false;
const currentPos = currentView.node.position;
const team = heroAttrs.fac;
// 使用缓存的最近技能攻击距离
const minSkillDistance = heroAttrs.getCachedMinSkillDistance();
if (minSkillDistance === 0) return false;
// 检查是否有敌人在最近技能距离内
return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Math.abs(currentPos.x - view.node.position.x);
if (model.fac !== team && !model.is_dead) {
return distance <= minSkillDistance;
}
return false;
});
}
}