feat(卡牌系统): 实现任务卡牌抽卡与锁定功能
- 新增 MissionCardComp 作为卡牌面板控制器,管理四个固定卡槽 - 实现抽卡按钮逻辑,根据卡池等级抽取并分发卡牌到四个槽位 - 实现卡池升级按钮,提升抽卡品质但不影响已锁定卡牌 - 新增 CardComp 作为单卡控制器,支持卡牌使用与槽位锁定功能 - 锁定状态下卡槽将跳过抽卡更新,保持原有卡牌 - 添加任务开始/结束时的卡槽清理与界面显隐控制 - 修复预制体字段缺失问题,补充 instance 和 targetOverrides 字段
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
|
||||
Reference in New Issue
Block a user