From 1523aab402a4b3e46cf9bbd0ccd135481d7d7dbc Mon Sep 17 00:00:00 2001 From: pan Date: Thu, 20 Aug 2026 11:11:44 +0800 Subject: [PATCH] =?UTF-8?q?refactor(hero):=20=E7=BB=9F=E4=B8=80=E8=8B=B1?= =?UTF-8?q?=E9=9B=84=E7=BC=96=E9=98=9F=E4=B8=8E=E5=87=BA=E7=94=9F=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E4=BD=8D=E7=BD=AE=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 将硬编码的英雄排位置常量迁移至FightSet枚举,实现单一数据源 2. 重构英雄编队逻辑,按职业优先级分为三排站位 3. 调整召唤队列,按职业优先级排序后串行登场 --- assets/script/game/common/config/GameSet.ts | 4 ++ assets/script/game/hero/MoveComp.ts | 58 +++++++++++----- assets/script/game/map/MissionHeroComp.ts | 75 ++++++++++++--------- 3 files changed, 90 insertions(+), 47 deletions(-) diff --git a/assets/script/game/common/config/GameSet.ts b/assets/script/game/common/config/GameSet.ts index 5a8f2cb6..f4e1ccca 100644 --- a/assets/script/game/common/config/GameSet.ts +++ b/assets/script/game/common/config/GameSet.ts @@ -23,6 +23,10 @@ export enum FacSet { MON = 1, } export enum FightSet { + /** 英雄三排出生/编队基准 x(y 均为 BoxSet.GAME_LINE):前排迎敌、中排、后排靠左 */ + HERO_ROW_FRONT_X = -200, // 前排(最靠右迎敌):坦克 / 战士 + HERO_ROW_MID_X = -260, // 中排:刺客 / 射手 + HERO_ROW_BACK_X = -320, // 后排(最靠左):法师 / 辅助 WAVE_COIN_BASE = 7, // 回合金币基础奖励 WAVE_COIN_GROW = 2, // 回合金币递增值 WAVE_COIN_MAX = 17, // 回合金币最大基础奖励 diff --git a/assets/script/game/hero/MoveComp.ts b/assets/script/game/hero/MoveComp.ts index 053e6cc2..1b21988e 100644 --- a/assets/script/game/hero/MoveComp.ts +++ b/assets/script/game/hero/MoveComp.ts @@ -14,7 +14,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec import { HeroViewComp } from "./HeroViewComp"; import { HeroAttrsComp } from "./HeroAttrsComp"; import { smc } from "../common/SingletonModuleComp"; -import { BoxSet, FacSet } from "../common/config/GameSet"; +import { BoxSet, FacSet, FightSet } from "../common/config/GameSet"; import { HRole } from "../common/config/heroSet"; import { Node } from "cc"; @@ -54,9 +54,16 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate // ======================== 英雄编队排队 ======================== - /** 编队锚点:最右迎敌位(与 MissionHeroComp.HERO_ROW_FRONT 的 x 对齐,避免循环依赖在此独立定义) */ - private static readonly QUEUE_ANCHOR_X = -200; - /** 编队槽位默认间隔(像素);人数超出边界宽度时自动压缩间距 */ + /** + * 三排编队锚点 x(单一数据源取自 FightSet,与 MissionHeroComp 的 HERO_ROW_FRONT/MID/BACK 一致): + * 前排 / 中排 / 后排。准备阶段按"职业优先级名次"映射到三排(见 rebuildHeroQueueSlots)。 + */ + private static readonly ROW_FRONT_X = FightSet.HERO_ROW_FRONT_X; + private static readonly ROW_MID_X = FightSet.HERO_ROW_MID_X; + private static readonly ROW_BACK_X = FightSet.HERO_ROW_BACK_X; + /** 编队排队允许向左扩展到的最小 x(后排自锚点向左排开时的下限) */ + private static readonly QUEUE_LEFT_LIMIT = FightSet.HERO_ROW_BACK_X - 120; + /** 编队槽位默认间隔(像素);后排人数过多时自动压缩间距 */ private static readonly QUEUE_SPACING = 30; /** 编队槽位重算节流间隔(秒):召唤/死亡后最多延迟一次重算 */ private readonly queueRebuildInterval = 0.1; @@ -155,8 +162,9 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate /** * 英雄编队排队: - * 按 坦克→战士→刺客→射手→法师→辅助 的职业优先级(即 HRole 枚举值升序), - * 从最右迎敌位(QUEUE_ANCHOR_X)向左依次排开,防止同类型英雄落点重叠。 + * 按 坦克→战士→刺客→射手→法师→辅助 的职业优先级(即 HRole 枚举值升序)做全局排序, + * 再按名次映射三排锚点:第 1 名→前排、第 2 名→中排、第 3 名起→后排向左排开。 + * 槽位互不重叠,保证一个位置仅一个英雄。 * * @param entity 当前英雄实体 * @param model 英雄属性(取 speed / role) @@ -181,8 +189,10 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate const dir = targetX > selfX ? 1 : -1; move.direction = dir; view.scale = dir; + /** 编队排队允许突破战斗左边界(LETF_END=后排锚点),扩展至 QUEUE_LEFT_LIMIT */ + const queueClamp = (x: number): number => Math.max(MoveSystem.QUEUE_LEFT_LIMIT, x); const delta = (model.speed / 3) * this.dt * dir; - const newX = this.clampByFacBoundary(selfX + delta, model.fac); + const newX = queueClamp(selfX + delta); if (Math.abs(newX - selfX) >= 0.01) { view.node.setPosition(newX, view.node.position.y, 0); view.status_change("move"); @@ -204,8 +214,14 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate /** * 重算编队槽位: - * 收集全部存活英雄,按 职业优先级 → 出生序 → eid 稳定排序后, - * 从 QUEUE_ANCHOR_X 向左按间隔分配;人数过多时压缩间距,保证不越过 LETF_END。 + * 收集全部存活英雄,按 职业优先级(HRole 升序)→ 出生序 → eid 稳定排序得到全局名次 rank, + * 再按名次映射三排锚点: + * - rank 0(最优先)→ 前排 ROW_FRONT_X + * - rank 1 → 中排 ROW_MID_X + * - rank ≥ 2 → 后排,自 ROW_BACK_X 起按 (rank-1) 个间隔向左依次排开 + * 效果:高优先级职业永远占据更靠前的排;当更高优先级英雄插入时,低优先级英雄自动向后让位 + * (如 近战+射手+近战 → 两近战分占前/中排,射手被挤到后排)。 + * 后排人数过多时压缩间距,下限不越过 QUEUE_LEFT_LIMIT。 */ private rebuildHeroQueueSlots(): void { const heroes: { eid: number; role: number; spawnOrder: number }[] = []; @@ -223,15 +239,25 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate (a.eid - b.eid) ); - const count = heroes.length; - const maxWidth = MoveSystem.QUEUE_ANCHOR_X - BoxSet.LETF_END; - const spacing = count > 1 - ? Math.min(MoveSystem.QUEUE_SPACING, maxWidth / (count - 1)) - : 0; + /** 后排人数 = 总名次从 rank2 起的数量,用于预压缩间距 */ + const backCount = Math.max(0, heroes.length - 2); + const backMaxWidth = MoveSystem.ROW_BACK_X - MoveSystem.QUEUE_LEFT_LIMIT; + const backSpacing = backCount > 1 + ? Math.min(MoveSystem.QUEUE_SPACING, backMaxWidth / (backCount - 1)) + : MoveSystem.QUEUE_SPACING; this.queueSlots.length = 0; - for (let i = 0; i < count; i++) { - this.queueSlots.push({ eid: heroes[i].eid, targetX: MoveSystem.QUEUE_ANCHOR_X - i * spacing }); + for (let rank = 0; rank < heroes.length; rank++) { + let targetX: number; + if (rank === 0) { + targetX = MoveSystem.ROW_FRONT_X; + } else if (rank === 1) { + targetX = MoveSystem.ROW_MID_X; + } else { + /** 后排:rank2 在锚点,rank3 起向左排开 */ + targetX = MoveSystem.ROW_BACK_X - (rank - 2) * backSpacing; + } + this.queueSlots.push({ eid: heroes[rank].eid, targetX }); } } diff --git a/assets/script/game/map/MissionHeroComp.ts b/assets/script/game/map/MissionHeroComp.ts index 5a5dcec2..355569e6 100644 --- a/assets/script/game/map/MissionHeroComp.ts +++ b/assets/script/game/map/MissionHeroComp.ts @@ -7,7 +7,8 @@ * 2. 管理英雄的出生点和掉落动画。 * * 关键设计: - * - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。 + * - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**, + * 且队列按职业优先级(坦克→战士→刺客→射手→法师→辅助)排序后依次登场。 * - spawnHeroAt 负责生成英雄;允许同 uuid 重复召唤,多个相同英雄可同时站场。 * - 英雄可通过升级卡(SpecialUpgrade)升级。 * @@ -17,8 +18,8 @@ * revive 系技能为"濒死回血"(血量归零时消耗次数回血续命),不触发真死。 * * 出生点规则: - * 按攻击类型(HType)落排:近战 → 前排(x=-200,最靠右迎敌), - * 中距 → 中排(x=-260),远程 → 后排(x=-320,最靠左)。 + * 按职业定位(HRole)落排:坦克/战士 → 前排(x=-200,最靠右迎敌), + * 刺客/射手 → 中排(x=-260),法师/辅助 → 后排(x=-320,最靠左)。 * 同类型落点不再做 ± 偏移展开,落点重叠由 MoveSystem 的编队排队解决 * (准备阶段按职业优先级 坦克→战士→刺客→射手→法师→辅助 从最右向左排开)。 * posIndex 取 heroGrid 全局空闲下标(0~9)用于网格登记/索敌,与落点解耦。 @@ -36,10 +37,10 @@ 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, HType } from "../common/config/heroSet"; +import { HeroInfo, HRole } from "../common/config/heroSet"; import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; -import { FacSet, BoxSet } from "../common/config/GameSet"; +import { FacSet, BoxSet, FightSet } from "../common/config/GameSet"; import { MoveComp } from "../hero/MoveComp"; const { ccclass } = _decorator; @@ -55,27 +56,32 @@ export class MissionHeroComp extends CCComp { // ======================== 常量 ======================== /** - * 三排出生基准点(y 均为 BoxSet.GAME_LINE): - * - 前排 x=-200(最靠右迎敌):战士 / 坦克 - * - 中排 x=-260:射手 / 刺客 - * - 后排 x=-320(最靠左):辅助 / 法师 + * 三排出生基准点(y 均为 BoxSet.GAME_LINE,x 取自 FightSet): + * - 前排 x=FightSet.HERO_ROW_FRONT_X(最靠右迎敌):坦克 / 战士 + * - 中排 x=FightSet.HERO_ROW_MID_X:刺客 / 射手 + * - 后排 x=FightSet.HERO_ROW_BACK_X(最靠左):法师 / 辅助 */ - public static readonly HERO_ROW_FRONT = v3(-200, BoxSet.GAME_LINE, 0) - public static readonly HERO_ROW_MID = v3(-260, BoxSet.GAME_LINE, 0) - public static readonly HERO_ROW_BACK = v3(-320, BoxSet.GAME_LINE, 0) + public static readonly HERO_ROW_FRONT = v3(FightSet.HERO_ROW_FRONT_X, BoxSet.GAME_LINE, 0) + public static readonly HERO_ROW_MID = v3(FightSet.HERO_ROW_MID_X, BoxSet.GAME_LINE, 0) + public static readonly HERO_ROW_BACK = v3(FightSet.HERO_ROW_BACK_X, BoxSet.GAME_LINE, 0) /** - * 攻击类型 → 出生排映射: - * 近战 → 前排,中距 → 中排(优先中间位),远程 → 后排。 + * 职业定位 → 出生排映射: + * 坦克/战士 → 前排,刺客/射手 → 中排,法师/辅助 → 后排。 */ - private static readonly TYPE_ROW: Record = { - [HType.Melee]: MissionHeroComp.HERO_ROW_FRONT, - [HType.Mid]: MissionHeroComp.HERO_ROW_MID, - [HType.Long]: MissionHeroComp.HERO_ROW_BACK, + private static readonly ROLE_ROW: Record = { + [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 HERO_DROP_HEIGHT = 260 + /** 召唤登场间隔(秒):保证"依次上场"节奏,避免同帧堆叠掉落 */ + private static readonly SUMMON_INTERVAL = 0.35 // ======================== 运行时属性 ======================== @@ -89,8 +95,8 @@ export class MissionHeroComp extends CCComp { current_hero_num: number = -1 /** 是否正在消费召唤队列(防止并发) */ is_processing_queue: boolean = false - /** 召唤请求队列:保证召唤按顺序串行执行 */ - summon_queue: { uuid: number; hero_lv: number; card_lv: number }[] = [] + /** 召唤请求队列:按职业优先级排序后串行登场 */ + summon_queue: { uuid: number; hero_lv: number; card_lv: number; role: HRole }[] = [] /** 英雄出生序计数器(编队排队/渲染排序的稳定决胜) */ private heroSpawnOrder = 0 /** 预留英雄列表 */ @@ -155,8 +161,8 @@ export class MissionHeroComp extends CCComp { /** * 召唤请求入口: * 从事件参数中提取 uuid / hero_lv / card_lv,放入串行队列。 - * 二合一:同 uuid 已召唤且等级未达上限时允许入队,由 handleSingleSummon 执行合成; - * 同 uuid 且等级已达上限时拒绝(只能通过升级卡继续提升之外无法再合成)。 + * 队列按职业优先级(坦克→战士→刺客→射手→法师→辅助,HRole 升序)排序, + * 保证高优先级职业先登场。 * * @param event 事件名 * @param args { uuid, hero_lv, card_lv } @@ -169,8 +175,11 @@ export class MissionHeroComp extends CCComp { const outer_lv = smc.getHeroOuterLv(uuid); const hero_lv = Math.max(outer_lv, Math.max(1, Number(payload?.hero_lv ?? 1))); const card_lv = Math.max(1, Number(payload?.card_lv ?? 1)); + const role = HeroInfo[uuid]?.role ?? HRole.Support; - this.summon_queue.push({ uuid, hero_lv, card_lv }); + this.summon_queue.push({ uuid, hero_lv, card_lv, role }); + // 按职业优先级稳定排序:HRole 升序,同职业保持入队先后 + this.summon_queue.sort((a, b) => a.role - b.role); this.processSummonQueue(); } @@ -178,17 +187,17 @@ export class MissionHeroComp extends CCComp { /** * 为英雄分配出生槽位: - * 按攻击类型取三排基准点(近战前排、中距优先中排、远程后排),同类型不再做横向偏移展开; + * 按职业定位取三排基准点(坦克/战士前排、刺客/射手中排、法师/辅助后排),同类型不再做横向偏移展开; * 落点重叠由 MoveSystem 的编队排队(按职业优先级从最右向左排开)负责分开。 * * 槽位编码:posIndex 直接取 heroGrid 的全局空闲下标(0~9),与落点无映射。 * - * @param type 英雄攻击类型(HType,决定所属排) + * @param role 英雄职业定位(HRole,决定所属排) * @param heroes 当前存活英雄列表(spawnHeroAt 在实体创建前快照,避免复用池残留实体干扰网格判定) * @returns { posIndex heroGrid 空闲下标(-1 表示满员未登记), landingPos 落地点 } */ - private pickSpawnSlotForHero(type: HType, heroes: Hero[]): { posIndex: number; landingPos: Vec3 } { - const basePos = MissionHeroComp.TYPE_ROW[type] ?? MissionHeroComp.HERO_ROW_MID; + private pickSpawnSlotForHero(role: HRole, heroes: Hero[]): { posIndex: number; landingPos: Vec3 } { + const basePos = MissionHeroComp.ROLE_ROW[role] ?? MissionHeroComp.HERO_ROW_MID; // 已登记的网格下标集合(仅用于 posIndex 分配) const occupiedGrid = new Set(); @@ -209,7 +218,7 @@ export class MissionHeroComp extends CCComp { /** * 生成一个英雄: - * - 按攻击类型落排基准点(近战前排 / 中距中排 / 远程后排),不做横向偏移。 + * - 按职业定位落排基准点(坦克/战士前排、刺客/射手中排、法师/辅助后排),不做横向偏移。 * - 计算出生点(空中)和落点(地面),播放掉落动画。 * - 落地后由 MoveSystem 按职业优先级从最右向左排队分开。 * @@ -221,13 +230,13 @@ export class MissionHeroComp extends CCComp { private spawnHeroAt(uuid: number, hero_lv: number, card_lv: number) { let hero = ecs.getEntity(Hero); let scale = 1 - const type = HeroInfo[uuid]?.type ?? HType.Mid; + const role = HeroInfo[uuid]?.role ?? HRole.Support; const heroes = this.getAllHeroes().filter(h => { const m = h.get(HeroAttrsComp); return !!m && !m.is_dead; }); - const slot = this.pickSpawnSlotForHero(type, heroes); + const slot = this.pickSpawnSlotForHero(role, heroes); const finalPosIndex = slot.posIndex; const landingPos = slot.landingPos; @@ -271,7 +280,7 @@ export class MissionHeroComp extends CCComp { /** * 串行消费召唤队列: * 使用 is_processing_queue 标志防止同帧多次调用。 - * 逐个取出队列中的请求并处理。 + * 队列已按职业优先级排序,逐个取出并间隔登场,保证"依次上场"的节奏。 */ private async processSummonQueue() { if (this.is_processing_queue) return; @@ -281,6 +290,10 @@ export class MissionHeroComp extends CCComp { const payload = this.summon_queue.shift(); if (!payload) continue; this.spawnHeroAt(payload.uuid, payload.hero_lv, payload.card_lv); + // 登场间隔:让掉落动画逐个播放,避免同帧堆叠 + if (this.summon_queue.length > 0) { + await new Promise(resolve => setTimeout(resolve, MissionHeroComp.SUMMON_INTERVAL * 1000)); + } } } finally { this.is_processing_queue = false;