import { mLogger } from "../common/Logger"; import { _decorator, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, 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 { CardConfig, CardType } from "../common/config/CardSet"; const { ccclass, property } = _decorator; /** 视图层对象 */ @ccclass('CardComp') @ecs.register('CardComp', false) export class CardComp extends CCComp { private debugMode: boolean = true; /** 锁定态图标节点(显示时表示本槽位锁定) */ @property(Node) Lock: Node = null! @property(Node) unLock: Node = null! @property(Node) ap_node=null! @property(Node) hp_node=null! @property(Node) name_node=null! @property(Node) icon_node=null! @property(Node) cost_node=null! card_cost:number=0 card_type:CardType=CardType.Hero card_uuid:number=0 /** 是否处于锁定状态(锁定且有卡时,抽卡分发会被跳过) */ private isLocked: boolean = false; /** 图标图集缓存(后续接图标资源时直接复用) */ private uiconsAtlas: SpriteAtlas | null = null; /** 当前槽位承载的卡牌数据,null 表示空槽 */ private cardData: CardConfig | null = null; private readonly dragUseThreshold: number = 70; private touchStartY: number = 0; private isDragging: boolean = false; private isUsing: boolean = false; private restPosition: Vec3 = new Vec3(); private opacityComp: UIOpacity | null = null; onLoad() { /** 初始阶段只做UI状态准备,不触发业务逻辑 */ this.bindEvents(); this.restPosition = this.node.position.clone(); this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity); this.opacityComp.opacity = 255; this.updateLockUI(); this.applyEmptyUI(); } onDestroy() { this.unbindEvents(); } init(){ this.onMissionStart(); } /** 游戏开始初始化 */ onMissionStart() { } /** 游戏结束清理 */ onMissionEnd() { } start() { /** 单卡节点常驻,由数据控制显示内容 */ this.node.active = true; } /** 兼容旧接口:外部通过该入口更新卡牌 */ updateCardInfo(card:Node, data: CardConfig){ this.applyDrawCard(data); } private updateIcon(node: Node, iconId: string) { } /** 兼容旧接口:按索引更新卡牌(当前由 MissionCardComp 顺序分发) */ updateCardData(index: number, data: CardConfig) { this.applyDrawCard(data); } /** 兼容按钮回调入口:触发单卡使用 */ selectCard(e: any, index: string) { this.useCard(); } /** * 关闭界面 */ close() { } /** 抽卡分发入口:返回 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(); this.playRefreshAnim(); mLogger.log(this.debugMode, "CardComp", "card updated", { uuid: this.card_uuid, type: this.card_type, cost: this.card_cost }); return true; } /** 使用当前卡牌:仅做UI层清空,不触发效果事件(下一步再接) */ useCard(): CardConfig | null { if (!this.cardData || this.isUsing) return null; this.isUsing = true; const used = this.cardData; mLogger.log(this.debugMode, "CardComp", "use card", { uuid: used.uuid, type: used.type }); this.playUseDisappearAnim(() => { this.clearAfterUse(); this.isUsing = false; }); return used; } /** 查询槽位是否有卡 */ hasCard(): boolean { return !!this.cardData; } /** 外部设置锁定态 */ setLocked(value: boolean) { this.isLocked = value; this.updateLockUI(); } /** 外部读取当前锁定态 */ isSlotLocked(): boolean { return this.isLocked; } /** 系统清槽:用于任务开始/结束等强制重置场景 */ clearBySystem() { Tween.stopAllByTarget(this.node); if (this.opacityComp) { Tween.stopAllByTarget(this.opacityComp); this.opacityComp.opacity = 255; } this.cardData = null; this.card_uuid = 0; this.card_cost = 0; this.card_type = CardType.Hero; this.isLocked = false; this.isDragging = false; this.isUsing = false; this.node.setPosition(this.restPosition); this.node.setScale(new Vec3(1, 1, 1)); this.updateLockUI(); this.applyEmptyUI(); this.node.active = false; } /** 卡牌被玩家使用后的清槽行为 */ private clearAfterUse() { Tween.stopAllByTarget(this.node); if (this.opacityComp) { Tween.stopAllByTarget(this.opacityComp); this.opacityComp.opacity = 255; } this.cardData = null; this.card_uuid = 0; this.card_cost = 0; this.card_type = CardType.Hero; this.isLocked = false; this.isDragging = false; this.node.setPosition(this.restPosition); this.node.setScale(new Vec3(1, 1, 1)); this.updateLockUI(); this.applyEmptyUI(); this.node.active = false; } /** 绑定触控:卡面点击使用,锁按钮点击切换锁定 */ private bindEvents() { this.node.on(NodeEventType.TOUCH_START, this.onCardTouchStart, this); this.node.on(NodeEventType.TOUCH_MOVE, this.onCardTouchMove, this); this.node.on(NodeEventType.TOUCH_END, this.onCardTouchEnd, this); this.node.on(NodeEventType.TOUCH_CANCEL, this.onCardTouchCancel, 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_START, this.onCardTouchStart, this); this.node.off(NodeEventType.TOUCH_MOVE, this.onCardTouchMove, this); this.node.off(NodeEventType.TOUCH_END, this.onCardTouchEnd, this); this.node.off(NodeEventType.TOUCH_CANCEL, this.onCardTouchCancel, this); this.Lock?.off(NodeEventType.TOUCH_END, this.onToggleLock, this); this.unLock?.off(NodeEventType.TOUCH_END, this.onToggleLock, this); } private onCardTouchStart(event: EventTouch) { if (!this.cardData || this.isUsing) return; this.touchStartY = event.getUILocation().y; this.isDragging = true; } private onCardTouchMove(event: EventTouch) { if (!this.isDragging || !this.cardData || this.isUsing) return; const currentY = event.getUILocation().y; const deltaY = Math.max(0, currentY - this.touchStartY); this.node.setPosition(this.restPosition.x, this.restPosition.y + deltaY, this.restPosition.z); } /** 上拉超过阈值才视为使用,否则回弹原位 */ private onCardTouchEnd(event: EventTouch) { if (!this.isDragging || !this.cardData || this.isUsing) return; const endY = event.getUILocation().y; const deltaY = endY - this.touchStartY; this.isDragging = false; if (deltaY >= this.dragUseThreshold) { this.useCard(); return; } this.playReboundAnim(); } private onCardTouchCancel() { if (!this.isDragging || this.isUsing) return; this.isDragging = false; this.playReboundAnim(); } /** 点击锁控件:切换锁态;空槽不允许锁定 */ private onToggleLock(event?: Event) { if (!this.cardData) return; this.isLocked = !this.isLocked; this.updateLockUI(); mLogger.log(this.debugMode, "CardComp", "toggle lock", { uuid: this.card_uuid, locked: this.isLocked }); 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; } if (this.opacityComp) this.opacityComp.opacity = 255; this.node.setPosition(this.restPosition); 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 playRefreshAnim() { Tween.stopAllByTarget(this.node); this.node.setPosition(this.restPosition); this.node.setScale(new Vec3(0.92, 0.92, 1)); tween(this.node) .to(0.08, { scale: new Vec3(1.06, 1.06, 1) }) .to(0.1, { scale: new Vec3(1, 1, 1) }) .start(); } private playReboundAnim() { Tween.stopAllByTarget(this.node); tween(this.node) .to(0.12, { position: this.restPosition, scale: new Vec3(1, 1, 1) }) .start(); } private playUseDisappearAnim(onComplete: () => void) { const targetPos = new Vec3(this.restPosition.x, this.restPosition.y + 120, this.restPosition.z); Tween.stopAllByTarget(this.node); if (this.opacityComp) { Tween.stopAllByTarget(this.opacityComp); this.opacityComp.opacity = 255; tween(this.opacityComp) .to(0.18, { opacity: 0 }) .start(); } tween(this.node) .to(0.18, { position: targetPos, scale: new Vec3(0.8, 0.8, 1) }) .call(onComplete) .start(); } /** 渲染空槽状态 */ 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(); } }