From 8a151a39227bc1fd80fb7fd497ddd2c80e0ff5de Mon Sep 17 00:00:00 2001 From: walkpan Date: Wed, 25 Mar 2026 23:04:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=85=B3=E5=8D=A1):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8B=B1=E9=9B=84=E6=95=B0=E9=87=8F=E4=B8=8A=E9=99=90=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 MissionCardComp 中添加英雄数量显示与上限控制逻辑 - 当英雄数量达到上限时禁止使用英雄卡牌 - 英雄死亡时减少当前英雄计数并刷新显示 - 添加英雄数量变化的动画反馈效果 - 移除 SingletonModuleComp 中未使用的 unlockCoin 字段 --- assets/resources/gui/role_controller.prefab | 3 + .../script/game/common/SingletonModuleComp.ts | 1 - assets/script/game/hero/Hero.ts | 10 ++ assets/script/game/map/CardComp.ts | 15 +++ assets/script/game/map/MissionCardComp.ts | 112 +++++++++++++++++- assets/script/game/map/MissionHeroComp.ts | 3 +- 6 files changed, 140 insertions(+), 4 deletions(-) diff --git a/assets/resources/gui/role_controller.prefab b/assets/resources/gui/role_controller.prefab index 72f3cc41..a87f2e5e 100644 --- a/assets/resources/gui/role_controller.prefab +++ b/assets/resources/gui/role_controller.prefab @@ -10890,6 +10890,9 @@ "__uuid__": "46f1e2cb-6fa7-4e9e-b419-e424ba47fe68", "__expectedType__": "cc.Prefab" }, + "hero_num_node": { + "__id__": 144 + }, "_id": "" }, { diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index 015a081c..71102d14 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -69,7 +69,6 @@ export class SingletonModuleComp extends ecs.Comp { max_mission:4,//最大关卡 coin:0, time:15*60,//游戏时间 - unlockCoin: 20, }, scores: { score: 0, // 基础得分 diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 142c681d..54708741 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -196,6 +196,16 @@ export class BattleEntityLifecycleSystem extends ecs.ComblockSystem const label = this.resolveLabel(heroAttrs); if (heroAttrs) { mLogger.log(heroAttrs.debugMode, 'BattleEntityLifecycle', `${label}离开世界: ${heroAttrs.hero_name}`); + if (heroAttrs.fac === FacSet.HERO) { + const missionData = smc.vmdata?.mission_data; + if (missionData) { + missionData.hero_num = Math.max(0, (missionData.hero_num ?? 0) - 1); + } + oops.message.dispatchEvent(GameEvent.HeroDead, { + eid: e.eid, + model: heroAttrs + }); + } } else { mLogger.log(true, 'BattleEntityLifecycle', `${label}离开世界: 实体ID ${e.eid}`); } diff --git a/assets/script/game/map/CardComp.ts b/assets/script/game/map/CardComp.ts index 61d72746..3702e455 100644 --- a/assets/script/game/map/CardComp.ts +++ b/assets/script/game/map/CardComp.ts @@ -6,6 +6,8 @@ import { CardConfig, CardType, SpecialCardList } from "../common/config/CardSet" import { CardUseComp } from "./CardUseComp"; import { HeroInfo } from "../common/config/heroSet"; import { SkillSet } from "../common/config/SkillSet"; +import { GameEvent } from "../common/config/GameEvent"; +import { oops } from "db://oops-framework/core/Oops"; @@ -159,6 +161,19 @@ export class CardComp extends CCComp { useCard(): CardConfig | null { if (!this.cardData || this.isUsing) return null; + if (this.cardData.type === CardType.Hero) { + const guard = { + cancel: false, + reason: "", + uuid: this.cardData.uuid, + hero_lv: this.cardData.hero_lv + }; + oops.message.dispatchEvent(GameEvent.UseHeroCard, guard); + if (guard.cancel) { + this.playReboundAnim(); + return null; + } + } this.isUsing = true; const used = this.cardData; mLogger.log(this.debugMode, "CardComp", "use card", { diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index a0e3b165..c13b973b 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -4,7 +4,6 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { GameEvent } from "../common/config/GameEvent"; import { CARD_POOL_INIT_LEVEL, CARD_POOL_MAX_LEVEL, CardConfig, CardInitCoins, CardsUpSet, getCardsByLv } from "../common/config/CardSet"; -import { smc } from "../common/SingletonModuleComp"; import { CardComp } from "./CardComp"; import { oops } from "db://oops-framework/core/Oops"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; @@ -43,6 +42,8 @@ export class MissionCardComp extends CCComp { hero_info_node:Node = null! //场上英雄信息面板所在节点 @property(Prefab) hero_info_prefab:Prefab=null! //场上英雄信息面板Prefab + @property(Node) + hero_num_node:Node=null! /** 预留图集缓存(后续接入按钮/卡面图标时复用) */ private uiconsAtlas: SpriteAtlas | null = null; /** 四个槽位对应的单卡控制器缓存 */ @@ -52,6 +53,11 @@ export class MissionCardComp extends CCComp { private coin: number = CardInitCoins; private readonly heroInfoItemGap: number = 86; private heroInfoSyncTimer: number = 0; + private readonly heroDefaultMaxCount: number = 5; + private readonly heroExtendMaxCount: number = 6; + private heroMaxCount: number = this.heroDefaultMaxCount; + private heroCurrentCount: number = 0; + private heroNumLabel: Label | null = null; private heroInfoItems: Map before); + } + + private onHeroDead() { + this.refreshHeroInfoPanels(); + this.updateHeroNumUI(true, false); + } + + private onUseHeroCard(event: string, args: any) { + const payload = args ?? event; + if (!payload) return; + const current = this.getAliveHeroCount(); + this.heroCurrentCount = current; + if (current >= this.heroMaxCount) { + payload.cancel = true; + payload.reason = "hero_limit"; + oops.gui.toast(`英雄已满 (${current}/${this.heroMaxCount})`); + this.playHeroNumDeniedAnim(); + } } private onDrawTouchStart() { @@ -402,6 +435,11 @@ export class MissionCardComp extends CCComp { removeKeys.push(eid); return; } + if (item.model?.is_dead) { + if (item.node.isValid) item.node.destroy(); + removeKeys.push(eid); + return; + } if (!item.comp.isModelAlive()) { if (item.node.isValid) item.node.destroy(); removeKeys.push(eid); @@ -415,6 +453,7 @@ export class MissionCardComp extends CCComp { if (removeKeys.length > 0) { this.relayoutHeroInfoPanels(); } + this.updateHeroNumUI(false, false); } private updateHeroInfoPanel(item: { @@ -449,6 +488,77 @@ export class MissionCardComp extends CCComp { } } this.heroInfoSyncTimer = 0; + this.heroCurrentCount = 0; + this.updateHeroNumUI(false, false); + } + + public setHeroMaxCount(max: number) { + const next = Math.max(this.heroDefaultMaxCount, Math.min(this.heroExtendMaxCount, Math.floor(max || this.heroDefaultMaxCount))); + if (next === this.heroMaxCount) return; + this.heroMaxCount = next; + this.updateHeroNumUI(true, false); + } + + public tryExpandHeroMax(add: number = 1): boolean { + const next = this.heroMaxCount + Math.max(0, Math.floor(add)); + const before = this.heroMaxCount; + this.setHeroMaxCount(next); + return this.heroMaxCount > before; + } + + public canUseHeroCard(): boolean { + return this.getAliveHeroCount() < this.heroMaxCount; + } + + private getAliveHeroCount(): number { + let count = 0; + this.heroInfoItems.forEach(item => { + if (!item?.node || !item.node.isValid) return; + if (!item.comp?.isModelAlive()) return; + if (item.model?.is_dead) return; + count += 1; + }); + return count; + } + + private updateHeroNumUI(animate: boolean, isIncrease: boolean) { + this.heroCurrentCount = this.getAliveHeroCount(); + if (!this.hero_num_node || !this.hero_num_node.isValid) return; + const numNode = this.hero_num_node.getChildByName("num"); + if (!this.heroNumLabel) { + this.heroNumLabel = numNode?.getComponent(Label) || numNode?.getComponentInChildren(Label) || null; + } + if (this.heroNumLabel) { + this.heroNumLabel.string = `${this.heroCurrentCount}/${this.heroMaxCount}`; + } + if (!animate || !isIncrease) return; + this.playHeroNumGainAnim(); + } + + private playHeroNumGainAnim() { + if (!this.hero_num_node || !this.hero_num_node.isValid) return; + const iconNode = this.hero_num_node.getChildByName("icon"); + const numNode = this.hero_num_node.getChildByName("num"); + this.playHeroNumNodePop(iconNode, 1.16); + this.playHeroNumNodePop(numNode, 1.16); + } + + private playHeroNumDeniedAnim() { + if (!this.hero_num_node || !this.hero_num_node.isValid) return; + const iconNode = this.hero_num_node.getChildByName("icon"); + const numNode = this.hero_num_node.getChildByName("num"); + this.playHeroNumNodePop(iconNode, 1.1); + this.playHeroNumNodePop(numNode, 1.1); + } + + private playHeroNumNodePop(node: Node | null, scalePeak: number) { + if (!node || !node.isValid) return; + Tween.stopAllByTarget(node); + node.setScale(1, 1, 1); + tween(node) + .to(0.08, { scale: new Vec3(scalePeak, scalePeak, 1) }) + .to(0.1, { scale: new Vec3(1, 1, 1) }) + .start(); } /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */ diff --git a/assets/script/game/map/MissionHeroComp.ts b/assets/script/game/map/MissionHeroComp.ts index d98203f7..ff81007c 100644 --- a/assets/script/game/map/MissionHeroComp.ts +++ b/assets/script/game/map/MissionHeroComp.ts @@ -1,4 +1,4 @@ -import { _decorator, resources, Sprite, SpriteAtlas, SpriteFrame, v3, Vec3 } from "cc"; +import { _decorator, Node, resources, Sprite, SpriteAtlas, SpriteFrame, v3, 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 { Hero } from "../hero/Hero"; @@ -41,7 +41,6 @@ export class MissionHeroCompComp extends CCComp { } fight_ready(){ smc.vmdata.mission_data.hero_num=0 - } // protected update(dt: number): void {