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 { HeroDisVal, HType } from "../common/config/heroSet"; import { Node } from "cc"; @ecs.register('MonMoveComp') export class MonMoveComp extends ecs.Comp { /** 朝向:1=向右,-1=向左 */ direction: number = -1; /** 当前移动目标 X */ targetX: number = 0; /** 是否允许移动(出生落地前会短暂关闭) */ moving: boolean = true; /** 站位基准 Y */ baseY: number = 0; /** 出生序,用于同条件渲染排序稳定 */ spawnOrder: number = 0; reset() { this.direction = -1; this.targetX = 0; this.moving = true; this.baseY = 0; this.spawnOrder = 0; } } @ecs.register('MonMoveSystem') export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { /** 渲染层级重排节流,避免每帧排序 */ private readonly renderSortInterval = 0.05; private lastRenderSortAt = 0; private monMoveMatcher: ecs.IMatcher | null = null; private heroViewMatcher: ecs.IMatcher | null = null; private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number }[] = []; private renderEntryCount = 0; private getMonMoveMatcher(): ecs.IMatcher { if (!this.monMoveMatcher) { this.monMoveMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp, MonMoveComp); } return this.monMoveMatcher; } private getHeroViewMatcher(): ecs.IMatcher { if (!this.heroViewMatcher) { this.heroViewMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp); } return this.heroViewMatcher; } filter(): ecs.IMatcher { return ecs.allOf(MonMoveComp, HeroViewComp, HeroAttrsComp); } update(e: ecs.Entity) { /** 战斗未开始/暂停时不驱动移动 */ if (!smc.mission.play || smc.mission.pause) return; const model = e.get(HeroAttrsComp); const move = e.get(MonMoveComp); const view = e.get(HeroViewComp); if (!model || !move || !view || !view.node) return; if (model.fac !== FacSet.MON) return; if (!move.moving) return; /** 关卡阶段性冻结怪物行为 */ if (smc.mission.stop_mon_action) { this.clearCombatTarget(model); view.status_change("idle"); return; } if (model.is_stop || model.is_dead || model.is_reviving || model.isFrost()) { this.clearCombatTarget(model); if (!model.is_reviving) view.status_change("idle"); return; } /** 所有移动都锁定在 baseY,避免出现“漂移” */ if (view.node.position.y !== move.baseY) { view.node.setPosition(view.node.position.x, move.baseY, 0); } // 渲染层级统交由 MoveSystem 统一处理,避免两个 System 争抢 setSiblingIndex // 仅在战斗中才处理索敌和移动 if (!smc.mission.in_fight) return; const nearestEnemy = this.findNearestEnemy(e); if (nearestEnemy) { /** 有敌人:进入战斗位移逻辑 */ this.processCombatLogic(e, move, view, model, nearestEnemy); this.syncCombatTarget(model, view, nearestEnemy); } else { /** 无敌人:继续向左推进 */ this.clearCombatTarget(model); model.is_atking = false; this.moveEntity(view, -1, model.speed / 3); } } private clearCombatTarget(model: HeroAttrsComp): void { model.combat_target_eid = -1; model.enemy_in_cast_range = false; } private syncCombatTarget(model: HeroAttrsComp, selfView: HeroViewComp, enemyView: HeroViewComp): void { if (!enemyView || !enemyView.node || !enemyView.ent) { this.clearCombatTarget(model); return; } const enemyAttrs = enemyView.ent.get(HeroAttrsComp); if (!enemyAttrs || enemyAttrs.is_dead || enemyAttrs.is_reviving || enemyAttrs.fac === model.fac) { this.clearCombatTarget(model); return; } model.combat_target_eid = enemyView.ent.eid; model.enemy_in_cast_range = this.isEnemyInAttackRange(model, selfView.node.position.x, enemyView.node.position.x); } private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean { const dist = Math.abs(selfX - enemyX); const rangeType = model.type as HType.Melee | HType.Mid | HType.Long; const attackRange = HeroDisVal[rangeType]; return dist <= attackRange; } private processCombatLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { const selfX = view.node.position.x; const enemyX = enemy.node.position.x; const dist = Math.abs(selfX - enemyX); const inRange = this.isEnemyInAttackRange(model, selfX, enemyX); // 接触判定距离,只有接触英雄才停止移动 const touchDistance = 50; const isTouching = dist <= touchDistance; // 攻击判定 if (inRange) { model.is_atking = true; } else { model.is_atking = false; } // 移动判定:只有接触了英雄才停止移动,否则继续向英雄方向移动 if (isTouching) { view.status_change("idle"); } else { const dir = enemyX > selfX ? 1 : -1; this.moveEntity(view, dir, model.speed / 3); } } private moveEntity(view: HeroViewComp, direction: number, speed: number) { const model = view.ent.get(HeroAttrsComp); const move = view.ent.get(MonMoveComp); if (!model || !move) return; // 简化的边界限制(怪物主要往左走,英雄防线在左侧,-999999 代表左侧尽头) const moveMinX = -999999; const moveMaxX = 999999; const currentX = view.node.position.x; const delta = speed * this.dt * direction; let newX = currentX + delta; newX = Math.max(moveMinX, Math.min(moveMaxX, newX)); if (Math.abs(newX - currentX) < 0.01) { view.status_change("idle"); return; } view.node.setPosition(newX, move.baseY, 0); move.direction = direction; // 确保怪物的朝向表现,向左走 scale=-1,向右走 scale=1 if (direction < 0) { view.scale = -1; } else if (direction > 0) { view.scale = 1; } 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; /** 一次遍历筛出最近敌人(仅比较 x 轴距离,忽略 Y,飞行和地面都能互相攻击) */ ecs.query(this.getHeroViewMatcher()).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; } }