feat(monster&spawn): 新增飞行怪物支持,重构怪物移动与刷怪系统

抽离MonMoveComp拆分怪物移动逻辑,让MoveComp仅负责英雄移动
新增Fly和FlyBoss怪物类型,配置三层飞行轨道支持空中怪物
重写波次刷怪逻辑,移除固定5槽限制,按轨道自由排布怪物
将怪物生成上限与恢复阈值从5/3调整为50/30
优化渲染排序逻辑,为飞行怪添加持续浮动动画
移除跨波怪物属性继承,波次切换时自动清理残留怪物
This commit is contained in:
walkpan
2026-05-12 12:23:37 +08:00
parent 92db480baf
commit e7075004fe
7 changed files with 390 additions and 264 deletions

View File

@@ -5,6 +5,7 @@ import { smc } from "../common/SingletonModuleComp";
import { FacSet } from "../common/config/GameSet";
import { HeroDisVal, HType } from "../common/config/heroSet";
import { BoxCollider2D, Node } from "cc";
import { MonMoveComp } from "./MonMoveComp";
@ecs.register('MoveComp')
export class MoveComp extends ecs.Comp {
@@ -47,8 +48,10 @@ interface MoveFacConfig {
@ecs.register('MoveSystem')
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
/** 近战判定射程(来自 heroSet */
private readonly meleeAttackRange = HeroDisVal[HType.Melee];
/** 近战判定射程 */
private readonly meleeAttackRange = 250;
/** 远程判定射程 */
private readonly longAttackRange = 600;
private readonly heroFrontAnchorX = -100;
private readonly monFrontAnchorX = 0;
/** 常规同阵营横向最小间距(英雄) */
@@ -106,21 +109,13 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
/** 战斗未开始/暂停时不驱动移动 */
if (!smc.mission.play || smc.mission.pause) return;
/** 在战斗阶段,英雄和怪物不移动 */
if (smc.mission.in_fight) 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 (model.fac !== FacSet.HERO) return; // 只处理英雄移动
if (!move.moving) return;
/** 关卡阶段性冻结怪物行为 */
if (model.fac === FacSet.MON && 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");
@@ -131,7 +126,10 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (view.node.position.y !== move.baseY) {
view.node.setPosition(view.node.position.x, move.baseY, 0);
}
this.updateRenderOrder(view);
// 渲染层级重排放在独立的系统或这里统筹,这里我们让它处理所有英雄和怪物的排序
this.updateRenderOrder();
const nearestEnemy = this.findNearestEnemy(e);
if (nearestEnemy) {
/** 有敌人:进入战斗位移逻辑 */
@@ -169,12 +167,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const dist = Math.abs(selfX - enemyX);
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
if (rangeType === HType.Melee) return dist <= this.meleeAttackRange;
if (rangeType === HType.Long) {
const [, maxRange] = this.resolveCombatRange(model, 360, 720);
return dist <= maxRange;
}
const [, maxRange] = this.resolveCombatRange(model, 120, 720);
return dist <= maxRange;
return dist <= this.longAttackRange;
}
private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
@@ -366,30 +359,42 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return nearest;
}
private updateRenderOrder(view: HeroViewComp) {
private updateRenderOrder() {
const scene = smc.map?.MapView?.scene;
const actorRoot = scene?.entityLayer?.node?.getChildByName("HERO");
if (!actorRoot) return;
if (view.node.parent !== actorRoot) {
view.node.parent = actorRoot;
}
const now = Date.now() / 1000;
if (now - this.lastRenderSortAt < this.renderSortInterval) return;
this.lastRenderSortAt = now;
this.renderEntryCount = 0;
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
ecs.query(this.getHeroViewMatcher()).forEach(e => {
const attrs = e.get(HeroAttrsComp);
const actorView = e.get(HeroViewComp);
const actorMove = e.get(MoveComp);
if (!attrs || !actorView?.node || !actorMove || attrs.is_dead) return;
if (!attrs || !actorView?.node || attrs.is_dead) return;
if (attrs.fac !== FacSet.HERO && attrs.fac !== FacSet.MON) return;
if (actorView.node.parent !== actorRoot) {
actorView.node.parent = actorRoot;
}
const frontScore = attrs.fac === FacSet.HERO ? actorView.node.position.x : -actorView.node.position.x;
// 获取 spawnOrder由于可能挂载 MoveComp 或 MonMoveComp我们需要动态获取
let spawnOrder = 0;
const heroMove = e.get(MoveComp);
if (heroMove) {
spawnOrder = heroMove.spawnOrder;
} else {
const monMove = e.get(MonMoveComp);
if (monMove) {
spawnOrder = monMove.spawnOrder;
}
}
const isFly = actorView.node.position.y > 50;
// 飞行怪在最前,其次是地面的 X 坐标
const frontScore = isFly ? 999999 - actorView.node.position.x : (attrs.fac === FacSet.HERO ? actorView.node.position.x : -actorView.node.position.x);
const entryIndex = this.renderEntryCount;
let entry = this.renderEntries[entryIndex];
if (!entry) {
@@ -399,7 +404,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
entry.node = actorView.node;
entry.bossPriority = attrs.is_boss ? 1 : 0;
entry.frontScore = frontScore;
entry.spawnOrder = actorMove.spawnOrder;
entry.spawnOrder = spawnOrder;
entry.eid = e.eid;
this.renderEntryCount += 1;
});