diff --git a/assets/resources/gui/element/hnode.prefab b/assets/resources/gui/element/hnode.prefab index 5b8f8dd3..2a745241 100644 --- a/assets/resources/gui/element/hnode.prefab +++ b/assets/resources/gui/element/hnode.prefab @@ -35,10 +35,13 @@ "_components": [ { "__id__": 74 + }, + { + "__id__": 76 } ], "_prefab": { - "__id__": 76 + "__id__": 78 }, "_lpos": { "__type__": "cc.Vec3", @@ -1733,6 +1736,24 @@ "__type__": "cc.CompPrefabInfo", "fileId": "9c8CWaboJEQ4tagNhw+LQr" }, + { + "__type__": "a832fh9yR9LJK1kR+tZ1lin", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 1 + }, + "_enabled": true, + "__prefab": { + "__id__": 77 + }, + "_id": "" + }, + { + "__type__": "cc.CompPrefabInfo", + "fileId": "11S1XoDG5Ndo+S7MbRH4us" + }, { "__type__": "cc.PrefabInfo", "root": { diff --git a/assets/resources/gui/role_controller.prefab b/assets/resources/gui/role_controller.prefab index 64783045..27966477 100644 --- a/assets/resources/gui/role_controller.prefab +++ b/assets/resources/gui/role_controller.prefab @@ -778,7 +778,7 @@ }, { "__type__": "cc.PrefabInstance", - "fileId": "8el1xtaIhFn4eJMCp78t65", + "fileId": "b1fQvsJE5Axp3W57Iu81HO", "prefabRootNode": { "__id__": 1 }, @@ -890,7 +890,7 @@ }, { "__type__": "cc.PrefabInstance", - "fileId": "f2Vo21jDdPfbsPEDATg/6w", + "fileId": "24ho6yMw5OyKqGnnlb1lZe", "prefabRootNode": { "__id__": 1 }, @@ -920,7 +920,7 @@ "propertyPath": [ "_name" ], - "value": "hnode-001" + "value": "hnode" }, { "__type__": "cc.TargetInfo", @@ -1002,7 +1002,7 @@ }, { "__type__": "cc.PrefabInstance", - "fileId": "95i/033cdI1ar3RE/LeAFh", + "fileId": "bf9CO3VXpEapXp0MM4jeGj", "prefabRootNode": { "__id__": 1 }, @@ -1032,7 +1032,7 @@ "propertyPath": [ "_name" ], - "value": "hnode-002" + "value": "hnode" }, { "__type__": "cc.TargetInfo", @@ -1114,7 +1114,7 @@ }, { "__type__": "cc.PrefabInstance", - "fileId": "96aOqwbNZC2554Tb3MVHAi", + "fileId": "cbhqcW/7BHOIfs5h+tuWvE", "prefabRootNode": { "__id__": 1 }, @@ -1144,7 +1144,7 @@ "propertyPath": [ "_name" ], - "value": "hnode-003" + "value": "hnode" }, { "__type__": "cc.TargetInfo", @@ -1226,7 +1226,7 @@ }, { "__type__": "cc.PrefabInstance", - "fileId": "82lTFZokVJ1YXiKvnc7dBe", + "fileId": "f9xptjk/hA+IJ5/9k6XxMX", "prefabRootNode": { "__id__": 1 }, @@ -1256,7 +1256,7 @@ "propertyPath": [ "_name" ], - "value": "hnode-004" + "value": "hnode" }, { "__type__": "cc.TargetInfo", @@ -1338,7 +1338,7 @@ }, { "__type__": "cc.PrefabInstance", - "fileId": "27cEJ94ONNHKT7mJlaxtlG", + "fileId": "b9NtsF98ZKrpqsdLUl8ouf", "prefabRootNode": { "__id__": 1 }, @@ -1368,7 +1368,7 @@ "propertyPath": [ "_name" ], - "value": "hnode-005" + "value": "hnode" }, { "__type__": "cc.TargetInfo", @@ -1517,7 +1517,7 @@ "_constraint": 0, "_constraintNum": 2, "_affectedByScale": false, - "_isAlign": false, + "_isAlign": true, "_id": "" }, { @@ -11461,7 +11461,10 @@ "hero_info_node": { "__id__": 24 }, - "hero_info_prefab": null, + "hero_info_prefab": { + "__uuid__": "46f1e2cb-6fa7-4e9e-b419-e424ba47fe68", + "__expectedType__": "cc.Prefab" + }, "_id": "" }, { @@ -11833,7 +11836,7 @@ "node": { "__id__": 560 }, - "_enabled": true, + "_enabled": false, "__prefab": { "__id__": 572 }, @@ -11847,10 +11850,7 @@ "b": 255, "a": 255 }, - "_spriteFrame": { - "__uuid__": "deedea09-8f2b-400f-9803-4cfd38e45d1a@58cb7", - "__expectedType__": "cc.SpriteFrame" - }, + "_spriteFrame": null, "_type": 1, "_fillType": 0, "_sizeMode": 0, @@ -11863,10 +11863,7 @@ "_fillRange": 0, "_isTrimmedMode": true, "_useGrayscale": false, - "_atlas": { - "__uuid__": "deedea09-8f2b-400f-9803-4cfd38e45d1a", - "__expectedType__": "cc.SpriteAtlas" - }, + "_atlas": null, "_id": "" }, { diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 51a0ad74..142c681d 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -113,7 +113,12 @@ export class Hero extends ecs.Entity { this.add(hv); hv.init(); // 广播主角召唤事件,触发外部系统监听逻辑 - oops.message.dispatchEvent(GameEvent.MasterCalled,{uuid:uuid}) + oops.message.dispatchEvent(GameEvent.MasterCalled, { + eid: this.eid, + uuid, + hero_lv, + model + }) // 初始化移动组件:方向、目标 X、站位基准 Y const move = this.get(MoveComp); diff --git a/assets/script/game/map/HInfoComp.ts b/assets/script/game/map/HInfoComp.ts new file mode 100644 index 00000000..2a787830 --- /dev/null +++ b/assets/script/game/map/HInfoComp.ts @@ -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(); + } +} diff --git a/assets/script/game/map/HInfoComp.ts.meta b/assets/script/game/map/HInfoComp.ts.meta new file mode 100644 index 00000000..93c998a0 --- /dev/null +++ b/assets/script/game/map/HInfoComp.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "a832f87d-c91f-4b24-ad64-47eb59d658a7", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index c41c482c..1ba362b8 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -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 = 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();