diff --git a/assets/script/game/map/CardComp.ts b/assets/script/game/map/CardComp.ts index d9fcb168..d78f0447 100644 --- a/assets/script/game/map/CardComp.ts +++ b/assets/script/game/map/CardComp.ts @@ -1,5 +1,5 @@ import { mLogger } from "../common/Logger"; -import { _decorator, Label, Node, NodeEventType, Sprite, SpriteAtlas, resources } from "cc"; +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"; @@ -38,10 +38,19 @@ export class CardComp extends CCComp { 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(); @@ -109,14 +118,28 @@ export class CardComp extends CCComp { 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) return null; + if (!this.cardData || this.isUsing) return null; + this.isUsing = true; const used = this.cardData; - this.clearAfterUse(); + mLogger.log(this.debugMode, "CardComp", "use card", { + uuid: used.uuid, + type: used.type + }); + this.playUseDisappearAnim(() => { + this.clearAfterUse(); + this.isUsing = false; + }); return used; } @@ -138,43 +161,95 @@ export class CardComp extends CCComp { /** 系统清槽:用于任务开始/结束等强制重置场景 */ 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); } - /** 点击卡面:执行单卡使用(仅UI变化) */ - private onCardTouchEnd() { - this.useCard(); + 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(); } /** 点击锁控件:切换锁态;空槽不允许锁定 */ @@ -182,6 +257,10 @@ export class CardComp extends CCComp { 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(); } @@ -197,6 +276,8 @@ export class CardComp extends CCComp { 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; @@ -204,6 +285,45 @@ export class CardComp extends CCComp { 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, ""); diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index dc9034c9..35ce7139 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -40,6 +40,10 @@ export class MissionCardComp extends CCComp { this.bindEvents(); this.cacheCardComps(); this.onMissionStart(); + mLogger.log(this.debugMode, "MissionCardComp", "onLoad init", { + slots: this.cardComps.length, + poolLv: this.poolLv + }); } onDestroy() { @@ -53,8 +57,14 @@ export class MissionCardComp extends CCComp { onMissionStart() { this.poolLv = CARD_POOL_INIT_LEVEL; this.clearAllCards(); + if (this.cards_up) { + this.cards_up.active = true; + } this.updatePoolLvUI(); this.node.active = true; + mLogger.log(this.debugMode, "MissionCardComp", "mission start", { + poolLv: this.poolLv + }); } /** 任务结束时:清空4槽并隐藏面板 */ @@ -100,14 +110,23 @@ export class MissionCardComp extends CCComp { /** 抽卡按钮:每次固定抽4张,然后顺序分发给4个单卡脚本 */ private onClickDraw() { + mLogger.log(this.debugMode, "MissionCardComp", "click draw", { + poolLv: this.poolLv + }); const cards = this.buildDrawCards(); this.dispatchCardsToSlots(cards); } /** 升级按钮:仅提升卡池等级,卡槽是否更新由下一次抽卡触发 */ private onClickUpgrade() { - if (this.poolLv >= CARD_POOL_MAX_LEVEL) return; + if (this.poolLv >= CARD_POOL_MAX_LEVEL) { + mLogger.log(this.debugMode, "MissionCardComp", "pool already max", this.poolLv); + return; + } this.poolLv += 1; + if (this.poolLv >= CARD_POOL_MAX_LEVEL && this.cards_up) { + this.cards_up.active = false; + } this.updatePoolLvUI(); mLogger.log(this.debugMode, "MissionCardComp", "pool level up", this.poolLv); } @@ -130,7 +149,12 @@ export class MissionCardComp extends CCComp { /** 全量分发给4槽;每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */ private dispatchCardsToSlots(cards: CardConfig[]) { for (let i = 0; i < this.cardComps.length; i++) { - this.cardComps[i].applyDrawCard(cards[i] ?? null); + const accepted = this.cardComps[i].applyDrawCard(cards[i] ?? null); + mLogger.log(this.debugMode, "MissionCardComp", "dispatch card", { + index: i, + card: cards[i]?.uuid ?? 0, + accepted + }); } } @@ -145,6 +169,7 @@ export class MissionCardComp extends CCComp { const label = this.cards_up.getComponentInChildren(Label); if (!label) return; label.string = `卡池Lv.${this.poolLv}`; + mLogger.log(this.debugMode, "MissionCardComp", "pool lv ui update", this.poolLv); } /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */