feat(关卡): 添加英雄数量上限机制

- 在 MissionCardComp 中添加英雄数量显示与上限控制逻辑
- 当英雄数量达到上限时禁止使用英雄卡牌
- 英雄死亡时减少当前英雄计数并刷新显示
- 添加英雄数量变化的动画反馈效果
- 移除 SingletonModuleComp 中未使用的 unlockCoin 字段
This commit is contained in:
walkpan
2026-03-25 23:04:12 +08:00
parent 7db182d9fc
commit 8a151a3922
6 changed files with 140 additions and 4 deletions

View File

@@ -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", {

View File

@@ -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<number, {
node: Node,
model: HeroAttrsComp,
@@ -81,6 +87,8 @@ export class MissionCardComp extends CCComp {
onMissionStart() {
this.poolLv = CARD_POOL_INIT_LEVEL;
this.coin = CardInitCoins
this.heroMaxCount = this.heroDefaultMaxCount;
this.heroCurrentCount = 0;
this.clearHeroInfoPanels();
this.layoutCardSlots();
this.clearAllCards();
@@ -91,6 +99,7 @@ export class MissionCardComp extends CCComp {
this.resetButtonScale(this.cards_up);
this.updatePoolLvUI();
this.updateCoinUI();
this.updateHeroNumUI(false, false);
this.node.active = true;
const cards = this.buildDrawCards();
this.dispatchCardsToSlots(cards);
@@ -129,6 +138,8 @@ export class MissionCardComp extends CCComp {
this.on(GameEvent.MissionEnd, this.onMissionEnd, this);
this.on(GameEvent.CoinAdd, this.onCoinAdd, this);
oops.message.on(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.on(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.on(GameEvent.UseHeroCard, this.onUseHeroCard, this);
/** 按钮事件:抽卡与卡池升级 */
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
@@ -148,6 +159,8 @@ export class MissionCardComp extends CCComp {
/** 解除按钮监听,避免节点销毁后回调泄漏 */
private unbindEvents() {
oops.message.off(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.off(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.off(GameEvent.UseHeroCard, this.onUseHeroCard, 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);
@@ -161,7 +174,27 @@ export class MissionCardComp extends CCComp {
const eid = Number(payload?.eid ?? 0);
const model = payload?.model as HeroAttrsComp | undefined;
if (!eid || !model) return;
const before = this.heroCurrentCount;
this.ensureHeroInfoPanel(eid, model);
this.updateHeroNumUI(true, this.heroCurrentCount > 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) 删除组件是触发组件处理自定义释放逻辑 */

View File

@@ -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 {