diff --git a/assets/resources/gui/role_controller.prefab b/assets/resources/gui/role_controller.prefab index f3f64a91..c3667501 100644 --- a/assets/resources/gui/role_controller.prefab +++ b/assets/resources/gui/role_controller.prefab @@ -7076,6 +7076,8 @@ "__id__": 0 }, "fileId": "5aMCdIWc5OmJF+7Y1vMDAV", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -7270,6 +7272,8 @@ "__id__": 0 }, "fileId": "57ozFIFb9ETJnSg6jZ4keY", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -7407,6 +7411,8 @@ "__id__": 0 }, "fileId": "80R6KCqF1MUotJRCKCOAB1", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -7564,6 +7570,8 @@ "__id__": 0 }, "fileId": "4b2ngPxLNPTLyKGNy8mqMw", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -7645,6 +7653,8 @@ "__id__": 0 }, "fileId": "658QGyYfxEyJvkxOrsGTX4", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -7802,6 +7812,8 @@ "__id__": 0 }, "fileId": "8eSy9TOKJMi4sjqRA6RoQk", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -7841,6 +7853,8 @@ "__id__": 0 }, "fileId": "63FHyGP9BOaqAuH4RtPSZ0", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -7954,6 +7968,8 @@ "__id__": 0 }, "fileId": "46VgrHm5VNH5UNmq7bgAi+", + "instance": null, + "targetOverrides": null, "nestedPrefabInstanceRoots": null }, { @@ -8817,7 +8833,9 @@ "cards_chou": { "__id__": 288 }, - "cards_up": null, + "cards_up": { + "__id__": 237 + }, "_id": "" }, { diff --git a/assets/script/game/map/CardComp.ts b/assets/script/game/map/CardComp.ts index 9affd11f..d9fcb168 100644 --- a/assets/script/game/map/CardComp.ts +++ b/assets/script/game/map/CardComp.ts @@ -1,28 +1,19 @@ import { mLogger } from "../common/Logger"; -import { _decorator, Label, Node, tween, Vec3, Color, Sprite, Tween, SpriteAtlas, resources } from "cc"; +import { _decorator, Label, Node, NodeEventType, Sprite, SpriteAtlas, 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"; -import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; -import { GameEvent } from "../common/config/GameEvent"; -import { smc } from "../common/SingletonModuleComp"; -import { HeroAttrsComp } from "../hero/HeroAttrsComp"; -import { CardType } from "../common/config/CardSet"; +import { CardConfig, CardType } from "../common/config/CardSet"; const { ccclass, property } = _decorator; -interface ICardEvent { - type?: CardType; - level?: number; -} - /** 视图层对象 */ @ccclass('CardComp') @ecs.register('CardComp', false) export class CardComp extends CCComp { private debugMode: boolean = true; - /** 视图层逻辑代码分离演示 */ + /** 锁定态图标节点(显示时表示本槽位锁定) */ @property(Node) Lock: Node = null! @property(Node) @@ -41,18 +32,23 @@ export class CardComp extends CCComp { card_cost:number=0 card_type:CardType=CardType.Hero card_uuid:number=0 - // 是否处于锁定状态 - private isLocked: boolean = true; - // 图标图集缓存 + /** 是否处于锁定状态(锁定且有卡时,抽卡分发会被跳过) */ + private isLocked: boolean = false; + /** 图标图集缓存(后续接图标资源时直接复用) */ private uiconsAtlas: SpriteAtlas | null = null; + /** 当前槽位承载的卡牌数据,null 表示空槽 */ + private cardData: CardConfig | null = null; onLoad() { - + /** 初始阶段只做UI状态准备,不触发业务逻辑 */ + this.bindEvents(); + this.updateLockUI(); + this.applyEmptyUI(); } onDestroy() { - + this.unbindEvents(); } init(){ this.onMissionStart(); @@ -68,24 +64,27 @@ export class CardComp extends CCComp { } start() { - // 初始隐藏或显示逻辑 - this.node.active = false; + /** 单卡节点常驻,由数据控制显示内容 */ + this.node.active = true; } - updateCardInfo(card:Node, data: any){ - + /** 兼容旧接口:外部通过该入口更新卡牌 */ + updateCardInfo(card:Node, data: CardConfig){ + this.applyDrawCard(data); } private updateIcon(node: Node, iconId: string) { } - updateCardData(index: number, data: any) { - + /** 兼容旧接口:按索引更新卡牌(当前由 MissionCardComp 顺序分发) */ + updateCardData(index: number, data: CardConfig) { + this.applyDrawCard(data); } + /** 兼容按钮回调入口:触发单卡使用 */ selectCard(e: any, index: string) { - + this.useCard(); } @@ -96,6 +95,132 @@ export class CardComp extends CCComp { } + /** 抽卡分发入口:返回 true 表示本次已成功接收新卡 */ + applyDrawCard(data: CardConfig | null): boolean { + if (!data) return false; + /** 锁定且已有旧卡时,跳过本次刷新,保持老卡 */ + if (this.isLocked && this.cardData) { + mLogger.log(this.debugMode, "CardComp", "slot locked, skip update", this.card_uuid); + return false; + } + this.cardData = data; + this.card_uuid = data.uuid; + this.card_type = data.type; + this.card_cost = data.cost; + this.node.active = true; + this.applyCardUI(); + return true; + } + + /** 使用当前卡牌:仅做UI层清空,不触发效果事件(下一步再接) */ + useCard(): CardConfig | null { + if (!this.cardData) return null; + const used = this.cardData; + this.clearAfterUse(); + return used; + } + + /** 查询槽位是否有卡 */ + hasCard(): boolean { + return !!this.cardData; + } + + /** 外部设置锁定态 */ + setLocked(value: boolean) { + this.isLocked = value; + this.updateLockUI(); + } + + /** 外部读取当前锁定态 */ + isSlotLocked(): boolean { + return this.isLocked; + } + + /** 系统清槽:用于任务开始/结束等强制重置场景 */ + clearBySystem() { + this.cardData = null; + this.card_uuid = 0; + this.card_cost = 0; + this.card_type = CardType.Hero; + this.isLocked = false; + this.updateLockUI(); + this.applyEmptyUI(); + } + + /** 卡牌被玩家使用后的清槽行为 */ + private clearAfterUse() { + this.cardData = null; + this.card_uuid = 0; + this.card_cost = 0; + this.card_type = CardType.Hero; + this.isLocked = false; + this.updateLockUI(); + this.applyEmptyUI(); + } + + /** 绑定触控:卡面点击使用,锁按钮点击切换锁定 */ + private bindEvents() { + this.node.on(NodeEventType.TOUCH_END, this.onCardTouchEnd, this); + this.Lock?.on(NodeEventType.TOUCH_END, this.onToggleLock, this); + this.unLock?.on(NodeEventType.TOUCH_END, this.onToggleLock, this); + } + + /** 解绑触控,防止节点销毁后残留回调 */ + private unbindEvents() { + this.node.off(NodeEventType.TOUCH_END, this.onCardTouchEnd, this); + this.Lock?.off(NodeEventType.TOUCH_END, this.onToggleLock, this); + this.unLock?.off(NodeEventType.TOUCH_END, this.onToggleLock, this); + } + + /** 点击卡面:执行单卡使用(仅UI变化) */ + private onCardTouchEnd() { + this.useCard(); + } + + /** 点击锁控件:切换锁态;空槽不允许锁定 */ + private onToggleLock(event?: Event) { + if (!this.cardData) return; + this.isLocked = !this.isLocked; + this.updateLockUI(); + event?.stopPropagation(); + } + + /** 根据锁态刷新 Lock / unLock 显示 */ + private updateLockUI() { + if (this.Lock) this.Lock.active = this.isLocked; + if (this.unLock) this.unLock.active = !this.isLocked; + } + + /** 根据当前 cardData 渲染卡面文字与图标 */ + private applyCardUI() { + if (!this.cardData) { + this.applyEmptyUI(); + return; + } + this.setLabel(this.name_node, `${CardType[this.card_type]}-${this.card_uuid}`); + this.setLabel(this.cost_node, `${this.card_cost}`); + if (this.ap_node) this.ap_node.active = false; + if (this.hp_node) this.hp_node.active = false; + this.updateIcon(this.icon_node, `${this.card_uuid}`); + } + + /** 渲染空槽状态 */ + private applyEmptyUI() { + this.setLabel(this.name_node, ""); + this.setLabel(this.cost_node, ""); + if (this.ap_node) this.ap_node.active = false; + if (this.hp_node) this.hp_node.active = false; + const sprite = this.icon_node?.getComponent(Sprite); + if (sprite) sprite.spriteFrame = null; + } + + /** 安全设置文本,兼容节点上或子节点上的 Label */ + private setLabel(node: Node | null, value: string) { + if (!node) return; + const label = node.getComponent(Label) || node.getComponentInChildren(Label); + if (label) label.string = value; + } + /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { this.node.destroy(); diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index c3667c28..dc9034c9 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -1,14 +1,10 @@ import { mLogger } from "../common/Logger"; -import { _decorator, Label, Node, tween, Vec3, Color, Sprite, Tween, SpriteAtlas, resources } from "cc"; +import { _decorator, Label, Node, NodeEventType, SpriteAtlas } 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 { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { GameEvent } from "../common/config/GameEvent"; -import { smc } from "../common/SingletonModuleComp"; -import { HeroAttrsComp } from "../hero/HeroAttrsComp"; -import { CardType } from "../common/config/CardSet"; - - +import { CARD_POOL_INIT_LEVEL, CARD_POOL_MAX_LEVEL, CardConfig, getCardsByLv } from "../common/config/CardSet"; +import { CardComp } from "./CardComp"; const { ccclass, property } = _decorator; @@ -18,7 +14,7 @@ const { ccclass, property } = _decorator; @ecs.register('MissionCard', false) export class MissionCardComp extends CCComp { private debugMode: boolean = true; - /** 视图层逻辑代码分离演示 */ + /** 四个插卡槽位(固定顺序分发:1~4) */ @property(Node) card1:Node = null! @property(Node) @@ -32,27 +28,39 @@ export class MissionCardComp extends CCComp { @property(Node) cards_up:Node = null! + /** 预留图集缓存(后续接入按钮/卡面图标时复用) */ private uiconsAtlas: SpriteAtlas | null = null; + /** 四个槽位对应的单卡控制器缓存 */ + private cardComps: CardComp[] = []; + /** 当前卡池等级(仅影响抽卡来源,不直接改卡槽现有内容) */ + private poolLv: number = CARD_POOL_INIT_LEVEL; onLoad() { - + /** 绑定事件 -> 缓存子控制器 -> 初始化UI状态 */ + this.bindEvents(); + this.cacheCardComps(); + this.onMissionStart(); } onDestroy() { - + this.unbindEvents(); } init(){ this.onMissionStart(); } - /** 游戏开始初始化 */ + /** 任务开始时:重置卡池等级、清空4槽、显示面板 */ onMissionStart() { - + this.poolLv = CARD_POOL_INIT_LEVEL; + this.clearAllCards(); + this.updatePoolLvUI(); + this.node.active = true; } - /** 游戏结束清理 */ + /** 任务结束时:清空4槽并隐藏面板 */ onMissionEnd() { - + this.clearAllCards(); + this.node.active = false; } start() { @@ -61,11 +69,82 @@ export class MissionCardComp extends CCComp { - /** - * 关闭界面 - */ + /** 关闭面板(不销毁数据模型,仅隐藏) */ close() { - + this.node.active = false; + } + + /** 只处理UI层事件,不做卡牌效果分发 */ + private bindEvents() { + /** 生命周期事件 */ + this.on(GameEvent.MissionStart, this.onMissionStart, this); + this.on(GameEvent.MissionEnd, this.onMissionEnd, this); + /** 按钮事件:抽卡与卡池升级 */ + this.cards_chou?.on(NodeEventType.TOUCH_END, this.onClickDraw, this); + this.cards_up?.on(NodeEventType.TOUCH_END, this.onClickUpgrade, this); + } + + /** 解除按钮监听,避免节点销毁后回调泄漏 */ + private unbindEvents() { + this.cards_chou?.off(NodeEventType.TOUCH_END, this.onClickDraw, this); + this.cards_up?.off(NodeEventType.TOUCH_END, this.onClickUpgrade, this); + } + + /** 将四个卡槽节点映射为 CardComp,形成固定顺序控制数组 */ + private cacheCardComps() { + const nodes = [this.card1, this.card2, this.card3, this.card4]; + this.cardComps = nodes + .map(node => node?.getComponent(CardComp)) + .filter((comp): comp is CardComp => !!comp); + } + + /** 抽卡按钮:每次固定抽4张,然后顺序分发给4个单卡脚本 */ + private onClickDraw() { + const cards = this.buildDrawCards(); + this.dispatchCardsToSlots(cards); + } + + /** 升级按钮:仅提升卡池等级,卡槽是否更新由下一次抽卡触发 */ + private onClickUpgrade() { + if (this.poolLv >= CARD_POOL_MAX_LEVEL) return; + this.poolLv += 1; + this.updatePoolLvUI(); + mLogger.log(this.debugMode, "MissionCardComp", "pool level up", this.poolLv); + } + + /** 构建本次抽卡结果,保证最终可分发4条数据 */ + private buildDrawCards(): CardConfig[] { + const cards = getCardsByLv(this.poolLv); + /** 正常情况下直接取前4 */ + if (cards.length >= 4) return cards.slice(0, 4); + /** 兜底:当返回不足4张时循环补齐,保证分发不缺位 */ + const filled = [...cards]; + while (filled.length < 4) { + const fallback = getCardsByLv(this.poolLv); + if (fallback.length === 0) break; + filled.push(fallback[filled.length % fallback.length]); + } + return filled; + } + + /** 全量分发给4槽;每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */ + private dispatchCardsToSlots(cards: CardConfig[]) { + for (let i = 0; i < this.cardComps.length; i++) { + this.cardComps[i].applyDrawCard(cards[i] ?? null); + } + } + + /** 系统清空4槽(用于任务切换) */ + private clearAllCards() { + this.cardComps.forEach(comp => comp.clearBySystem()); + } + + /** 更新升级按钮上的等级文案,反馈当前卡池层级 */ + private updatePoolLvUI() { + if (!this.cards_up) return; + const label = this.cards_up.getComponentInChildren(Label); + if (!label) return; + label.string = `卡池Lv.${this.poolLv}`; } /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */