feat(map): 为三类商店卡牌组件添加动画交互效果

1.  新增按钮按下/取消缩放反馈、点击回弹动画
2.  新增卡牌入场淡入放大、购买放大淡出、刷新缩小淡出动画
3.  优化卡片切换逻辑,支持按状态播放对应动画
4.  同步重构了ItemListComp、EquipListComp、SCardComp三个组件
5.  删除了废弃的装备预制体元数据文件
This commit is contained in:
panFD
2026-07-26 14:48:43 +08:00
parent d135aa9905
commit 24eb72ef99
7 changed files with 4150 additions and 3350 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "101841f1-5f9a-4115-964f-5a6ca92a0e46",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "equip-001"
}
}

View File

@@ -12,7 +12,7 @@
* EquipBoxComp 按 Field 类型被动驻场,属性加成由 FieldSkillHelper 全局聚合。
*/
import { mLogger } from "../common/Logger";
import { _decorator, Node, Sprite, Label, NodeEventType } from "cc";
import { _decorator, Node, Sprite, Label, NodeEventType, Vec3, UIOpacity, tween, Tween } 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 } from "../common/config/CardSet";
@@ -74,17 +74,28 @@ export class EquipListComp extends CCComp {
private cardData: CardConfig | null = null;
/** 是否已购买 */
private isPurchased: boolean = false;
/** 透明度组件(用于淡入淡出动画) */
private opacityComp: UIOpacity | null = null;
/** 入场动画时长(秒) */
private readonly enterDuration: number = 0.25;
/** 购买/逝去动画时长(秒) */
private readonly exitDuration: number = 0.2;
// ======================== 生命周期 ========================
onLoad() {
this.buy_node?.on(NodeEventType.TOUCH_START, this.onBuyTouchStart, this);
this.buy_node?.on(NodeEventType.TOUCH_END, this.onBuyClick, this);
this.buy_node?.on(NodeEventType.TOUCH_CANCEL, this.onBuyTouchCancel, this);
this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
}
onDestroy() {
super.onDestroy();
if (this.buy_node && this.buy_node.isValid) {
this.buy_node.off(NodeEventType.TOUCH_START, this.onBuyTouchStart, this);
this.buy_node.off(NodeEventType.TOUCH_END, this.onBuyClick, this);
this.buy_node.off(NodeEventType.TOUCH_CANCEL, this.onBuyTouchCancel, this);
}
}
@@ -93,10 +104,33 @@ export class EquipListComp extends CCComp {
/**
* 初始化装备卡数据并渲染 UI
*
* 若槽位已有旧卡且显示中,先播普通逝去动画(缩小淡出),结束后切新卡;
* 刚购买的槽位跳过逝去动画直接应用新卡(购买动画已播放大淡出)。
*
* @param data 装备卡配置(必须为 trigger_type=Field 的装备卡)
*/
applyCardData(data: CardConfig): void {
if (!data) return;
// 刚购买(已播放大逝去动画)→ 不播普通逝去动画,直接应用新卡
if (this.isPurchased) {
this.cardData = data;
this.isPurchased = false;
this.updateUI();
return;
}
// 旧卡存在且显示中 → 播普通逝去动画,结束后切新卡
if (this.cardData && this.node.active) {
this.playRefreshExitAnim(() => {
this.cardData = data;
this.isPurchased = false;
this.updateUI();
});
return;
}
// 首次赋值 → 直接渲染
this.cardData = data;
this.isPurchased = false;
this.updateUI();
@@ -150,6 +184,107 @@ export class EquipListComp extends CCComp {
costLabel.string = `${this.cardData.cost ?? 0}`;
}
}
// 播放入场动画(由小变大 + 淡入)
this.playEnterAnim();
}
// ======================== 动画 ========================
/** 播放入场动画卡牌由小变大scale 0→1+ 淡入 */
private playEnterAnim() {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
this.node.setScale(0, 0, 1);
if (this.opacityComp) {
this.opacityComp.opacity = 0;
}
tween(this.node)
.to(this.enterDuration, { scale: new Vec3(1, 1, 1) }, { easing: 'backOut' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.enterDuration, { opacity: 255 })
.start();
}
}
/** 播放购买动画卡牌放大scale 1→1.1+ 淡出 */
private playPurchaseAnim() {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
tween(this.node)
.to(this.exitDuration, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadIn' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.exitDuration, { opacity: 0 })
.start();
}
}
/**
* 播放普通刷新"逝去"动画旧卡缩小scale 1→0+ 淡出,结束后回调切换新卡。
* @param onComplete 动画结束回调,用于应用新卡数据
*/
private playRefreshExitAnim(onComplete: () => void) {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
tween(this.node)
.to(this.exitDuration, { scale: new Vec3(0, 0, 1) }, { easing: 'quadIn' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.exitDuration, { opacity: 0 })
.call(onComplete)
.start();
} else {
this.scheduleOnce(onComplete, this.exitDuration);
}
}
// ======================== 按钮动画 ========================
/** 按钮正常缩放 */
private readonly btnNormalScale: number = 1;
/** 按钮按下缩放 */
private readonly btnPressScale: number = 0.92;
/** 按钮点击回弹峰值缩放 */
private readonly btnClickScale: number = 1.08;
/** 购买按钮按下:缩小反馈 */
private onBuyTouchStart() {
this.playBtnScaleTo(this.btnPressScale, 0.06);
}
/** 购买按钮取消:恢复缩放 */
private onBuyTouchCancel() {
this.playBtnScaleTo(this.btnNormalScale, 0.08);
}
/** 按钮缩放动画(通用) */
private playBtnScaleTo(scale: number, duration: number) {
if (!this.buy_node || !this.buy_node.isValid) return;
Tween.stopAllByTarget(this.buy_node);
tween(this.buy_node)
.to(duration, { scale: new Vec3(scale, scale, 1) })
.start();
}
/** 按钮点击回弹动画:先弹到峰值再回到正常 */
private playBtnClickAnim() {
if (!this.buy_node || !this.buy_node.isValid) return;
Tween.stopAllByTarget(this.buy_node);
tween(this.buy_node)
.to(0.05, { scale: new Vec3(this.btnClickScale, this.btnClickScale, 1) })
.to(0.08, { scale: new Vec3(this.btnNormalScale, this.btnNormalScale, 1) })
.start();
}
/** 更新图标精灵 */
@@ -178,6 +313,9 @@ export class EquipListComp extends CCComp {
private onBuyClick() {
if (!this.cardData || this.isPurchased) return;
// 播放按钮点击回弹动画
this.playBtnClickAnim();
oops.audio.playEffect("music/button");
// 扣费
@@ -201,6 +339,9 @@ export class EquipListComp extends CCComp {
this.buy_node.active = false;
}
// 播放购买动画(卡牌放大 + 淡出)
this.playPurchaseAnim();
mLogger.log(this.debugMode, "EquipListComp", "equipment purchased", {
uuid: this.cardData.uuid,
cost

View File

@@ -14,7 +14,7 @@
* 本组件同时服务于装备商店和商品商店,通过 cardData 的 trigger_type / kind 自动判断类型。
*/
import { mLogger } from "../common/Logger";
import { _decorator, Node, Sprite, Label, NodeEventType } from "cc";
import { _decorator, Node, Sprite, Label, NodeEventType, Vec3, UIOpacity, tween, Tween } 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, CardTriggerType, CKind } from "../common/config/CardSet";
@@ -76,17 +76,28 @@ export class ItemListComp extends CCComp {
private cardData: CardConfig | null = null;
/** 是否已购买 */
private isPurchased: boolean = false;
/** 透明度组件(用于淡入淡出动画) */
private opacityComp: UIOpacity | null = null;
/** 入场动画时长(秒) */
private readonly enterDuration: number = 0.25;
/** 购买/逝去动画时长(秒) */
private readonly exitDuration: number = 0.2;
// ======================== 生命周期 ========================
onLoad() {
this.buy_node?.on(NodeEventType.TOUCH_START, this.onBuyTouchStart, this);
this.buy_node?.on(NodeEventType.TOUCH_END, this.onBuyClick, this);
this.buy_node?.on(NodeEventType.TOUCH_CANCEL, this.onBuyTouchCancel, this);
this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
}
onDestroy() {
super.onDestroy();
if (this.buy_node && this.buy_node.isValid) {
this.buy_node.off(NodeEventType.TOUCH_START, this.onBuyTouchStart, this);
this.buy_node.off(NodeEventType.TOUCH_END, this.onBuyClick, this);
this.buy_node.off(NodeEventType.TOUCH_CANCEL, this.onBuyTouchCancel, this);
}
}
@@ -95,10 +106,33 @@ export class ItemListComp extends CCComp {
/**
* 初始化商品卡数据并渲染 UI
*
* 若槽位已有旧卡且显示中,先播普通逝去动画(缩小淡出),结束后切新卡;
* 刚购买的槽位跳过逝去动画直接应用新卡(购买动画已播放大淡出)。
*
* @param data 商品/装备卡配置
*/
applyCardData(data: CardConfig): void {
if (!data) return;
// 刚购买(已播放大逝去动画)→ 不播普通逝去动画,直接应用新卡
if (this.isPurchased) {
this.cardData = data;
this.isPurchased = false;
this.updateUI();
return;
}
// 旧卡存在且显示中 → 播普通逝去动画,结束后切新卡
if (this.cardData && this.node.active) {
this.playRefreshExitAnim(() => {
this.cardData = data;
this.isPurchased = false;
this.updateUI();
});
return;
}
// 首次赋值 → 直接渲染
this.cardData = data;
this.isPurchased = false;
this.updateUI();
@@ -196,6 +230,107 @@ export class ItemListComp extends CCComp {
costLabel.string = `${this.cardData.cost ?? 0}`;
}
}
// 播放入场动画(由小变大 + 淡入)
this.playEnterAnim();
}
// ======================== 动画 ========================
/** 播放入场动画卡牌由小变大scale 0→1+ 淡入 */
private playEnterAnim() {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
this.node.setScale(0, 0, 1);
if (this.opacityComp) {
this.opacityComp.opacity = 0;
}
tween(this.node)
.to(this.enterDuration, { scale: new Vec3(1, 1, 1) }, { easing: 'backOut' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.enterDuration, { opacity: 255 })
.start();
}
}
/** 播放购买动画卡牌放大scale 1→1.1+ 淡出 */
private playPurchaseAnim() {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
tween(this.node)
.to(this.exitDuration, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadIn' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.exitDuration, { opacity: 0 })
.start();
}
}
/**
* 播放普通刷新"逝去"动画旧卡缩小scale 1→0+ 淡出,结束后回调切换新卡。
* @param onComplete 动画结束回调,用于应用新卡数据
*/
private playRefreshExitAnim(onComplete: () => void) {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
tween(this.node)
.to(this.exitDuration, { scale: new Vec3(0, 0, 1) }, { easing: 'quadIn' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.exitDuration, { opacity: 0 })
.call(onComplete)
.start();
} else {
this.scheduleOnce(onComplete, this.exitDuration);
}
}
// ======================== 按钮动画 ========================
/** 按钮正常缩放 */
private readonly btnNormalScale: number = 1;
/** 按钮按下缩放 */
private readonly btnPressScale: number = 0.92;
/** 按钮点击回弹峰值缩放 */
private readonly btnClickScale: number = 1.08;
/** 购买按钮按下:缩小反馈 */
private onBuyTouchStart() {
this.playBtnScaleTo(this.btnPressScale, 0.06);
}
/** 购买按钮取消:恢复缩放 */
private onBuyTouchCancel() {
this.playBtnScaleTo(this.btnNormalScale, 0.08);
}
/** 按钮缩放动画(通用) */
private playBtnScaleTo(scale: number, duration: number) {
if (!this.buy_node || !this.buy_node.isValid) return;
Tween.stopAllByTarget(this.buy_node);
tween(this.buy_node)
.to(duration, { scale: new Vec3(scale, scale, 1) })
.start();
}
/** 按钮点击回弹动画:先弹到峰值再回到正常 */
private playBtnClickAnim() {
if (!this.buy_node || !this.buy_node.isValid) return;
Tween.stopAllByTarget(this.buy_node);
tween(this.buy_node)
.to(0.05, { scale: new Vec3(this.btnClickScale, this.btnClickScale, 1) })
.to(0.08, { scale: new Vec3(this.btnNormalScale, this.btnNormalScale, 1) })
.start();
}
/** 更新图标精灵 */
@@ -222,6 +357,9 @@ export class ItemListComp extends CCComp {
private onBuyClick() {
if (!this.cardData || this.isPurchased) return;
// 播放按钮点击回弹动画
this.playBtnClickAnim();
oops.audio.playEffect("music/button");
// 扣费
@@ -269,6 +407,9 @@ export class ItemListComp extends CCComp {
if (this.buy_node) {
this.buy_node.active = false;
}
// 播放购买动画(卡牌放大 + 淡出)
this.playPurchaseAnim();
}
/** 标记为已购买状态(用于跨波次保持购买记录) */

View File

@@ -11,7 +11,7 @@
* 由 MissSkillsComp 接收并创建 SBox 实体 / SkillBoxComp 执行技能逻辑。
*/
import { mLogger } from "../common/Logger";
import { _decorator, Node, Sprite, Label, NodeEventType } from "cc";
import { _decorator, Node, Sprite, Label, NodeEventType, Vec3, UIOpacity, tween, Tween } 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 } from "../common/config/CardSet";
@@ -73,17 +73,28 @@ export class SCardComp extends CCComp {
private cardData: CardConfig | null = null;
/** 是否已购买 */
private isPurchased: boolean = false;
/** 透明度组件(用于淡入淡出动画) */
private opacityComp: UIOpacity | null = null;
/** 入场动画时长(秒) */
private readonly enterDuration: number = 0.25;
/** 购买/逝去动画时长(秒) */
private readonly exitDuration: number = 0.2;
// ======================== 生命周期 ========================
onLoad() {
this.buy_node?.on(NodeEventType.TOUCH_START, this.onBuyTouchStart, this);
this.buy_node?.on(NodeEventType.TOUCH_END, this.onBuyClick, this);
this.buy_node?.on(NodeEventType.TOUCH_CANCEL, this.onBuyTouchCancel, this);
this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
}
onDestroy() {
super.onDestroy();
if (this.buy_node && this.buy_node.isValid) {
this.buy_node.off(NodeEventType.TOUCH_START, this.onBuyTouchStart, this);
this.buy_node.off(NodeEventType.TOUCH_END, this.onBuyClick, this);
this.buy_node.off(NodeEventType.TOUCH_CANCEL, this.onBuyTouchCancel, this);
}
}
@@ -92,9 +103,41 @@ export class SCardComp extends CCComp {
/**
* 初始化技能卡数据并渲染 UI
*
* 若槽位已有旧卡且显示中,先播普通逝去动画(缩小淡出),结束后切新卡;
* 刚购买的槽位跳过逝去动画直接应用新卡(购买动画已播放大淡出)。
*
* @param data 技能卡配置,传 null 时清空显示
*/
applyCardData(data: CardConfig | null): void {
// 刚购买(已播放大逝去动画)→ 不播普通逝去动画,直接应用新卡
if (this.isPurchased) {
this.cardData = data;
this.isPurchased = false;
if (data) {
this.node.active = true;
this.updateUI();
} else {
this.applyEmptyUI();
}
return;
}
// 旧卡存在且显示中 → 播普通逝去动画,结束后切新卡
if (this.cardData && this.node.active) {
this.playRefreshExitAnim(() => {
this.cardData = data;
this.isPurchased = false;
if (data) {
this.node.active = true;
this.updateUI();
} else {
this.applyEmptyUI();
}
});
return;
}
// 首次赋值或空槽 → 直接渲染
this.cardData = data;
this.isPurchased = false;
if (data) {
@@ -107,6 +150,14 @@ export class SCardComp extends CCComp {
/** 清空 UI 显示 */
private applyEmptyUI() {
// 清理动画状态
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
this.opacityComp.opacity = 255;
}
this.node.setScale(1, 1, 1);
if (this.name_label) this.name_label.string = "";
if (this.level_label) this.level_label.string = "";
const fieldLabels = [this.fied1, this.fied2, this.fied3];
@@ -171,6 +222,107 @@ export class SCardComp extends CCComp {
costLabel.string = `${this.cardData.cost ?? 0}`;
}
}
// 播放入场动画(由小变大 + 淡入)
this.playEnterAnim();
}
// ======================== 动画 ========================
/** 播放入场动画卡牌由小变大scale 0→1+ 淡入 */
private playEnterAnim() {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
this.node.setScale(0, 0, 1);
if (this.opacityComp) {
this.opacityComp.opacity = 0;
}
tween(this.node)
.to(this.enterDuration, { scale: new Vec3(1, 1, 1) }, { easing: 'backOut' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.enterDuration, { opacity: 255 })
.start();
}
}
/** 播放购买动画卡牌放大scale 1→1.1+ 淡出 */
private playPurchaseAnim() {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
tween(this.node)
.to(this.exitDuration, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadIn' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.exitDuration, { opacity: 0 })
.start();
}
}
/**
* 播放普通刷新"逝去"动画旧卡缩小scale 1→0+ 淡出,结束后回调切换新卡。
* @param onComplete 动画结束回调,用于应用新卡数据
*/
private playRefreshExitAnim(onComplete: () => void) {
Tween.stopAllByTarget(this.node);
if (this.opacityComp) {
Tween.stopAllByTarget(this.opacityComp);
}
tween(this.node)
.to(this.exitDuration, { scale: new Vec3(0, 0, 1) }, { easing: 'quadIn' })
.start();
if (this.opacityComp) {
tween(this.opacityComp)
.to(this.exitDuration, { opacity: 0 })
.call(onComplete)
.start();
} else {
this.scheduleOnce(onComplete, this.exitDuration);
}
}
// ======================== 按钮动画 ========================
/** 按钮正常缩放 */
private readonly btnNormalScale: number = 1;
/** 按钮按下缩放 */
private readonly btnPressScale: number = 0.92;
/** 按钮点击回弹峰值缩放 */
private readonly btnClickScale: number = 1.08;
/** 购买按钮按下:缩小反馈 */
private onBuyTouchStart() {
this.playBtnScaleTo(this.btnPressScale, 0.06);
}
/** 购买按钮取消:恢复缩放 */
private onBuyTouchCancel() {
this.playBtnScaleTo(this.btnNormalScale, 0.08);
}
/** 按钮缩放动画(通用) */
private playBtnScaleTo(scale: number, duration: number) {
if (!this.buy_node || !this.buy_node.isValid) return;
Tween.stopAllByTarget(this.buy_node);
tween(this.buy_node)
.to(duration, { scale: new Vec3(scale, scale, 1) })
.start();
}
/** 按钮点击回弹动画:先弹到峰值再回到正常 */
private playBtnClickAnim() {
if (!this.buy_node || !this.buy_node.isValid) return;
Tween.stopAllByTarget(this.buy_node);
tween(this.buy_node)
.to(0.05, { scale: new Vec3(this.btnClickScale, this.btnClickScale, 1) })
.to(0.08, { scale: new Vec3(this.btnNormalScale, this.btnNormalScale, 1) })
.start();
}
/** 获取技能描述:驻场技能取 FieldSkillSet其余取技能/卡牌配置 */
@@ -214,6 +366,9 @@ export class SCardComp extends CCComp {
private onBuyClick() {
if (!this.cardData || this.isPurchased) return;
// 播放按钮点击回弹动画
this.playBtnClickAnim();
oops.audio.playEffect("music/button");
// 扣费
@@ -239,6 +394,9 @@ export class SCardComp extends CCComp {
this.buy_node.active = false;
}
// 播放购买动画(卡牌放大 + 淡出)
this.playPurchaseAnim();
mLogger.log(this.debugMode, "SCardComp", "skill card purchased", {
uuid: this.cardData.uuid,
cost