feat(map): 重构英雄图鉴页面,实现完整的英雄卡片展示与详情功能
1. 重写HerosListComp组件,实现卡片动态生成、选中高亮、详情更新逻辑 2. 完善CardLiteComp组件,支持英雄卡渲染、点击交互与动画加载 3. 清理冗余的预制体绑定代码,修复异步加载竞态问题 4. 添加详细的日志与注释,优化可维护性
This commit is contained in:
@@ -31,17 +31,17 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 375
|
||||
"__id__": 343
|
||||
},
|
||||
{
|
||||
"__id__": 377
|
||||
"__id__": 345
|
||||
},
|
||||
{
|
||||
"__id__": 379
|
||||
"__id__": 347
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 381
|
||||
"__id__": 349
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -6536,14 +6536,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 370
|
||||
"__id__": 338
|
||||
},
|
||||
{
|
||||
"__id__": 372
|
||||
"__id__": 340
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 374
|
||||
"__id__": 342
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -6593,20 +6593,20 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 363
|
||||
"__id__": 331
|
||||
},
|
||||
{
|
||||
"__id__": 365
|
||||
"__id__": 333
|
||||
},
|
||||
{
|
||||
"__id__": 310
|
||||
},
|
||||
{
|
||||
"__id__": 367
|
||||
"__id__": 335
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 369
|
||||
"__id__": 337
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -6666,7 +6666,7 @@
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 362
|
||||
"__id__": 330
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -7010,34 +7010,21 @@
|
||||
"_parent": {
|
||||
"__id__": 313
|
||||
},
|
||||
"_children": [
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 323
|
||||
},
|
||||
{
|
||||
"__id__": 331
|
||||
"__id__": 325
|
||||
},
|
||||
{
|
||||
"__id__": 339
|
||||
},
|
||||
{
|
||||
"__id__": 347
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 355
|
||||
},
|
||||
{
|
||||
"__id__": 357
|
||||
},
|
||||
{
|
||||
"__id__": 359
|
||||
"__id__": 327
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 361
|
||||
"__id__": 329
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -7273,454 +7260,6 @@
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 312
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 324
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 323
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "b8313fa7-28e5-4d92-9d64-a1e0ecb040a8",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "24rlgXRJ9AHLGpMW+aYyEx",
|
||||
"instance": {
|
||||
"__id__": 325
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "02nuyp0OlFMrhOJh7vihYl",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 326
|
||||
},
|
||||
{
|
||||
"__id__": 328
|
||||
},
|
||||
{
|
||||
"__id__": 329
|
||||
},
|
||||
{
|
||||
"__id__": 330
|
||||
}
|
||||
],
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 327
|
||||
},
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "cardlite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"24rlgXRJ9AHLGpMW+aYyEx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 327
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -260,
|
||||
"y": -120,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 327
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 327
|
||||
},
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 312
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 332
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 331
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "b8313fa7-28e5-4d92-9d64-a1e0ecb040a8",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "24rlgXRJ9AHLGpMW+aYyEx",
|
||||
"instance": {
|
||||
"__id__": 333
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "82qCKYiEZLkJaOe4tyrOBe",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 334
|
||||
},
|
||||
{
|
||||
"__id__": 336
|
||||
},
|
||||
{
|
||||
"__id__": 337
|
||||
},
|
||||
{
|
||||
"__id__": 338
|
||||
}
|
||||
],
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 335
|
||||
},
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "cardlite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"24rlgXRJ9AHLGpMW+aYyEx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 335
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -90,
|
||||
"y": -120,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 335
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 335
|
||||
},
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 312
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 340
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 339
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "b8313fa7-28e5-4d92-9d64-a1e0ecb040a8",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "24rlgXRJ9AHLGpMW+aYyEx",
|
||||
"instance": {
|
||||
"__id__": 341
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "623HfkMl9GcIckg94/cCZ3",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 342
|
||||
},
|
||||
{
|
||||
"__id__": 344
|
||||
},
|
||||
{
|
||||
"__id__": 345
|
||||
},
|
||||
{
|
||||
"__id__": 346
|
||||
}
|
||||
],
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 343
|
||||
},
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "cardlite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"24rlgXRJ9AHLGpMW+aYyEx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 343
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 80,
|
||||
"y": -120,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 343
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 343
|
||||
},
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_objFlags": 0,
|
||||
"_parent": {
|
||||
"__id__": 312
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 348
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 347
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "b8313fa7-28e5-4d92-9d64-a1e0ecb040a8",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "24rlgXRJ9AHLGpMW+aYyEx",
|
||||
"instance": {
|
||||
"__id__": 349
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "164auV8KBJbKiVegqqYH2a",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 350
|
||||
},
|
||||
{
|
||||
"__id__": 352
|
||||
},
|
||||
{
|
||||
"__id__": 353
|
||||
},
|
||||
{
|
||||
"__id__": 354
|
||||
}
|
||||
],
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 351
|
||||
},
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "cardlite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"24rlgXRJ9AHLGpMW+aYyEx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 351
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 250,
|
||||
"y": -120,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 351
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 351
|
||||
},
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
@@ -7731,7 +7270,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 356
|
||||
"__id__": 324
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -7759,7 +7298,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 358
|
||||
"__id__": 326
|
||||
},
|
||||
"_alignFlags": 41,
|
||||
"_target": null,
|
||||
@@ -7795,7 +7334,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 360
|
||||
"__id__": 328
|
||||
},
|
||||
"_resizeMode": 0,
|
||||
"_layoutType": 3,
|
||||
@@ -7859,7 +7398,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 364
|
||||
"__id__": 332
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -7887,7 +7426,7 @@
|
||||
},
|
||||
"_enabled": false,
|
||||
"__prefab": {
|
||||
"__id__": 366
|
||||
"__id__": 334
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -7932,7 +7471,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 368
|
||||
"__id__": 336
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -7981,7 +7520,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 371
|
||||
"__id__": 339
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -8009,7 +7548,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 373
|
||||
"__id__": 341
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -8058,7 +7597,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 376
|
||||
"__id__": 344
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -8086,7 +7625,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 378
|
||||
"__id__": 346
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -8122,7 +7661,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 380
|
||||
"__id__": 348
|
||||
},
|
||||
"hero_icon": {
|
||||
"__id__": 95
|
||||
@@ -8149,6 +7688,10 @@
|
||||
"__uuid__": "b8313fa7-28e5-4d92-9d64-a1e0ecb040a8",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"lv_node": {
|
||||
"__id__": 130
|
||||
},
|
||||
"type_node": null,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
@@ -8167,18 +7710,6 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": [
|
||||
{
|
||||
"__id__": 347
|
||||
},
|
||||
{
|
||||
"__id__": 339
|
||||
},
|
||||
{
|
||||
"__id__": 331
|
||||
},
|
||||
{
|
||||
"__id__": 323
|
||||
},
|
||||
{
|
||||
"__id__": 130
|
||||
}
|
||||
|
||||
@@ -1,74 +1,415 @@
|
||||
/**
|
||||
|
||||
* @file CardLiteComp.ts
|
||||
* @description 英雄图鉴卡预制体组件(UI 视图层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 管理英雄图鉴中单张卡牌的 **显示**(名称、图标动画、费用、卡池等级、等级)。
|
||||
* 2. 接收 CardConfig 数据并渲染对应卡面(英雄 / 技能 / 特殊卡)。
|
||||
* 3. 点击卡牌时通过事件通知父组件(HerosListComp)切换选中英雄详情。
|
||||
*
|
||||
* 关键设计:
|
||||
* - 轻量版 CardComp,无拖拽使用、无锁定、无费用扣除等交互。
|
||||
* - 英雄卡图标使用 AnimationClip 动态加载 idle 动画;非英雄卡使用静态图标。
|
||||
* - 通过 iconVisualToken 防止异步加载竞态。
|
||||
*
|
||||
* 依赖:
|
||||
* - CardConfig / CardType / CKind 等卡牌数据结构(CardSet)
|
||||
* - HeroInfo(heroSet)—— 用于渲染英雄卡面信息
|
||||
* - SkillSet(SkillSet)—— 用于渲染技能卡图标
|
||||
* - smc —— 全局单例,访问图标图集缓存
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources, Light, UITransform, Widget } from "cc";
|
||||
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources, UITransform, Widget } 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, SpecialRefreshCardList, SpecialUpgradeCardList, CKind, CardPoolList } from "../common/config/CardSet";
|
||||
import { HeroInfo } from "../common/config/heroSet";
|
||||
import { SkillSet } from "../common/config/SkillSet";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { TalentType } from "../common/config/TalentSet";
|
||||
import { getLvColor } from "../common/config/GameSet";
|
||||
import { MissionEconomy } from "./MissionEconomy";
|
||||
|
||||
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* CardLiteComp —— 单张卡牌简单视图组件
|
||||
*
|
||||
|
||||
* 挂载在英雄图鉴卡预制体上,由 HerosListComp 实例化并管理。
|
||||
* 负责单卡的渲染与点击选择。
|
||||
*/
|
||||
@ccclass('CardLiteComp')
|
||||
@ecs.register('CardLiteComp', false)
|
||||
export class CardLiteComp extends CCComp {
|
||||
/** 是否开启调试日志 */
|
||||
private debugMode: boolean = true;
|
||||
|
||||
// ======================== 编辑器绑定节点 ========================
|
||||
|
||||
/** 锁定态图标节点(显示时表示本槽位锁定) */
|
||||
|
||||
@property(Node)
|
||||
name_node=null!
|
||||
/** 卡牌图标节点(英雄动画 / 技能图标) */
|
||||
name_node: Node = null!
|
||||
@property(Node)
|
||||
icon_node=null!
|
||||
/** 费用显示节点 */
|
||||
icon_node: Node = null!
|
||||
@property(Node)
|
||||
cost_node=null!
|
||||
/** 卡牌种类标识节点(如近战 / 远程 / 辅助等分类子节点的容器) */
|
||||
cost_node: Node = null!
|
||||
@property(Node)
|
||||
Ckind_node=null!
|
||||
/** 卡牌背景底框节点(按卡池等级切换子节点显示) */
|
||||
Ckind_node: Node = null!
|
||||
@property(Node)
|
||||
BG_node=null!
|
||||
|
||||
BG_node: Node = null!
|
||||
@property(Node)
|
||||
pool_lv_node=null! //英雄对应的卡池等级
|
||||
|
||||
pool_lv_node: Node = null!
|
||||
@property(Label)
|
||||
lvl_node: Label = null! //英雄本身的等级
|
||||
lvl_node: Label = null!
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 当前卡牌的金币费用 */
|
||||
card_cost:number=0
|
||||
/** 当前卡牌类型(英雄 / 技能 / 特殊升级 / 特殊刷新) */
|
||||
card_type:CardType=CardType.Hero
|
||||
/** 当前卡牌的唯一标识 UUID */
|
||||
card_uuid:number=0
|
||||
|
||||
card_cost: number = 0
|
||||
card_type: CardType = CardType.Hero
|
||||
card_uuid: number = 0
|
||||
private cardData: CardConfig | null = null;
|
||||
|
||||
private iconVisualToken: number = 0;
|
||||
private opacityComp: UIOpacity | null = null;
|
||||
|
||||
/** 点击回调(由外部 HerosListComp 设置) */
|
||||
onClickCallback: ((comp: CardLiteComp) => void) | null = null;
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
onLoad() {
|
||||
this.bindEvents();
|
||||
this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
|
||||
this.opacityComp.opacity = 255;
|
||||
if (!this.cardData) {
|
||||
this.applyEmptyUI();
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
super.onDestroy();
|
||||
this.unbindEvents();
|
||||
}
|
||||
|
||||
start() {
|
||||
this.node.active = true;
|
||||
}
|
||||
|
||||
// ======================== 对外接口 ========================
|
||||
|
||||
/**
|
||||
* 设置卡牌数据并渲染。
|
||||
* @param data 卡牌配置数据
|
||||
*/
|
||||
setData(data: CardConfig) {
|
||||
if (!data) return;
|
||||
this.cardData = data;
|
||||
this.card_uuid = data.uuid;
|
||||
this.card_type = data.type;
|
||||
this.card_cost = data.cost ?? 0;
|
||||
this.node.active = true;
|
||||
this.applyCardUI();
|
||||
mLogger.log(this.debugMode, "CardLiteComp", "setData", {
|
||||
uuid: this.card_uuid,
|
||||
type: this.card_type,
|
||||
cost: this.card_cost
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:通过英雄 UUID 和卡池等级直接设置英雄卡。
|
||||
* 自动从 CardPoolList 查找匹配的卡牌配置。
|
||||
* @param uuid 英雄 UUID
|
||||
* @param poolLv 卡池等级(默认 1)
|
||||
*/
|
||||
setHeroByUuid(uuid: number, poolLv: number = 1) {
|
||||
const card = CardPoolList.find(c => c.uuid === uuid && c.pool_lv === poolLv);
|
||||
if (card) {
|
||||
this.setData(card);
|
||||
return;
|
||||
}
|
||||
const hero = HeroInfo[uuid];
|
||||
if (hero) {
|
||||
this.setData({
|
||||
uuid: hero.uuid,
|
||||
type: CardType.Hero,
|
||||
cost: 5,
|
||||
weight: 25,
|
||||
pool_lv: poolLv as any,
|
||||
kind: CKind.Hero,
|
||||
hero_lv: hero.lv || 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 查询当前是否有卡牌数据 */
|
||||
hasCard(): boolean {
|
||||
return !!this.cardData;
|
||||
}
|
||||
|
||||
/** 获取当前卡牌数据 */
|
||||
getCardData(): CardConfig | null {
|
||||
return this.cardData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空卡牌数据并重置 UI。
|
||||
*/
|
||||
clear() {
|
||||
this.cardData = null;
|
||||
this.card_uuid = 0;
|
||||
this.card_cost = 0;
|
||||
this.card_type = CardType.Hero;
|
||||
this.applyEmptyUI();
|
||||
this.node.active = false;
|
||||
}
|
||||
|
||||
// ======================== 事件绑定 ========================
|
||||
|
||||
private bindEvents() {
|
||||
this.node.on(NodeEventType.TOUCH_END, this.onCardClick, this);
|
||||
}
|
||||
|
||||
private unbindEvents() {
|
||||
if (this.node && this.node.isValid) {
|
||||
this.node.off(NodeEventType.TOUCH_END, this.onCardClick, this);
|
||||
}
|
||||
}
|
||||
|
||||
/** 点击卡牌:触发回调通知父组件 */
|
||||
private onCardClick(event: EventTouch) {
|
||||
if (!this.cardData) return;
|
||||
event.propagationStopped = true;
|
||||
if (this.onClickCallback) {
|
||||
this.onClickCallback(this);
|
||||
}
|
||||
mLogger.log(this.debugMode, "CardLiteComp", "card clicked", {
|
||||
uuid: this.card_uuid,
|
||||
type: this.card_type
|
||||
});
|
||||
}
|
||||
|
||||
// ======================== UI 渲染 ========================
|
||||
|
||||
/**
|
||||
* 根据当前 cardData 渲染卡面。
|
||||
*
|
||||
* 渲染逻辑根据卡牌类型分三路:
|
||||
* 1. 英雄卡:显示英雄名、等级、idle 动画。
|
||||
* 2. 技能卡:显示技能名、静态图标。
|
||||
* 3. 特殊卡:显示卡名、静态图标。
|
||||
*
|
||||
* 同时根据 pool_lv 切换背景底框,根据 kind 切换种类标识。
|
||||
*/
|
||||
private applyCardUI() {
|
||||
if (!this.cardData) {
|
||||
this.applyEmptyUI();
|
||||
return;
|
||||
}
|
||||
|
||||
this.iconVisualToken += 1;
|
||||
if (this.opacityComp) this.opacityComp.opacity = 255;
|
||||
|
||||
mLogger.log(this.debugMode, "CardLiteComp", "applyCardUI nodes", {
|
||||
uuid: this.card_uuid,
|
||||
hasName: !!this.name_node,
|
||||
hasIcon: !!this.icon_node,
|
||||
hasCost: !!this.cost_node,
|
||||
hasBG: !!this.BG_node,
|
||||
hasPoolLv: !!this.pool_lv_node,
|
||||
hasLvl: !!this.lvl_node,
|
||||
bgChildren: this.BG_node?.children.map(c => c.name) || [],
|
||||
poolChildren: this.pool_lv_node?.children.map(c => c.name) || [],
|
||||
nodeActive: this.node.active,
|
||||
nodeScale: this.node.scale.toString(),
|
||||
nodePos: this.node.position.toString(),
|
||||
parentName: this.node.parent?.name || "none",
|
||||
});
|
||||
|
||||
const kindName = CKind[this.cardData.kind];
|
||||
|
||||
if (this.BG_node) {
|
||||
this.BG_node.children.forEach(child => {
|
||||
child.active = (child.name === kindName);
|
||||
});
|
||||
}
|
||||
|
||||
const cardLvStr = `lv${this.cardData.pool_lv}`;
|
||||
if (this.pool_lv_node) {
|
||||
this.pool_lv_node.children.forEach(child => {
|
||||
child.active = (child.name === cardLvStr);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.card_type === CardType.Hero) {
|
||||
const hero = HeroInfo[this.card_uuid];
|
||||
const heroLv = Math.max(1, this.cardData.hero_lv ?? hero?.lv ?? 1);
|
||||
this.setLabel(this.name_node, `${hero?.name || ""}`);
|
||||
if (this.lvl_node) {
|
||||
this.lvl_node.string = `Lv.${heroLv}`;
|
||||
this.lvl_node.color = getLvColor(heroLv);
|
||||
this.lvl_node.node.active = true;
|
||||
}
|
||||
} else if (this.card_type === CardType.Skill) {
|
||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
||||
const skill = SkillSet[this.card_uuid];
|
||||
const skillCard = CardPoolList.find(c => c.uuid === this.card_uuid);
|
||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
||||
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
|
||||
} else {
|
||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
||||
const specialCard = this.card_type === CardType.SpecialUpgrade
|
||||
? SpecialUpgradeCardList[this.card_uuid]
|
||||
: SpecialRefreshCardList[this.card_uuid];
|
||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
||||
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
|
||||
}
|
||||
|
||||
if (this.cost_node) {
|
||||
this.cost_node.active = true;
|
||||
const numNode = this.cost_node.getChildByName("num");
|
||||
if (numNode) {
|
||||
this.setLabel(numNode, `${this.card_cost}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.icon_node) {
|
||||
const iconNode = this.icon_node;
|
||||
if (this.card_type === CardType.Hero) {
|
||||
iconNode.setScale(new Vec3(-1.5, 1.5, 1));
|
||||
this.updateHeroAnimation(iconNode, this.card_uuid, this.iconVisualToken);
|
||||
return;
|
||||
}
|
||||
iconNode.setScale(new Vec3(1, 1, 1));
|
||||
this.clearIconAnimation(iconNode);
|
||||
const iconId = this.resolveCardIconId(this.card_type, this.card_uuid);
|
||||
if (iconId) {
|
||||
this.updateIcon(iconNode, iconId);
|
||||
} else {
|
||||
const sprite = iconNode?.getComponent(Sprite) || iconNode?.getComponentInChildren(Sprite);
|
||||
if (sprite) sprite.spriteFrame = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染空槽状态:清空名称、费用、等级、图标等。
|
||||
*/
|
||||
private applyEmptyUI() {
|
||||
this.iconVisualToken += 1;
|
||||
this.setLabel(this.name_node, "");
|
||||
if (this.cost_node) {
|
||||
const numNode = this.cost_node.getChildByName("num");
|
||||
if (numNode) {
|
||||
this.setLabel(numNode, "");
|
||||
}
|
||||
this.cost_node.active = false;
|
||||
}
|
||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
||||
if (this.BG_node) {
|
||||
this.BG_node.children.forEach(child => child.active = false);
|
||||
}
|
||||
if (this.pool_lv_node) {
|
||||
this.pool_lv_node.children.forEach(child => child.active = false);
|
||||
}
|
||||
if (this.icon_node) {
|
||||
(this.icon_node as Node).setScale(new Vec3(1, 1, 1));
|
||||
this.clearIconAnimation(this.icon_node as Node);
|
||||
const sprite = this.icon_node?.getComponent(Sprite) || this.icon_node?.getComponentInChildren(Sprite);
|
||||
if (sprite) sprite.spriteFrame = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 动画 ========================
|
||||
|
||||
/**
|
||||
* 入场动画:先缩小再弹大再回归正常比例。
|
||||
*/
|
||||
playRefreshAnim() {
|
||||
Tween.stopAllByTarget(this.node);
|
||||
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();
|
||||
}
|
||||
|
||||
// ======================== 工具方法 ========================
|
||||
|
||||
/**
|
||||
* 安全设置文本,兼容节点上或子节点上的 Label。
|
||||
*/
|
||||
private setLabel(node: Node | null, value: string) {
|
||||
if (!node) return;
|
||||
const label = node.getComponent(Label) || node.getComponentInChildren(Label);
|
||||
if (label) label.string = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新图标:从全局缓存图集中获取对应帧。
|
||||
*/
|
||||
private updateIcon(node: Node, iconId: string) {
|
||||
if (!node || !iconId) return;
|
||||
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
|
||||
if (!sprite) return;
|
||||
if (smc.uiconsAtlas) {
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
|
||||
sprite.spriteFrame = frame || null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据卡牌类型和 UUID 解析出图标 ID(在 SpriteAtlas 中的帧名)。
|
||||
*/
|
||||
private resolveCardIconId(type: CardType, uuid: number): string {
|
||||
if (type === CardType.Skill) {
|
||||
return SkillSet[uuid]?.icon || `${uuid}`;
|
||||
}
|
||||
if (type === CardType.Hero) {
|
||||
return HeroInfo[uuid]?.icon || `${uuid}`;
|
||||
}
|
||||
return `${uuid}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为英雄卡图标加载并播放 idle 动画。
|
||||
* 使用 token 做竞态保护,确保异步回调时不会覆盖已更新的图标。
|
||||
*/
|
||||
private updateHeroAnimation(node: Node, uuid: number, token: number) {
|
||||
const sprite = node?.getComponent(Sprite) || node?.getComponentInChildren(Sprite);
|
||||
if (sprite) sprite.spriteFrame = null;
|
||||
const hero = HeroInfo[uuid];
|
||||
if (!hero) return;
|
||||
const anim = node.getComponent(Animation) || node.addComponent(Animation);
|
||||
this.clearAnimationClips(anim);
|
||||
const path = `game/heros/hero/${hero.path}/idle`;
|
||||
resources.load(path, AnimationClip, (err, clip) => {
|
||||
if (err || !clip) {
|
||||
mLogger.log(this.debugMode, "CardLiteComp", `load hero animation failed ${uuid}`, err);
|
||||
return;
|
||||
}
|
||||
if (token !== this.iconVisualToken || !this.cardData || this.card_type !== CardType.Hero || this.card_uuid !== uuid) {
|
||||
return;
|
||||
}
|
||||
this.clearAnimationClips(anim);
|
||||
anim.addClip(clip);
|
||||
anim.play("idle");
|
||||
});
|
||||
}
|
||||
|
||||
/** 清除图标节点上的动画(停止播放并移除所有 clip) */
|
||||
private clearIconAnimation(node: Node) {
|
||||
const anim = node?.getComponent(Animation);
|
||||
if (!anim) return;
|
||||
anim.stop();
|
||||
this.clearAnimationClips(anim);
|
||||
}
|
||||
|
||||
/** 移除 Animation 组件上的全部 AnimationClip */
|
||||
private clearAnimationClips(anim: Animation) {
|
||||
const clips = (anim as any).clips as AnimationClip[] | undefined;
|
||||
if (!clips || clips.length === 0) return;
|
||||
[...clips].forEach(clip => anim.removeClip(clip, true));
|
||||
}
|
||||
|
||||
// ======================== 生命周期钩子 ========================
|
||||
|
||||
/** ECS 组件移除时的释放钩子:销毁节点 */
|
||||
reset() {
|
||||
|
||||
@@ -2,83 +2,275 @@
|
||||
* @file HerosListComp.ts
|
||||
* @description 英雄图鉴弹出页面(UI 视图层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 以卡片列表形式展示所有可用英雄(使用 CardLiteComp 预制体)。
|
||||
* 2. 点击卡片选中英雄,右侧详情面板显示 idle 动画、名称、AP、HP、CD、技能描述。
|
||||
* 3. 支持卡池等级筛选(全部 / Lv1 / Lv2 / Lv3)。
|
||||
*
|
||||
* 关键设计:
|
||||
* - cards_node 为卡片容器,通过 instantiate(card_lite_prefab) 动态生成卡片。
|
||||
* - 选中状态通过 selectNode 高亮管理,同一时间只有一张卡片高亮。
|
||||
* - hero_icon 使用 Animation + iconVisualToken 机制防止异步加载竞态。
|
||||
*
|
||||
* 依赖:
|
||||
* - HeroInfo / HeroList(heroSet)—— 英雄静态配置与全量 UUID 列表
|
||||
* - CardLiteComp —— 轻量卡片组件
|
||||
* - buildSkillDesc(HeroSkillDesc)—— 技能描述生成器
|
||||
*/
|
||||
import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources, tween, Vec3, Widget, Prefab } from "cc";
|
||||
import { _decorator, Animation, AnimationClip, Label, Node, Prefab, Sprite, Widget, instantiate, resources } 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 { HeroInfo, HeroList } from "../common/config/heroSet";
|
||||
import { IType, SkillSet } from "../common/config/SkillSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { HeroInfo, HeroList } from "../common/config/heroSet";
|
||||
import { buildSkillDesc } from "../common/config/HeroSkillDesc";
|
||||
import { CardLiteComp } from "./CardLiteComp";
|
||||
|
||||
const {property, ccclass } = _decorator;
|
||||
const { property, ccclass } = _decorator;
|
||||
|
||||
/**
|
||||
* HerosListComp —— 英雄图鉴轮播视图组件
|
||||
*
|
||||
* 在任务主页展示所有可用英雄,玩家可点击切换当前选中英雄的名称、AP、HP、CD、技能信息
|
||||
*/
|
||||
@ccclass('HerosListComp')
|
||||
@ecs.register('HerosListComp', false)
|
||||
export class HerosListComp extends CCComp {
|
||||
// ======================== 编辑器绑定节点 ========================
|
||||
|
||||
/** 当前英雄 idle 图标节点 */
|
||||
@property(Node)
|
||||
hero_icon=null!
|
||||
|
||||
/** 攻击力标签节点 */
|
||||
@property(Node)
|
||||
ap_node=null!
|
||||
/** 生命值标签节点 */
|
||||
@property(Node)
|
||||
hp_node=null!
|
||||
/** 冷却时间标签节点 */
|
||||
@property(Node)
|
||||
cd_node=null!
|
||||
/** 技能信息容器节点(包含 Line1~Line5 子节点) */
|
||||
@property(Node)
|
||||
info_node=null!
|
||||
/** 英雄名称标签节点 */
|
||||
@property(Node)
|
||||
name_node=null!
|
||||
hero_icon = null!
|
||||
|
||||
/** 英雄图鉴卡容器节点 */
|
||||
@property(Node)
|
||||
cards_node=null!
|
||||
ap_node = null!
|
||||
|
||||
@property(Node)
|
||||
hp_node = null!
|
||||
|
||||
@property(Node)
|
||||
cd_node = null!
|
||||
|
||||
@property(Node)
|
||||
info_node = null!
|
||||
|
||||
@property(Node)
|
||||
name_node = null!
|
||||
|
||||
@property(Node)
|
||||
cards_node = null!
|
||||
|
||||
/** 英雄图鉴卡预制体 */
|
||||
@property(Prefab)
|
||||
card_lite_prefab=null!
|
||||
card_lite_prefab = null!
|
||||
|
||||
@property(Node)
|
||||
lv_node = null!
|
||||
|
||||
@property(Node)
|
||||
type_node = null!
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 当前选中英雄在 HeroList 中的索引 */
|
||||
huuid:number=null!
|
||||
/** 当前选中英雄在 HeroList 数组中的下标 */
|
||||
/** 调试日志开关 */
|
||||
debugMode: boolean = false;
|
||||
huuid: number = 0
|
||||
private iconVisualToken: number = 0
|
||||
private selectNode: Node | null = null
|
||||
debugMode: boolean = false
|
||||
|
||||
onLoad() {
|
||||
|
||||
}
|
||||
/** 预留:弹窗打开时接收参数 */
|
||||
onAdded(args: any) {
|
||||
|
||||
|
||||
}
|
||||
/** 关闭英雄图鉴弹窗 */
|
||||
closeHeros(){
|
||||
|
||||
start() {
|
||||
this.initCardList()
|
||||
if (HeroList.length > 0) {
|
||||
this.onCardSelect(HeroList[0])
|
||||
}
|
||||
}
|
||||
|
||||
closeHeros() {
|
||||
oops.gui.remove(UIID.Heros)
|
||||
}
|
||||
start() {
|
||||
|
||||
|
||||
// ======================== 卡片列表 ========================
|
||||
|
||||
private initCardList() {
|
||||
mLogger.log(true, "HerosListComp", "initCardList start", {
|
||||
hasCardsNode: !!this.cards_node,
|
||||
hasPrefab: !!this.card_lite_prefab,
|
||||
heroListLen: HeroList.length,
|
||||
})
|
||||
|
||||
if (!this.cards_node || !this.card_lite_prefab) return
|
||||
|
||||
mLogger.log(true, "HerosListComp", "cards_node info", {
|
||||
name: this.cards_node.name,
|
||||
active: this.cards_node.active,
|
||||
childrenCount: this.cards_node.children.length,
|
||||
pos: this.cards_node.position.toString(),
|
||||
scale: this.cards_node.scale.toString(),
|
||||
parentName: this.cards_node.parent?.name || "none",
|
||||
})
|
||||
|
||||
this.cards_node.removeAllChildren()
|
||||
|
||||
for (const uuid of HeroList) {
|
||||
const hero = HeroInfo[uuid]
|
||||
if (!hero) continue
|
||||
|
||||
const cardNode = instantiate(this.card_lite_prefab)
|
||||
cardNode.name = `card_${uuid}`
|
||||
|
||||
mLogger.log(true, "HerosListComp", `card instantiated ${uuid}`, {
|
||||
cardActive: cardNode.active,
|
||||
cardPos: cardNode.position.toString(),
|
||||
cardScale: cardNode.scale.toString(),
|
||||
cardChildren: cardNode.children.map(c => c.name),
|
||||
cardWidth: cardNode.getComponent("cc.UITransform")?.width,
|
||||
cardHeight: cardNode.getComponent("cc.UITransform")?.height,
|
||||
})
|
||||
|
||||
const comp = cardNode.getComponent(CardLiteComp) || cardNode.addComponent(CardLiteComp)
|
||||
comp.setHeroByUuid(uuid, hero.pool_lv ?? 1)
|
||||
comp.onClickCallback = (cardComp: CardLiteComp) => {
|
||||
this.onCardSelect(uuid)
|
||||
this.highlightCard(cardNode)
|
||||
}
|
||||
|
||||
this.cards_node.addChild(cardNode)
|
||||
}
|
||||
|
||||
mLogger.log(true, "HerosListComp", "initCardList done", {
|
||||
totalChildren: this.cards_node.children.length,
|
||||
})
|
||||
}
|
||||
|
||||
private highlightCard(cardNode: Node) {
|
||||
if (this.selectNode) {
|
||||
const oldWidget = this.selectNode.getComponent(Widget)
|
||||
if (oldWidget) oldWidget.enabled = false
|
||||
this.selectNode.setScale(1, 1, 1)
|
||||
}
|
||||
this.selectNode = cardNode
|
||||
cardNode.setScale(1.1, 1.1, 1)
|
||||
}
|
||||
|
||||
private onCardSelect(uuid: number) {
|
||||
this.huuid = uuid
|
||||
this.updateHeroDetail(uuid)
|
||||
}
|
||||
|
||||
// ======================== 详情面板 ========================
|
||||
|
||||
private updateHeroDetail(uuid: number) {
|
||||
const hero = HeroInfo[uuid]
|
||||
if (!hero) return
|
||||
|
||||
const heroLv = Math.max(1, Math.floor(hero.lv ?? 1))
|
||||
const suffix = heroLv >= 2 ? "★".repeat(heroLv - 1) : ""
|
||||
this.setLabelText(this.name_node, `${suffix}${hero.name || ""}${suffix}`)
|
||||
this.setLabelText(this.ap_node, `${(hero.ap ?? 0) * heroLv}`)
|
||||
this.setLabelText(this.hp_node, `${(hero.hp ?? 0) * heroLv}`)
|
||||
|
||||
this.updateCdDisplay(hero)
|
||||
|
||||
if (this.info_node) {
|
||||
const desc = buildSkillDesc(hero)
|
||||
const infoLabel = this.info_node.getChildByName("info")?.getComponent(Label)
|
||||
|| this.info_node.getComponent(Label)
|
||||
|| this.info_node.getComponentInChildren(Label)
|
||||
if (infoLabel) infoLabel.string = desc || hero.info || ""
|
||||
}
|
||||
|
||||
this.updateLvDisplay(hero)
|
||||
this.updateTypeDisplay(hero)
|
||||
this.updateHeroAnimation(uuid)
|
||||
}
|
||||
|
||||
private updateCdDisplay(hero: typeof HeroInfo[number]) {
|
||||
if (!this.cd_node) return
|
||||
const skillKeys = Object.keys(hero.skills)
|
||||
if (skillKeys.length === 0) return
|
||||
const firstSkill = hero.skills[Number(skillKeys[0])]
|
||||
if (firstSkill) {
|
||||
this.setLabelText(this.cd_node, `${firstSkill.cd}s`)
|
||||
}
|
||||
}
|
||||
|
||||
private updateLvDisplay(hero: typeof HeroInfo[number]) {
|
||||
if (!this.lv_node) return
|
||||
const cardLvStr = `lv${hero.pool_lv ?? 1}`
|
||||
this.lv_node.active = true
|
||||
this.lv_node.children.forEach(child => {
|
||||
if (child.name === "light") {
|
||||
child.active = false
|
||||
} else if (child.name === "bg") {
|
||||
child.active = true
|
||||
} else {
|
||||
child.active = (child.name === cardLvStr)
|
||||
}
|
||||
})
|
||||
const widget = this.lv_node.getComponent(Widget)
|
||||
if (widget) widget.updateAlignment()
|
||||
this.lv_node.children.forEach(child => {
|
||||
const childWidget = child.getComponent(Widget)
|
||||
if (childWidget) childWidget.updateAlignment()
|
||||
})
|
||||
}
|
||||
|
||||
private updateTypeDisplay(hero: typeof HeroInfo[number]) {
|
||||
if (!this.type_node) return
|
||||
this.type_node.active = true
|
||||
const typeStr = `${hero.type ?? 0}`
|
||||
this.type_node.children.forEach(child => {
|
||||
child.active = (child.name === typeStr)
|
||||
})
|
||||
}
|
||||
|
||||
// ======================== 英雄动画 ========================
|
||||
|
||||
private updateHeroAnimation(uuid: number) {
|
||||
if (!this.hero_icon) return
|
||||
const hero = HeroInfo[uuid]
|
||||
if (!hero) return
|
||||
|
||||
const sprite = this.hero_icon.getComponent(Sprite) || this.hero_icon.getComponentInChildren(Sprite)
|
||||
if (sprite) sprite.spriteFrame = null
|
||||
|
||||
const anim = this.hero_icon.getComponent(Animation) || this.hero_icon.addComponent(Animation)
|
||||
this.clearAnimationClips(anim)
|
||||
|
||||
this.iconVisualToken += 1
|
||||
const token = this.iconVisualToken
|
||||
const path = `game/heros/hero/${hero.path}/idle`
|
||||
|
||||
resources.load(path, AnimationClip, (err, clip) => {
|
||||
if (err || !clip) {
|
||||
mLogger.log(this.debugMode, "HerosListComp", `load hero animation failed ${uuid}`, err)
|
||||
return
|
||||
}
|
||||
if (token !== this.iconVisualToken) return
|
||||
this.clearAnimationClips(anim)
|
||||
anim.addClip(clip)
|
||||
anim.play("idle")
|
||||
})
|
||||
}
|
||||
|
||||
private clearAnimationClips(anim: Animation) {
|
||||
const clips = anim.clips
|
||||
if (clips && clips.length > 0) {
|
||||
for (let i = clips.length - 1; i >= 0; i--) {
|
||||
const clip = clips[i]
|
||||
if (clip) anim.removeClip(clip, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== UI 工具 ========================
|
||||
|
||||
private setLabelText(node: Node, text: string) {
|
||||
if (!node) return
|
||||
const label = node.getComponent(Label) || node.getComponentInChildren(Label)
|
||||
if (label) {
|
||||
label.string = text
|
||||
}
|
||||
}
|
||||
|
||||
/** ECS 组件移除时销毁节点 */
|
||||
reset() {
|
||||
this.node.destroy();
|
||||
this.node.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user