feat: 为卡牌组件添加拖拽使用动画并增强日志
- 为 CardComp 添加拖拽使用交互:上拉超过阈值触发使用,否则回弹 - 增加卡牌刷新、回弹、使用消失的 Tween 动画 - 在 MissionCardComp 和 CardComp 的关键节点添加调试日志 - 修复升级按钮在达到最大等级后隐藏升级提示的问题 - 优化卡牌使用和清槽时的动画与状态重置逻辑
This commit is contained in:
@@ -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, "");
|
||||
|
||||
@@ -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) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
|
||||
Reference in New Issue
Block a user