refactor(heroCard): 重构英雄卡牌系统逻辑与UI布局
1. 重写抽卡逻辑,移除升级卡机制,改为从基础英雄池按权重抽取不重复英雄 2. 优化英雄卡入场、购买、刷新动画,新增按钮触摸反馈效果 3. 调整mission.prefab与cHero.prefab的UI元素位置与布局参数 4. 简化卡牌布局代码,交由prefab的Widget组件自动约束
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
* - 英雄卡效果事件为 GameEvent.CallHero,而非 UseSkillCard。
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Sprite, Label, NodeEventType, Vec3 } from "cc";
|
||||
import { _decorator, Node, Sprite, Label, NodeEventType, Vec3, Tween, tween, UIOpacity } 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";
|
||||
@@ -88,6 +88,8 @@ export class CHeroComp extends CCComp {
|
||||
private fixedBaseZ: number = 0;
|
||||
/** 静止位置(由 MissionCardComp 布局决定) */
|
||||
private restPosition: Vec3 = new Vec3();
|
||||
/** 透明度组件(用于淡入淡出) */
|
||||
private opacityComp: UIOpacity | null = null;
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
@@ -106,7 +108,12 @@ export class CHeroComp extends CCComp {
|
||||
fied3: !!this.fied3,
|
||||
buy_node: !!this.buy_node
|
||||
});
|
||||
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);
|
||||
|
||||
// 补偿首次渲染:addComponent 当帧 onLoad 尚未触发,
|
||||
// 若 MissionCardComp 在 onLoad 前已 applyCardData,此处绑定就绪后重刷一次
|
||||
@@ -118,7 +125,9 @@ export class CHeroComp extends CCComp {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +136,10 @@ export class CHeroComp extends CCComp {
|
||||
/**
|
||||
* 初始化英雄卡数据并渲染 UI
|
||||
*
|
||||
* 若槽位已有旧卡且处于显示状态,先播放旧卡"逝去"动画(缩小淡出),
|
||||
* 动画结束后再切换为新卡数据并播放入场动画。
|
||||
* 首次赋值(node 未激活)直接渲染,无逝去动画。
|
||||
*
|
||||
* @param data 卡牌配置,传 null 时清空显示
|
||||
*/
|
||||
applyCardData(data: CardConfig | null): void {
|
||||
@@ -142,6 +155,36 @@ export class CHeroComp extends CCComp {
|
||||
icon: data?.icon ?? null,
|
||||
nodeActiveBefore: this.node?.active
|
||||
});
|
||||
|
||||
// 刚购买(已播放大逝去动画)→ 不播普通逝去动画,直接应用新卡
|
||||
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) {
|
||||
@@ -154,6 +197,12 @@ export class CHeroComp 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];
|
||||
@@ -252,6 +301,122 @@ export class CHeroComp extends CCComp {
|
||||
costLabel.string = `${this.cardData.cost ?? 0}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 入场动画:从静止位置下方升起 + 淡入
|
||||
this.playEnterAnim();
|
||||
}
|
||||
|
||||
// ======================== 动画效果 ========================
|
||||
|
||||
/** 入场动画时长(秒) */
|
||||
private readonly enterDuration: number = 0.25;
|
||||
/** 购买动画时长(秒) */
|
||||
private readonly purchaseDuration: number = 0.2;
|
||||
|
||||
/**
|
||||
* 播放入场动画:由小变大(scale 0→1)+ 淡入。
|
||||
* 不依赖位置,与 Widget 布局完全解耦。
|
||||
*/
|
||||
private playEnterAnim() {
|
||||
Tween.stopAllByTarget(this.node);
|
||||
if (this.opacityComp) {
|
||||
Tween.stopAllByTarget(this.opacityComp);
|
||||
this.opacityComp.opacity = 0;
|
||||
}
|
||||
this.node.setScale(0, 0, 1);
|
||||
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)+ 淡出,结束后隐藏节点。
|
||||
* 动画结束由 onBuyClick 的后续逻辑(CardUsed 刷新卡池)接管。
|
||||
*/
|
||||
private playPurchaseAnim() {
|
||||
Tween.stopAllByTarget(this.node);
|
||||
if (this.opacityComp) {
|
||||
Tween.stopAllByTarget(this.opacityComp);
|
||||
}
|
||||
tween(this.node)
|
||||
.to(this.purchaseDuration, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadIn' })
|
||||
.start();
|
||||
if (this.opacityComp) {
|
||||
tween(this.opacityComp)
|
||||
.to(this.purchaseDuration, { 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.purchaseDuration, { scale: new Vec3(0, 0, 1) }, { easing: 'quadIn' })
|
||||
.start();
|
||||
if (this.opacityComp) {
|
||||
tween(this.opacityComp)
|
||||
.to(this.purchaseDuration, { opacity: 0 })
|
||||
.call(onComplete)
|
||||
.start();
|
||||
} else {
|
||||
// 无透明度组件时直接延迟回调
|
||||
this.scheduleOnce(onComplete, this.purchaseDuration);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 按钮动画 ========================
|
||||
|
||||
/** 按钮正常缩放 */
|
||||
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(onComplete?: () => void) {
|
||||
if (!this.buy_node || !this.buy_node.isValid) {
|
||||
onComplete?.();
|
||||
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) })
|
||||
.call(() => onComplete?.())
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,15 +463,19 @@ export class CHeroComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 购买点击处理:
|
||||
* 1. 英雄卡先通过 UseHeroCard 事件做数量上限校验(guard 模式,可被取消)。
|
||||
* 2. 扣除金币(失败则提示并中止)。
|
||||
* 3. 按卡牌类型分发效果:英雄卡 → CallHero 召唤英雄;特殊卡 → UseSpecialCard。
|
||||
* 4. 派发 CardUsed 通知 MissionCardComp 刷新英雄卡池。
|
||||
* 5. 标记已购买,隐藏购买按钮。
|
||||
* 1. 播放按钮点击回弹动画。
|
||||
* 2. 英雄卡先通过 UseHeroCard 事件做数量上限校验(guard 模式,可被取消)。
|
||||
* 3. 扣除金币(失败则提示并中止)。
|
||||
* 4. 按卡牌类型分发效果:英雄卡 → CallHero 召唤英雄;特殊卡 → UseSpecialCard。
|
||||
* 5. 派发 CardUsed 通知 MissionCardComp 刷新英雄卡池。
|
||||
* 6. 标记已购买,隐藏购买按钮。
|
||||
*/
|
||||
private onBuyClick() {
|
||||
if (!this.cardData || this.isPurchased) return;
|
||||
|
||||
// 播放按钮点击回弹动画(视觉反馈,不阻塞后续逻辑)
|
||||
this.playBtnClickAnim();
|
||||
|
||||
oops.audio.playEffect("music/button");
|
||||
|
||||
// 英雄卡数量上限校验:guard 模式,MissionCardComp 可设 cancel=true 阻止使用
|
||||
@@ -353,6 +522,9 @@ export class CHeroComp extends CCComp {
|
||||
break;
|
||||
}
|
||||
|
||||
// 播放购买动画(缩小淡出),刷新卡池由 CardUsed 事件触发
|
||||
this.playPurchaseAnim();
|
||||
|
||||
// 标记已购买
|
||||
this.isPurchased = true;
|
||||
if (this.buy_node) {
|
||||
|
||||
Reference in New Issue
Block a user