feat(heroList): 升级按钮交互与提示优化

1.  新增升级按钮点击回弹动画与自动绑定逻辑
2.  替换按钮禁用逻辑为气泡提示反馈
3.  重构smalltip预制体并集成到升级按钮
4.  添加完整的调试日志与异常处理
This commit is contained in:
pan
2026-08-24 15:13:13 +08:00
parent bb098d690b
commit f4c5f0daae
4 changed files with 1174 additions and 16622 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,14 +22,14 @@
"__id__": 2
}
],
"_active": false,
"_active": true,
"_components": [
{
"__id__": 22
"__id__": 30
}
],
"_prefab": {
"__id__": 24
"__id__": 32
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -74,24 +74,27 @@
},
{
"__id__": 11
}
],
"_active": true,
"_components": [
{
"__id__": 17
},
{
"__id__": 19
}
],
"_active": true,
"_components": [
{
"__id__": 25
},
{
"__id__": 27
}
],
"_prefab": {
"__id__": 21
"__id__": 29
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 63.35300000000001,
"y": 0,
"z": 0
},
"_lrot": {
@@ -119,7 +122,7 @@
},
{
"__type__": "cc.Node",
"_name": "Arrow",
"_name": "BubbleFrame04_White_Bg",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -144,7 +147,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -35.679,
"y": 0,
"z": 0
},
"_lrot": {
@@ -156,8 +159,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"x": 0.5,
"y": 0.5,
"z": 1
},
"_mobility": 0,
@@ -182,6 +185,181 @@
"__prefab": {
"__id__": 5
},
"_contentSize": {
"__type__": "cc.Size",
"width": 300,
"height": 140
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "5aIVIWevtJH5l+s9IbfCVa"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 7
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d552a",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "e3Rmjr7aNLlZ2fsbmMjUHG"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 64,
"_originalHeight": 72,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d6yD+nfRxBOKOOi1Pr4ztO"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "57iKiUyRJMJ56cQDIrFssC",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "Arrow",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
}
],
"_prefab": {
"__id__": 18
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -35.2,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.5,
"y": 0.5,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"_contentSize": {
"__type__": "cc.Size",
"width": 38,
@@ -204,11 +382,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 7
"__id__": 15
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -249,18 +427,18 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 9
"__id__": 17
},
"_alignFlags": 20,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": -18.179000000000002,
"_bottom": -8.950000000000003,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
@@ -304,14 +482,14 @@
"_active": true,
"_components": [
{
"__id__": 12
"__id__": 20
},
{
"__id__": 14
"__id__": 22
}
],
"_prefab": {
"__id__": 16
"__id__": 24
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -348,11 +526,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
"__id__": 19
},
"_enabled": true,
"__prefab": {
"__id__": 13
"__id__": 21
},
"_contentSize": {
"__type__": "cc.Size",
@@ -376,11 +554,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
"__id__": 19
},
"_enabled": true,
"__prefab": {
"__id__": 15
"__id__": 23
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -461,7 +639,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 18
"__id__": 26
},
"_contentSize": {
"__type__": "cc.Size",
@@ -489,7 +667,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 20
"__id__": 28
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -501,10 +679,7 @@
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d552a",
"__expectedType__": "cc.SpriteFrame"
},
"_spriteFrame": null,
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
@@ -550,7 +725,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 23
"__id__": 31
},
"_contentSize": {
"__type__": "cc.Size",

View File

@@ -17,7 +17,7 @@
* - CardLiteComp —— 轻量卡片组件
* - buildSkillDesc(HeroSkillDesc)—— 技能描述生成器
*/
import { _decorator, Animation, AnimationClip, Label, Node, Prefab, Sprite, UITransform, Widget, instantiate, resources } from "cc";
import { _decorator, Animation, AnimationClip, Button, Label, Node, Prefab, Sprite, UITransform, Vec3, Widget, instantiate, resources, 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 { oops } from "db://oops-framework/core/Oops";
@@ -54,7 +54,7 @@ export class HerosListComp extends CCComp {
/** 卡牌背景底框节点(按卡池等级切换子节点显示) */
@property(Node)
BG_node=null!
BG_node = null!
@property(Node)
info_node = null!
@@ -75,7 +75,7 @@ export class HerosListComp extends CCComp {
@property(Node)
type_node = null!
/** 升级按钮节点 */
/** 升级按钮节点(smalltip 气泡已作为其子节点挂在下面) */
@property(Node)
upgrade_btn = null!
@@ -95,12 +95,45 @@ export class HerosListComp extends CCComp {
debugMode: boolean = false
start() {
this.bindUpgradeBtn()
this.initCardList()
if (HeroList.length > 0) {
this.onCardSelect(HeroList[0])
}
}
protected onLoad(): void {
// onLoad 阶段属性可能尚未绑定,延迟到 start 再处理
}
/**
* 绑定升级按钮点击事件
* 在 start 中调用,确保 @property 属性已正确赋值
*/
private bindUpgradeBtn() {
mLogger.log(this.debugMode, "HerosListComp", "bindUpgradeBtn", {
hasUpgradeBtn: !!this.upgrade_btn,
upgradeBtnName: this.upgrade_btn?.name,
upgradeBtnActive: this.upgrade_btn?.active,
upgradeBtnValid: this.upgrade_btn?.isValid,
})
if (!this.upgrade_btn) {
mLogger.error(this.debugMode, "HerosListComp", "upgrade_btn 未绑定,请检查编辑器属性面板")
return
}
// 确保 smalltip 气泡初始隐藏,避免遮挡按钮触摸
const tipNode = this.upgrade_btn.getChildByName("smalltip")
if (tipNode) tipNode.active = false
let btn = this.upgrade_btn.getComponent(Button)
if (!btn) btn = this.upgrade_btn.addComponent(Button)
btn.interactable = true
this.upgrade_btn.on(Button.EventType.CLICK, this.onUpgradeClick, this)
mLogger.log(this.debugMode, "HerosListComp", "升级按钮点击事件已绑定")
}
protected onEnable(): void {
oops.message.on(GameEvent.HERO_GROWTH_UPDATE, this.onGrowthUpdate, this)
if (this.cards_node && this.cards_node.children.length > 0) {
@@ -127,9 +160,14 @@ export class HerosListComp extends CCComp {
* 消耗碎片提升英雄局外等级,成功后刷新详情面板
*/
onUpgradeClick() {
mLogger.log(this.debugMode, "HerosListComp", "onUpgradeClick 被触发", { huuid: this.huuid })
const uuid = this.huuid
if (!uuid) return
// 点击动画:按压回弹
this.playUpgradeBtnAnim()
const success = smc.upgradeHero(uuid)
if (success) {
mLogger.log(this.debugMode, "HerosListComp", `英雄 ${uuid} 升级成功`)
@@ -142,9 +180,64 @@ export class HerosListComp extends CCComp {
fragments: growth.fragments,
needFragments: need ?? "MAX",
})
// 碎片不足 / 已满级:气泡提示
const tipText = need === undefined ? "已满级" : "碎片不足"
this.showSmallTip(tipText)
}
}
/**
* 播放升级按钮按压回弹动画
*/
private playUpgradeBtnAnim() {
if (!this.upgrade_btn) {
mLogger.log(this.debugMode, "HerosListComp", "playUpgradeBtnAnim: upgrade_btn 为空")
return
}
mLogger.log(this.debugMode, "HerosListComp", "播放按钮动画")
Tween.stopAllByTarget(this.upgrade_btn)
this.upgrade_btn.setScale(1, 1, 1)
tween(this.upgrade_btn)
.to(0.08, { scale: new Vec3(0.9, 0.9, 1) }, { easing: 'quadOut' })
.to(0.12, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadOut' })
.to(0.08, { scale: new Vec3(1, 1, 1) }, { easing: 'quadIn' })
.start()
}
/**
* 在升级按钮附近弹出 smalltip 气泡提示
* @param text 提示文案
*/
private showSmallTip(text: string) {
// smalltip 作为 upgrade_btn 的子节点挂在下面
const tipNode = this.upgrade_btn?.getChildByName("smalltip")
if (!tipNode || !tipNode.isValid) {
mLogger.log(this.debugMode, "HerosListComp", `未在 upgrade_btn 下找到 smalltip 节点,无法提示:${text}`)
return
}
// 覆盖气泡文字
const label = tipNode.getComponentInChildren(Label)
if (label) label.string = text
tipNode.active = true
Tween.stopAllByTarget(tipNode)
tipNode.setScale(0, 0, 1)
tween(tipNode)
// 弹出动画(微放大再回弹)
.to(0.15, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadOut' })
.to(0.05, { scale: new Vec3(1, 1, 1) })
// 停留 1 秒
.delay(1)
// 缩小消失
.to(0.15, { scale: new Vec3(0, 0, 1) }, { easing: 'quadIn' })
.call(() => {
if (tipNode && tipNode.isValid) tipNode.active = false
})
.start()
}
// ======================== 卡片列表 ========================
private initCardList() {
@@ -326,12 +419,12 @@ export class HerosListComp extends CCComp {
chipComp.setHeroByUuid(uuid)
}
// 升级按钮状态:未满级且碎片足够时可用
// 升级按钮状态:始终可点击,点击后由 onUpgradeClick 判断碎片是否足够
// 碎片不足时按钮保持可点,通过气泡提示用户(而非禁用按钮)
if (this.upgrade_btn) {
const canUpgrade = !isMaxLv && growth.fragments >= needFragments
this.upgrade_btn.active = true
const btnComp = this.upgrade_btn.getComponent("cc.Button") as import("cc").Button | null
if (btnComp) btnComp.interactable = canUpgrade
if (btnComp) btnComp.interactable = true
}
}