feat: 重构英雄编队逻辑,优化出生与排队规则
1. 移除英雄出生落点偏移,改为按职业优先级从右向左排队 2. 重构英雄卡点击逻辑,支持点击整张卡牌选取 3. 新增英雄血量背景预制体与UI按钮样式 4. 优化AOE技能目标选取逻辑,使用中排基准点
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
* @description 统一移动组件与系统(英雄 + 怪物)
|
||||
*
|
||||
* 核心规则:
|
||||
* 1. 准备阶段:双方原地待命,不推进不索敌。
|
||||
* 1. 准备阶段:不推进不索敌;英雄按职业优先级(坦克→战士→刺客→射手→法师→辅助)
|
||||
* 从最右迎敌位向左排队移动,防止同类型落点重叠;怪物原地待命。
|
||||
* 2. 战斗阶段:双方向最近敌人推进,进入攻击范围(model.dis)后停止移动并攻击。
|
||||
* 3. 场上无敌人:停止移动(战斗结束,由 MissionComp 进入结算)。
|
||||
* 4. 击退/位移不会把单位推出屏幕:HERO 不越过 BoxSet.LETF_END,MON 不越过 BoxSet.RIGHT_END。
|
||||
@@ -14,6 +15,7 @@ import { HeroViewComp } from "./HeroViewComp";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { BoxSet, FacSet } from "../common/config/GameSet";
|
||||
import { HRole } from "../common/config/heroSet";
|
||||
import { Node } from "cc";
|
||||
|
||||
@ecs.register('MoveComp')
|
||||
@@ -50,6 +52,18 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number; laneScore: number }[] = [];
|
||||
private renderEntryCount = 0;
|
||||
|
||||
// ======================== 英雄编队排队 ========================
|
||||
|
||||
/** 编队锚点:最右迎敌位(与 MissionHeroComp.HERO_ROW_FRONT 的 x 对齐,避免循环依赖在此独立定义) */
|
||||
private static readonly QUEUE_ANCHOR_X = -200;
|
||||
/** 编队槽位默认间隔(像素);人数超出边界宽度时自动压缩间距 */
|
||||
private static readonly QUEUE_SPACING = 30;
|
||||
/** 编队槽位重算节流间隔(秒):召唤/死亡后最多延迟一次重算 */
|
||||
private readonly queueRebuildInterval = 0.1;
|
||||
private lastQueueRebuildAt = 0;
|
||||
/** 编队槽位缓存:按职业优先级排序后各英雄的目标 X */
|
||||
private readonly queueSlots: { eid: number; targetX: number }[] = [];
|
||||
|
||||
private getHeroViewMatcher(): ecs.IMatcher {
|
||||
if (!this.heroViewMatcher) {
|
||||
this.heroViewMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
|
||||
@@ -104,11 +118,15 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
}
|
||||
}
|
||||
|
||||
/** 准备阶段:双方原地待命,不推进不索敌 */
|
||||
/** 准备阶段:不推进不索敌;英雄向编队槽位排队移动,怪物原地待命 */
|
||||
if (!smc.mission.in_fight) {
|
||||
this.clearCombatTarget(model);
|
||||
model.is_atking = false;
|
||||
if (view.status !== "atk") view.status_change("idle");
|
||||
if (model.fac === FacSet.HERO) {
|
||||
this.processHeroQueue(e, model, move, view);
|
||||
} else if (view.status !== "atk") {
|
||||
view.status_change("idle");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,6 +151,90 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
model.enemy_in_cast_range = false;
|
||||
}
|
||||
|
||||
// ======================== 英雄编队排队(准备阶段) ========================
|
||||
|
||||
/**
|
||||
* 英雄编队排队:
|
||||
* 按 坦克→战士→刺客→射手→法师→辅助 的职业优先级(即 HRole 枚举值升序),
|
||||
* 从最右迎敌位(QUEUE_ANCHOR_X)向左依次排开,防止同类型英雄落点重叠。
|
||||
*
|
||||
* @param entity 当前英雄实体
|
||||
* @param model 英雄属性(取 speed / role)
|
||||
* @param move 移动组件(维护 direction)
|
||||
* @param view 视图组件(驱动位移与动画状态)
|
||||
*/
|
||||
private processHeroQueue(entity: ecs.Entity, model: HeroAttrsComp, move: MoveComp, view: HeroViewComp): void {
|
||||
const targetX = this.resolveHeroQueueTargetX(entity);
|
||||
const selfX = view.node.position.x;
|
||||
|
||||
/** 无槽位(未登记)或已到位:贴齐槽位并原地待命,恢复朝右迎敌 */
|
||||
if (targetX === null || Math.abs(targetX - selfX) <= 2) {
|
||||
if (targetX !== null && Math.abs(selfX - targetX) > 0.01) {
|
||||
view.node.setPosition(targetX, view.node.position.y, 0);
|
||||
}
|
||||
view.scale = 1;
|
||||
if (view.status !== "atk") view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
/** 未到位:向槽位推进(与战斗推进同速),朝移动方向转身 */
|
||||
const dir = targetX > selfX ? 1 : -1;
|
||||
move.direction = dir;
|
||||
view.scale = dir;
|
||||
const delta = (model.speed / 3) * this.dt * dir;
|
||||
const newX = this.clampByFacBoundary(selfX + delta, model.fac);
|
||||
if (Math.abs(newX - selfX) >= 0.01) {
|
||||
view.node.setPosition(newX, view.node.position.y, 0);
|
||||
view.status_change("move");
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询当前英雄的编队槽位目标 X(无槽位返回 null) */
|
||||
private resolveHeroQueueTargetX(entity: ecs.Entity): number | null {
|
||||
const now = Date.now() / 1000;
|
||||
if (now - this.lastQueueRebuildAt >= this.queueRebuildInterval) {
|
||||
this.lastQueueRebuildAt = now;
|
||||
this.rebuildHeroQueueSlots();
|
||||
}
|
||||
for (const slot of this.queueSlots) {
|
||||
if (slot.eid === entity.eid) return slot.targetX;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重算编队槽位:
|
||||
* 收集全部存活英雄,按 职业优先级 → 出生序 → eid 稳定排序后,
|
||||
* 从 QUEUE_ANCHOR_X 向左按间隔分配;人数过多时压缩间距,保证不越过 LETF_END。
|
||||
*/
|
||||
private rebuildHeroQueueSlots(): void {
|
||||
const heroes: { eid: number; role: number; spawnOrder: number }[] = [];
|
||||
ecs.query(this.getHeroViewMatcher()).forEach(e => {
|
||||
const m = e.get(HeroAttrsComp);
|
||||
if (!m || m.fac !== FacSet.HERO || m.is_dead) return;
|
||||
const v = e.get(HeroViewComp);
|
||||
if (!v?.node) return;
|
||||
const move = e.get(MoveComp);
|
||||
heroes.push({ eid: e.eid, role: m.role ?? HRole.Support, spawnOrder: move?.spawnOrder ?? 0 });
|
||||
});
|
||||
heroes.sort((a, b) =>
|
||||
(a.role - b.role) ||
|
||||
(a.spawnOrder - b.spawnOrder) ||
|
||||
(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;
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
private syncCombatTarget(model: HeroAttrsComp, selfView: HeroViewComp, enemyView: HeroViewComp): void {
|
||||
if (!enemyView || !enemyView.node || !enemyView.ent) {
|
||||
this.clearCombatTarget(model);
|
||||
|
||||
Reference in New Issue
Block a user