refactor(mission): 调整英雄出生位置判定逻辑

将英雄出生位置的职业判定改为攻击类型判定,更新了注释和相关映射变量,统一了落点偏移的计算逻辑。
This commit is contained in:
pan
2026-08-18 09:15:02 +08:00
parent 1cc09f3450
commit 9cd4cbb53d

View File

@@ -21,10 +21,10 @@
* revive 系技能为"濒死回血"(血量归零时消耗次数回血续命),不触发真死。
*
* 出生点规则:
* 按职业HRole分三排士/坦克 → 前排x=-200最靠右迎敌
* 射手/刺客 → 中排x=-260辅助/法师 → 后排x=-320最靠左
* 同一职业重复召唤时,从该职业基准点向两侧 ±SLOT_OFFSET 依次交替展开,避免落点重叠。
* posIndex 取 heroGrid 全局空闲下标0~9用于网格登记/索敌,落点偏移与 posIndex 解耦。
* 按攻击类型HType分三排战 → 前排x=-200最靠右迎敌
* 中距 → 中排x=-260远程 → 后排x=-320最靠左
* 同一类型重复召唤时,从该类型基准点向两侧 ±SLOT_OFFSET 依次交替展开,避免落点重叠。
* posIndex 取 heroGrid 全局空闲下标0~9用于网格登记/索敌,落点偏移与 posIndex、职业均解耦。
*
* 依赖:
* - Herohero/Hero.ts—— 英雄 ECS 实体类
@@ -39,7 +39,7 @@ import { Hero } from "../hero/Hero";
import { smc } from "../common/SingletonModuleComp";
import { Timer } from "db://oops-framework/core/common/timer/Timer";
import { GameEvent } from "../common/config/GameEvent";
import { HeroInfo, HRole } from "../common/config/heroSet";
import { HeroInfo, HType } from "../common/config/heroSet";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { FacSet, FightSet, BoxSet } from "../common/config/GameSet";
@@ -80,19 +80,16 @@ export class MissionHeroComp extends CCComp {
];
/**
* 职业 → 出生排映射:
* 战士/坦克 → 前排,射手/刺客 → 中排,辅助/法师 → 后排。
* 攻击类型 → 出生排映射:
* 战 → 前排,中距 → 中排,远程 → 后排。
*/
private static readonly ROLE_ROW: Record<HRole, Vec3> = {
[HRole.Tank]: MissionHeroComp.HERO_ROW_FRONT,
[HRole.Warrior]: MissionHeroComp.HERO_ROW_FRONT,
[HRole.Assassin]: MissionHeroComp.HERO_ROW_MID,
[HRole.Archer]: MissionHeroComp.HERO_ROW_MID,
[HRole.Mage]: MissionHeroComp.HERO_ROW_BACK,
[HRole.Support]: MissionHeroComp.HERO_ROW_BACK,
private static readonly TYPE_ROW: Record<HType, Vec3> = {
[HType.Melee]: MissionHeroComp.HERO_ROW_FRONT,
[HType.Mid]: MissionHeroComp.HERO_ROW_MID,
[HType.Long]: MissionHeroComp.HERO_ROW_BACK,
};
/** 同职业重复召唤时的落点横向间隔(像素) */
/** 同类型重复召唤时的落点横向间隔(像素) */
private static readonly SLOT_OFFSET = 30
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */
@@ -232,35 +229,34 @@ export class MissionHeroComp extends CCComp {
/**
* 为英雄分配出生槽位:
* 按职业取三排基准点(战士/坦克前排、射手/刺客中排、辅助/法师后排),
* 同职业第 1 个落基准点,之后按 ±SLOT_OFFSET 交替展开0, +20, -20, +40, -40 …)。
* 按攻击类型取三排基准点(近战前排、中距中排、远程后排),
* 同类型第 1 个落基准点,之后按 ±SLOT_OFFSET 交替展开0, +30, -30, +60, -60 …)。
*
* 槽位编码posIndex 直接取 heroGrid 的全局空闲下标0~9不做 role 分段,
* 避免 role*10 编码超出 heroGrid 容量(长度 10导致非坦克职业全部越界兜底
* 落点偏移由「同职业已占位英雄数」推展开序 n 计算,与 posIndex 解耦。
* 槽位编码posIndex 直接取 heroGrid 的全局空闲下标0~9与落点无映射。
* 落点偏移由「同攻击类型已占位英雄的 x 偏移」推展开序计算,与 posIndex、职业均解耦
*
* @param role 英雄职业(决定所属排)
* @param type 英雄攻击类型HType决定所属排)
* @param heroes 当前存活英雄列表spawnHeroAt 在实体创建前快照,避免复用池残留实体干扰槽位判定)
* @returns { posIndex heroGrid 空闲下标(-1 表示满员未登记), landingPos 落地点 }
*/
private pickSpawnSlotForHero(role: HRole, heroes: Hero[]): { posIndex: number; landingPos: Vec3 } {
const basePos = MissionHeroComp.ROLE_ROW[role] ?? MissionHeroComp.HERO_ROW_MID;
private pickSpawnSlotForHero(type: HType, heroes: Hero[]): { posIndex: number; landingPos: Vec3 } {
const basePos = MissionHeroComp.TYPE_ROW[type] ?? MissionHeroComp.HERO_ROW_MID;
// 同职业已占位的展开序集合(依据落点 x 反推,与 posIndex 解耦)
// 同攻击类型已占位的 x 偏移集合(依据落点 x 反推,与 posIndex 解耦)
const usedOffsets = new Set<number>();
const occupiedGrid = new Set<number>();
for (const h of heroes) {
const m = h.get(HeroAttrsComp)!;
if (m.posIndex >= 0) occupiedGrid.add(m.posIndex);
if (m.role !== role) continue;
if (m.type !== type) continue;
const view = h.get(HeroViewComp);
if (view?.node?.isValid) {
usedOffsets.add(Math.round(view.node.position.x - basePos.x));
}
}
// 展开序 n0=居中1=+202=-203=+40 …,找到第一个未被同职业占用的偏移
// 上限取 heroGrid 容量,同职业超过该数量时复用基准点(不再向外展开)
// 展开序 n0=居中1=+302=-303=+60 …,找到第一个未被同类型占用的偏移
// 上限取 heroGrid 容量,同类型超过该数量时复用基准点(不再向外展开)
let offsetX = 0;
for (let n = 0; n < smc.mission.heroGrid.length; n++) {
const step = Math.ceil(n / 2);
@@ -306,19 +302,19 @@ export class MissionHeroComp extends CCComp {
private spawnHeroAt(uuid: number, hero_lv: number, card_lv: number, posIndex: number, inheritPos: Vec3 | null = null) {
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
const role = HeroInfo[uuid]?.role ?? HRole.Warrior;
const type = HeroInfo[uuid]?.type ?? HType.Mid;
let finalPosIndex = posIndex;
let landingPos: Vec3;
if (inheritPos) {
// 合成继承沿用旧英雄节点位置posIndex 透传登记网格
landingPos = v3(inheritPos.x, MissionHeroComp.ROLE_ROW[role]?.y ?? BoxSet.GAME_LINE, 0);
landingPos = v3(inheritPos.x, MissionHeroComp.TYPE_ROW[type]?.y ?? BoxSet.GAME_LINE, 0);
} else {
const heroes = this.getAllHeroes().filter(h => {
const m = h.get(HeroAttrsComp);
return !!m && !m.is_dead;
});
const slot = this.pickSpawnSlotForHero(role, heroes);
const slot = this.pickSpawnSlotForHero(type, heroes);
finalPosIndex = slot.posIndex;
landingPos = slot.landingPos;
}