重构怪物移动逻辑,移除移动追击相关代码,改为定点攻击模式,无敌人时原地待机,仅根据攻击范围切换攻击状态与朝向。 重构波次刷怪逻辑,删除分段刷怪阶段处理,改为波次准备结束后批量生成所有怪物。 将原6路刷怪改为3行网格布局,调整怪物出生点的X轴起点与间距。 限制单波最大怪物数量为12,简化刷怪分配逻辑为按生成顺序自动排列行列。 清理冗余的运行时状态变量与废弃函数,优化代码整体结构。 BREAKING CHANGES: 怪物攻击逻辑从移动追击改为定点攻击,移除了MonMoveComp中的moveEntity和resolveCombatRange函数;刷怪系统从6路改为3行网格布局,移除了分段刷怪功能与相关状态变量。
173 lines
6.4 KiB
TypeScript
173 lines
6.4 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 { 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;
|
||
view.status_change("idle");
|
||
}
|
||
}
|
||
|
||
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 attackRange = model.dis;
|
||
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 inRange = this.isEnemyInAttackRange(model, selfX, enemyX);
|
||
|
||
// 攻击判定:怪物在固定位置,如果在攻击范围内则攻击,否则原地待机
|
||
if (inRange) {
|
||
model.is_atking = true;
|
||
// 确保朝向敌人
|
||
const dir = enemyX > selfX ? 1 : -1;
|
||
view.scale = dir;
|
||
} else {
|
||
model.is_atking = false;
|
||
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;
|
||
|
||
/** 一次遍历筛出最近敌人(仅比较 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;
|
||
}
|
||
} |