feat(ui): 添加战场英雄信息面板并增强主角召唤事件
扩展主角召唤事件,传递更多实体信息供UI系统使用。新增HInfoComp组件作为英雄信息面板基础,并在MissionCardComp中动态生成和管理英雄信息面板,实时显示英雄属性。同时调整相关预制体引用和布局配置。
This commit is contained in:
20
assets/script/game/map/HInfoComp.ts
Normal file
20
assets/script/game/map/HInfoComp.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 视图层对象 */
|
||||
@ccclass('HInfoComp')
|
||||
@ecs.register('HInfoComp', false)
|
||||
export class HInfoComp extends CCComp {
|
||||
private debugMode: boolean = true;
|
||||
/** 锁定态图标节点(显示时表示本槽位锁定) */
|
||||
|
||||
|
||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
reset() {
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
9
assets/script/game/map/HInfoComp.ts.meta
Normal file
9
assets/script/game/map/HInfoComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "a832f87d-c91f-4b24-ad64-47eb59d658a7",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Label, Node, NodeEventType, Prefab, SpriteAtlas, Tween, tween, Vec3 } from "cc";
|
||||
import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, SpriteAtlas, Tween, tween, Vec3 } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
@@ -7,6 +7,7 @@ import { CARD_POOL_INIT_LEVEL, CARD_POOL_MAX_LEVEL, CardConfig, CardInitCoins, C
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { CardComp } from "./CardComp";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -38,9 +39,9 @@ export class MissionCardComp extends CCComp {
|
||||
@property(Node)
|
||||
pool_lv_node:Node = null!
|
||||
@property(Node)
|
||||
hero_info_node:Node = null!
|
||||
hero_info_node:Node = null! //场上英雄信息面板所在节点
|
||||
@property(Prefab)
|
||||
hero_info_prefab:Prefab=null!
|
||||
hero_info_prefab:Prefab=null! //场上英雄信息面板Prefab
|
||||
/** 预留图集缓存(后续接入按钮/卡面图标时复用) */
|
||||
private uiconsAtlas: SpriteAtlas | null = null;
|
||||
/** 四个槽位对应的单卡控制器缓存 */
|
||||
@@ -48,6 +49,14 @@ export class MissionCardComp extends CCComp {
|
||||
/** 当前卡池等级(仅影响抽卡来源,不直接改卡槽现有内容) */
|
||||
private poolLv: number = CARD_POOL_INIT_LEVEL;
|
||||
private coin: number = CardInitCoins;
|
||||
private readonly heroInfoItemGap: number = 86;
|
||||
private heroInfoSyncTimer: number = 0;
|
||||
private heroInfoItems: Map<number, {
|
||||
node: Node,
|
||||
model: HeroAttrsComp,
|
||||
apLabel: Label | null,
|
||||
hpLabel: Label | null
|
||||
}> = new Map();
|
||||
onLoad() {
|
||||
/** 绑定事件 -> 缓存子控制器 -> 初始化UI状态 */
|
||||
this.bindEvents();
|
||||
@@ -62,6 +71,7 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
onDestroy() {
|
||||
this.unbindEvents();
|
||||
this.clearHeroInfoPanels();
|
||||
}
|
||||
init(){
|
||||
this.onMissionStart();
|
||||
@@ -71,6 +81,7 @@ export class MissionCardComp extends CCComp {
|
||||
onMissionStart() {
|
||||
this.poolLv = CARD_POOL_INIT_LEVEL;
|
||||
this.coin = CardInitCoins
|
||||
this.clearHeroInfoPanels();
|
||||
this.layoutCardSlots();
|
||||
this.clearAllCards();
|
||||
if (this.cards_up) {
|
||||
@@ -91,12 +102,18 @@ export class MissionCardComp extends CCComp {
|
||||
/** 任务结束时:清空4槽并隐藏面板 */
|
||||
onMissionEnd() {
|
||||
this.clearAllCards();
|
||||
this.clearHeroInfoPanels();
|
||||
this.node.active = false;
|
||||
}
|
||||
start() {
|
||||
|
||||
}
|
||||
|
||||
update(dt: number) {
|
||||
this.heroInfoSyncTimer += dt;
|
||||
if (this.heroInfoSyncTimer < 0.15) return;
|
||||
this.heroInfoSyncTimer = 0;
|
||||
this.refreshHeroInfoPanels();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +128,7 @@ export class MissionCardComp extends CCComp {
|
||||
this.on(GameEvent.MissionStart, this.onMissionStart, this);
|
||||
this.on(GameEvent.MissionEnd, this.onMissionEnd, this);
|
||||
this.on(GameEvent.CoinAdd, this.onCoinAdd, this);
|
||||
oops.message.on(GameEvent.MasterCalled, this.onMasterCalled, this);
|
||||
|
||||
/** 按钮事件:抽卡与卡池升级 */
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
@@ -129,6 +147,7 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/** 解除按钮监听,避免节点销毁后回调泄漏 */
|
||||
private unbindEvents() {
|
||||
oops.message.off(GameEvent.MasterCalled, this.onMasterCalled, this);
|
||||
this.cards_chou?.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_chou?.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_chou?.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
@@ -137,6 +156,14 @@ export class MissionCardComp extends CCComp {
|
||||
this.cards_up?.off(NodeEventType.TOUCH_CANCEL, this.onUpgradeTouchCancel, this);
|
||||
}
|
||||
|
||||
private onMasterCalled(event: string, args: any) {
|
||||
const payload = args ?? event;
|
||||
const eid = Number(payload?.eid ?? 0);
|
||||
const model = payload?.model as HeroAttrsComp | undefined;
|
||||
if (!eid || !model) return;
|
||||
this.ensureHeroInfoPanel(eid, model);
|
||||
}
|
||||
|
||||
private onDrawTouchStart() {
|
||||
this.playButtonPressAnim(this.cards_chou);
|
||||
}
|
||||
@@ -339,8 +366,110 @@ export class MissionCardComp extends CCComp {
|
||||
return CardsUpSet[lv] ?? 0;
|
||||
}
|
||||
|
||||
private ensureHeroInfoPanel(eid: number, model: HeroAttrsComp) {
|
||||
if (!this.hero_info_node || !this.hero_info_prefab) return;
|
||||
this.hero_info_node.active = true;
|
||||
const current = this.heroInfoItems.get(eid);
|
||||
if (current) {
|
||||
current.model = model;
|
||||
this.updateHeroInfoPanel(current);
|
||||
return;
|
||||
}
|
||||
const node = instantiate(this.hero_info_prefab);
|
||||
node.parent = this.hero_info_node;
|
||||
node.active = true;
|
||||
const item = {
|
||||
node,
|
||||
model,
|
||||
apLabel: this.resolvePanelLabel(node, [["ap", "val"]]),
|
||||
hpLabel: this.resolvePanelLabel(node, [["hp", "val"]])
|
||||
};
|
||||
this.heroInfoItems.set(eid, item);
|
||||
this.relayoutHeroInfoPanels();
|
||||
this.updateHeroInfoPanel(item);
|
||||
}
|
||||
|
||||
private refreshHeroInfoPanels() {
|
||||
const removeKeys: number[] = [];
|
||||
this.heroInfoItems.forEach((item, eid) => {
|
||||
if (!item.node || !item.node.isValid) {
|
||||
removeKeys.push(eid);
|
||||
return;
|
||||
}
|
||||
const ent = (item.model as any)?.ent;
|
||||
if (!ent) {
|
||||
if (item.node.isValid) item.node.destroy();
|
||||
removeKeys.push(eid);
|
||||
return;
|
||||
}
|
||||
this.updateHeroInfoPanel(item);
|
||||
});
|
||||
for (let i = 0; i < removeKeys.length; i++) {
|
||||
this.heroInfoItems.delete(removeKeys[i]);
|
||||
}
|
||||
if (removeKeys.length > 0) {
|
||||
this.relayoutHeroInfoPanels();
|
||||
}
|
||||
}
|
||||
|
||||
private updateHeroInfoPanel(item: {
|
||||
node: Node,
|
||||
model: HeroAttrsComp,
|
||||
apLabel: Label | null,
|
||||
hpLabel: Label | null
|
||||
}) {
|
||||
if (item.apLabel) item.apLabel.string = `${Math.max(0, Math.floor(item.model.ap ?? 0))}`;
|
||||
if (item.hpLabel) item.hpLabel.string = `${Math.max(0, Math.floor(item.model.hp_max ?? 0))}`;
|
||||
}
|
||||
|
||||
private relayoutHeroInfoPanels() {
|
||||
let index = 0;
|
||||
this.heroInfoItems.forEach(item => {
|
||||
if (!item.node || !item.node.isValid) return;
|
||||
const pos = item.node.position;
|
||||
item.node.setPosition(pos.x, -index * this.heroInfoItemGap, pos.z);
|
||||
index += 1;
|
||||
});
|
||||
}
|
||||
|
||||
private clearHeroInfoPanels() {
|
||||
this.heroInfoItems.forEach(item => {
|
||||
if (item.node && item.node.isValid) {
|
||||
item.node.destroy();
|
||||
}
|
||||
});
|
||||
this.heroInfoItems.clear();
|
||||
if (this.hero_info_node && this.hero_info_node.isValid) {
|
||||
for (let i = this.hero_info_node.children.length - 1; i >= 0; i--) {
|
||||
const child = this.hero_info_node.children[i];
|
||||
if (child && child.isValid) child.destroy();
|
||||
}
|
||||
}
|
||||
this.heroInfoSyncTimer = 0;
|
||||
}
|
||||
|
||||
private resolvePanelLabel(root: Node, paths: string[][]): Label | null {
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const node = this.findNodeByPath(root, paths[i]);
|
||||
if (!node) continue;
|
||||
const label = node.getComponent(Label) || node.getComponentInChildren(Label);
|
||||
if (label) return label;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private findNodeByPath(root: Node, path: string[]): Node | null {
|
||||
let current: Node | null = root;
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
current = current?.getChildByName(path[i]) ?? null;
|
||||
if (!current) return null;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
reset() {
|
||||
this.clearHeroInfoPanels();
|
||||
this.resetButtonScale(this.cards_chou);
|
||||
this.resetButtonScale(this.cards_up);
|
||||
this.node.destroy();
|
||||
|
||||
Reference in New Issue
Block a user