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 "__id__": 2
} }
], ],
"_active": false, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 22 "__id__": 30
} }
], ],
"_prefab": { "_prefab": {
"__id__": 24 "__id__": 32
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -74,24 +74,27 @@
}, },
{ {
"__id__": 11 "__id__": 11
}
],
"_active": true,
"_components": [
{
"__id__": 17
}, },
{ {
"__id__": 19 "__id__": 19
} }
], ],
"_active": true,
"_components": [
{
"__id__": 25
},
{
"__id__": 27
}
],
"_prefab": { "_prefab": {
"__id__": 21 "__id__": 29
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": 63.35300000000001, "y": 0,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -119,7 +122,7 @@
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "Arrow", "_name": "BubbleFrame04_White_Bg",
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"_parent": { "_parent": {
@@ -144,7 +147,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": -35.679, "y": 0,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -156,8 +159,8 @@
}, },
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 1, "x": 0.5,
"y": 1, "y": 0.5,
"z": 1 "z": 1
}, },
"_mobility": 0, "_mobility": 0,
@@ -182,6 +185,181 @@
"__prefab": { "__prefab": {
"__id__": 5 "__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": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 38, "width": 38,
@@ -204,11 +382,11 @@
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"node": { "node": {
"__id__": 3 "__id__": 11
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 7 "__id__": 15
}, },
"_customMaterial": null, "_customMaterial": null,
"_srcBlendFactor": 2, "_srcBlendFactor": 2,
@@ -249,18 +427,18 @@
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"node": { "node": {
"__id__": 3 "__id__": 11
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 9 "__id__": 17
}, },
"_alignFlags": 20, "_alignFlags": 20,
"_target": null, "_target": null,
"_left": 0, "_left": 0,
"_right": 0, "_right": 0,
"_top": 0, "_top": 0,
"_bottom": -18.179000000000002, "_bottom": -8.950000000000003,
"_horizontalCenter": 0, "_horizontalCenter": 0,
"_verticalCenter": 0, "_verticalCenter": 0,
"_isAbsLeft": true, "_isAbsLeft": true,
@@ -304,14 +482,14 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 12 "__id__": 20
}, },
{ {
"__id__": 14 "__id__": 22
} }
], ],
"_prefab": { "_prefab": {
"__id__": 16 "__id__": 24
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -348,11 +526,11 @@
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"node": { "node": {
"__id__": 11 "__id__": 19
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 13 "__id__": 21
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
@@ -376,11 +554,11 @@
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"node": { "node": {
"__id__": 11 "__id__": 19
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 15 "__id__": 23
}, },
"_customMaterial": null, "_customMaterial": null,
"_srcBlendFactor": 2, "_srcBlendFactor": 2,
@@ -461,7 +639,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 18 "__id__": 26
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
@@ -489,7 +667,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 20 "__id__": 28
}, },
"_customMaterial": null, "_customMaterial": null,
"_srcBlendFactor": 2, "_srcBlendFactor": 2,
@@ -501,10 +679,7 @@
"b": 255, "b": 255,
"a": 255 "a": 255
}, },
"_spriteFrame": { "_spriteFrame": null,
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d552a",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1, "_type": 1,
"_fillType": 0, "_fillType": 0,
"_sizeMode": 0, "_sizeMode": 0,
@@ -550,7 +725,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 23 "__id__": 31
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",

View File

@@ -17,7 +17,7 @@
* - CardLiteComp —— 轻量卡片组件 * - CardLiteComp —— 轻量卡片组件
* - buildSkillDesc(HeroSkillDesc)—— 技能描述生成器 * - 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 { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "db://oops-framework/core/Oops"; import { oops } from "db://oops-framework/core/Oops";
@@ -75,7 +75,7 @@ export class HerosListComp extends CCComp {
@property(Node) @property(Node)
type_node = null! type_node = null!
/** 升级按钮节点 */ /** 升级按钮节点(smalltip 气泡已作为其子节点挂在下面) */
@property(Node) @property(Node)
upgrade_btn = null! upgrade_btn = null!
@@ -95,12 +95,45 @@ export class HerosListComp extends CCComp {
debugMode: boolean = false debugMode: boolean = false
start() { start() {
this.bindUpgradeBtn()
this.initCardList() this.initCardList()
if (HeroList.length > 0) { if (HeroList.length > 0) {
this.onCardSelect(HeroList[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 { protected onEnable(): void {
oops.message.on(GameEvent.HERO_GROWTH_UPDATE, this.onGrowthUpdate, this) oops.message.on(GameEvent.HERO_GROWTH_UPDATE, this.onGrowthUpdate, this)
if (this.cards_node && this.cards_node.children.length > 0) { if (this.cards_node && this.cards_node.children.length > 0) {
@@ -127,9 +160,14 @@ export class HerosListComp extends CCComp {
* 消耗碎片提升英雄局外等级,成功后刷新详情面板 * 消耗碎片提升英雄局外等级,成功后刷新详情面板
*/ */
onUpgradeClick() { onUpgradeClick() {
mLogger.log(this.debugMode, "HerosListComp", "onUpgradeClick 被触发", { huuid: this.huuid })
const uuid = this.huuid const uuid = this.huuid
if (!uuid) return if (!uuid) return
// 点击动画:按压回弹
this.playUpgradeBtnAnim()
const success = smc.upgradeHero(uuid) const success = smc.upgradeHero(uuid)
if (success) { if (success) {
mLogger.log(this.debugMode, "HerosListComp", `英雄 ${uuid} 升级成功`) mLogger.log(this.debugMode, "HerosListComp", `英雄 ${uuid} 升级成功`)
@@ -142,9 +180,64 @@ export class HerosListComp extends CCComp {
fragments: growth.fragments, fragments: growth.fragments,
needFragments: need ?? "MAX", 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() { private initCardList() {
@@ -326,12 +419,12 @@ export class HerosListComp extends CCComp {
chipComp.setHeroByUuid(uuid) chipComp.setHeroByUuid(uuid)
} }
// 升级按钮状态:未满级且碎片足够时可用 // 升级按钮状态:始终可点击,点击后由 onUpgradeClick 判断碎片是否足够
// 碎片不足时按钮保持可点,通过气泡提示用户(而非禁用按钮)
if (this.upgrade_btn) { if (this.upgrade_btn) {
const canUpgrade = !isMaxLv && growth.fragments >= needFragments
this.upgrade_btn.active = true this.upgrade_btn.active = true
const btnComp = this.upgrade_btn.getComponent("cc.Button") as import("cc").Button | null const btnComp = this.upgrade_btn.getComponent("cc.Button") as import("cc").Button | null
if (btnComp) btnComp.interactable = canUpgrade if (btnComp) btnComp.interactable = true
} }
} }