refactor(monster): 重构怪物生成与敌人选择逻辑

1. 统一调整敌人选取算法,加入Y轴权重实现同路优先攻击
2. 重构怪物出生点位配置,改用硬编码数组统一管理
3. 移除怪物生成时的Y轴随机偏移,固定站位避免逻辑冲突
4. 简化怪物生成接口参数,使用索引直接获取预设点位
This commit is contained in:
pan
2026-06-11 11:13:15 +08:00
parent 0560999ce5
commit 257dfe4c15
3 changed files with 42 additions and 27 deletions

View File

@@ -81,6 +81,7 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
} }
/** 所有移动都锁定在 baseY避免出现“漂移” */ /** 所有移动都锁定在 baseY避免出现“漂移” */
// 注意:不再在 MonMoveComp 强行重置 X 轴坐标,避免与其他表现逻辑冲突
if (view.node.position.y !== move.baseY) { if (view.node.position.y !== move.baseY) {
view.node.setPosition(view.node.position.x, move.baseY, 0); view.node.setPosition(view.node.position.x, move.baseY, 0);
} }
@@ -154,13 +155,15 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
let nearest: HeroViewComp | null = null; let nearest: HeroViewComp | null = null;
let minDis = Infinity; let minDis = Infinity;
/** 一次遍历筛出最近敌人(仅比较 x 轴距离,忽略 Y飞行和地面都能互相攻击 */ /** 遍历筛出最近敌人:以 X 轴距离为主Y 轴距离作为同排的决胜权重,使角色优先攻击同路的敌人 */
ecs.query(this.getHeroViewMatcher()).forEach(e => { ecs.query(this.getHeroViewMatcher()).forEach(e => {
const m = e.get(HeroAttrsComp); const m = e.get(HeroAttrsComp);
if (m.fac !== myFac && !m.is_dead) { if (m.fac !== myFac && !m.is_dead) {
const v = e.get(HeroViewComp); const v = e.get(HeroViewComp);
if (v?.node) { if (v?.node) {
const d = Math.abs(currentPos.x - v.node.position.x); const dx = Math.abs(currentPos.x - v.node.position.x);
const dy = Math.abs(currentPos.y - v.node.position.y);
const d = dx + dy * 0.1; // Y轴权重较小仅在 X 相近时起决定作用
if (d < minDis) { if (d < minDis) {
minDis = d; minDis = d;
nearest = v; nearest = v;

View File

@@ -353,13 +353,15 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
let nearest: HeroViewComp | null = null; let nearest: HeroViewComp | null = null;
let minDis = Infinity; let minDis = Infinity;
/** 一次遍历筛出最近敌人(仅比较 x 轴距离) */ /** 遍历筛出最近敌人:以 X 轴距离为主Y 轴距离作为同排的决胜权重,使角色优先攻击同路的敌人 */
ecs.query(this.getHeroViewMatcher()).forEach(e => { ecs.query(this.getHeroViewMatcher()).forEach(e => {
const m = e.get(HeroAttrsComp); const m = e.get(HeroAttrsComp);
if (m.fac !== myFac && !m.is_dead) { if (m.fac !== myFac && !m.is_dead) {
const v = e.get(HeroViewComp); const v = e.get(HeroViewComp);
if (v?.node) { if (v?.node) {
const d = Math.abs(currentPos.x - v.node.position.x); const dx = Math.abs(currentPos.x - v.node.position.x);
const dy = Math.abs(currentPos.y - v.node.position.y);
const d = dx + dy * 0.1; // Y轴权重较小仅在 X 相近时起决定作用
if (d < minDis) { if (d < minDis) {
minDis = d; minDis = d;
nearest = v; nearest = v;

View File

@@ -53,14 +53,28 @@ export class MissionMonCompComp extends CCComp {
/** 怪物最多 12 个 */ /** 怪物最多 12 个 */
private static readonly MAX_MONSTERS = 12; private static readonly MAX_MONSTERS = 12;
/** 怪物出生点起点 X */
private static readonly MON_SPAWN_START_X = 60;
/** 怪物出生的 X 间距 (列距) */
private static readonly MON_SPAWN_GAP_X = 80;
/** 怪物出生掉落高度 */ /** 怪物出生掉落高度 */
private static readonly MON_DROP_HEIGHT = 0; private static readonly MON_DROP_HEIGHT = 0;
/** 3行高度偏移 (行距) */
private static readonly ROW_Y_OFFSETS = [90, 0, -90]; /** 硬编码的 12 个怪物占位点 (3行4列) */
public static readonly MON_POSITIONS: Vec3[] = [
// 第 1 列 (X=60)
v3(60, BoxSet.GAME_LINE + 90, 0), // index 0: Top
v3(60, BoxSet.GAME_LINE, 0), // index 1: Mid
v3(60, BoxSet.GAME_LINE - 90, 0), // index 2: Bot
// 第 2 列 (X=140)
v3(140, BoxSet.GAME_LINE + 90, 0), // index 3: Top
v3(140, BoxSet.GAME_LINE, 0), // index 4: Mid
v3(140, BoxSet.GAME_LINE - 90, 0), // index 5: Bot
// 第 3 列 (X=220)
v3(220, BoxSet.GAME_LINE + 90, 0), // index 6: Top
v3(220, BoxSet.GAME_LINE, 0), // index 7: Mid
v3(220, BoxSet.GAME_LINE - 90, 0), // index 8: Bot
// 第 4 列 (X=300)
v3(300, BoxSet.GAME_LINE + 90, 0), // index 9: Top
v3(300, BoxSet.GAME_LINE, 0), // index 10: Mid
v3(300, BoxSet.GAME_LINE - 90, 0), // index 11: Bot
];
// ======================== 编辑器属性 ======================== // ======================== 编辑器属性 ========================
@@ -202,10 +216,9 @@ export class MissionMonCompComp extends CCComp {
MonList[MonType.LongBoss].includes(item.uuid); MonList[MonType.LongBoss].includes(item.uuid);
const spawnIndex = this.waveSpawnedCount++; const spawnIndex = this.waveSpawnedCount++;
const row = spawnIndex % 3; const targetPosIndex = spawnIndex % MissionMonCompComp.MAX_MONSTERS;
const col = Math.floor(spawnIndex / 3);
// 构造一个模拟的 GeneratedMonster 数据传递给 addMonsterAt // 构造一个模拟的 GeneratedMonster 数据传递给 addMonsterAtGrid
const base = HeroInfo[item.uuid]; const base = HeroInfo[item.uuid];
const monData: GeneratedMonster = { const monData: GeneratedMonster = {
uuid: item.uuid, uuid: item.uuid,
@@ -216,7 +229,7 @@ export class MissionMonCompComp extends CCComp {
isBoss: isBoss, isBoss: isBoss,
spawnIndex: 0 spawnIndex: 0
}; };
this.addMonsterAtGrid(row, col, monData, item.level); this.addMonsterAtGrid(targetPosIndex, monData, item.level);
} }
// ======================== 波次管理 ======================== // ======================== 波次管理 ========================
@@ -243,10 +256,9 @@ export class MissionMonCompComp extends CCComp {
let count = Math.min(this.pendingMonsters.length, MissionMonCompComp.MAX_MONSTERS); let count = Math.min(this.pendingMonsters.length, MissionMonCompComp.MAX_MONSTERS);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const monData = this.pendingMonsters.shift()!; const monData = this.pendingMonsters.shift()!;
const row = this.waveSpawnedCount % 3; const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MAX_MONSTERS;
const col = Math.floor(this.waveSpawnedCount / 3);
console.log(`[MissionMonComp] [PhasePrepareEnd] 准备生成怪物 UUID=${monData.uuid}, 当前已生成数量=${this.waveSpawnedCount}`); console.log(`[MissionMonComp] [PhasePrepareEnd] 准备生成怪物 UUID=${monData.uuid}, 当前已生成数量=${this.waveSpawnedCount}`);
this.addMonsterAtGrid(row, col, monData); this.addMonsterAtGrid(targetPosIndex, monData);
this.waveSpawnedCount++; this.waveSpawnedCount++;
} }
// 生成完毕后清空 pendingMonsters // 生成完毕后清空 pendingMonsters
@@ -279,30 +291,28 @@ export class MissionMonCompComp extends CCComp {
// ======================== 怪物生成 ======================== // ======================== 怪物生成 ========================
/** /**
* 在指定层级、指定索引处生成一个怪物: * 在指定位置索引处生成一个怪物:
* *
* @param row 行 (0, 1, 2) * @param posIndex 位置索引 (0-11)
* @param col 列 (0, 1, 2, 3)
* @param monData 新引擎生成的怪物数据 (含 uuid, hp, ap, affixes 等) * @param monData 新引擎生成的怪物数据 (含 uuid, hp, ap, affixes 等)
* @param monLv 怪物等级 (仅对旧有的 level 参数做兼容,实际属性由 monData 决定) * @param monLv 怪物等级 (仅对旧有的 level 参数做兼容,实际属性由 monData 决定)
*/ */
private addMonsterAtGrid( private addMonsterAtGrid(
row: number, posIndex: number,
col: number,
monData: GeneratedMonster, monData: GeneratedMonster,
monLv: number = 1 monLv: number = 1
) { ) {
let mon = ecs.getEntity<Monster>(Monster); let mon = ecs.getEntity<Monster>(Monster);
let scale = -1; let scale = -1;
// 计算坐标 // 获取硬编码的占位点坐标,不再使用随机偏移
const spawnX = MissionMonCompComp.MON_SPAWN_START_X + col * MissionMonCompComp.MON_SPAWN_GAP_X; const basePos = MissionMonCompComp.MON_POSITIONS[posIndex];
const randomY = Math.random() * 20 - 10; // -10 到 10 的随机Y轴偏移 const spawnX = basePos.x;
const landingY = BoxSet.GAME_LINE + MissionMonCompComp.ROW_Y_OFFSETS[row] + randomY + (monData.isBoss ? 6 : 0); const landingY = basePos.y + (monData.isBoss ? 6 : 0);
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0); const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999; this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, row); mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, posIndex);
// 设置渲染排序 // 设置渲染排序
const move = mon.get(MonMoveComp); const move = mon.get(MonMoveComp);