12 Commits

Author SHA1 Message Date
walkpan
5520473e71 fix: 调整友方技能特效位置和动画轨迹
- 修正buff.prefab的缩放和透明度,提升视觉效果
- 调整sbox.prefab中多个UI元素的位置和对齐方式
- 简化友方技能特效动画为直线运动,移除贝塞尔曲线计算
- 为技能特效添加高度偏移,避免与角色模型重叠
2026-04-06 23:13:40 +08:00
walkpan
62b7b9783a feat(map): 新增任务技能面板并优化技能触发逻辑
- 新增 mskills.prefab 作为任务技能容器
- 将 MissSkillsComp 挂载到场景实体层,移除 MissionCardComp 中的引用
- 优化 SkillBoxComp 触发坐标计算,改为基于父节点位置
- 调整技能盒尺寸并添加等级标签显示
- 修复战斗开始时技能触发计时器重置逻辑
2026-04-06 22:09:43 +08:00
walkpan
2010e2adc5 refactor(card): 合并技能卡配置到统一卡片池并更新属性名
- 移除独立的 SkillCardList 配置,将技能卡属性直接集成到 CardPoolList
- 更新 SkillBoxComp 和 CardComp 中技能卡配置的获取方式,改为从 CardPoolList 查找
- 统一技能卡属性命名(如 is_inst 替代 is_instant),提升配置一致性
2026-04-06 22:03:52 +08:00
walkpan
fa629d71d9 feat(技能): 将技能卡释放逻辑移至独立组件并添加UI显示
- 新增 MissSkillsComp 组件,用于管理场景中释放的技能卡
- 将技能卡释放监听从 MissionHeroComp 移至 MissSkillsComp
- 新增 SkillBoxComp 组件,负责单个技能卡的表现和触发逻辑
- 在 role_controller.prefab 中添加 miss_skill_node 节点引用
- 技能卡现在会在场景中显示图标和剩余回合信息
- 支持即时技能和持续多回合技能的不同触发机制
2026-04-06 19:18:44 +08:00
walkpan
cc51d1fb5e refactor(map): 移除 CardUseComp 并将逻辑内联至 CardComp
简化卡片使用逻辑,删除独立的 CardUseComp 组件,将其 onCardUsed 方法中的事件分发逻辑直接移至 CardComp 的 executeCardEffectEntry 方法中。这减少了组件间的依赖和查找开销,使卡片使用流程更内聚。
2026-04-06 17:40:55 +08:00
walkpan
bb709ca905 feat(技能系统): 添加技能盒组件和预制体
- 新增 SkillBoxComp 和 MissSkillsComp 组件,用于技能盒视图逻辑
- 创建 sbox.prefab 技能盒预制体,包含完整的 UI 结构和精灵组件
- 为组件添加元数据文件,支持 ECS 框架集成
2026-04-06 17:32:25 +08:00
walkpan
d9aff08635 chore: 清理设计文档和调整GUI资源结构
- 删除过时的属性变更优化方案设计文档
- 删除肉鸽怪物刷新机制设计方案文档
- 移除旧的GUI资源文件
- 添加新的GUI元素预制体
- 更新GUI资源组织结构
2026-04-06 17:12:45 +08:00
walkpan
197ecefe80 feat(技能): 为友方技能添加飞行特效并优化目标选择逻辑
- 新增友方技能释放时的飞行特效,包括抛物线动画和缩放旋转效果
- 重构 applyFriendlySkillEffects 方法,将特效播放与实际效果应用分离
- 调整 buff.prefab 的缩放比例从 0.2 增大到 0.5 以适配新特效
- 优化友方技能目标选择逻辑,确保特效从施法位置正确飞向目标
2026-04-06 15:35:11 +08:00
walkpan
310d4f0eb0 feat: 新增buff动画资源并调整英雄技能冷却时间
- 新增buff动画资源文件及对应的prefab
- 修复dun_jin.anim中的动画名称错误
- 调整多个英雄的普通攻击和技能冷却时间以平衡游戏性
2026-04-06 14:09:50 +08:00
walkpan
ee380ff668 feat(英雄): 动态计算技能卡牌的虚拟攻击力
- 根据卡牌等级给予基础成长,使用与英雄升级相同的公式
- 获取场上最高攻击力英雄的数值,确保后期治疗和增益效果足够
- 移除固定的基准攻击力,使技能数值更适应实际战斗场景
2026-04-06 10:05:10 +08:00
walkpan
e3676c2775 feat(config): 更新技能卡牌配置以适配备战期玩法
- 将攻击性技能卡替换为增益/辅助类技能
- 新增单体/群体攻击、生命、全能及护盾技能
- 调整卡牌权重和等级池分布,确保备战期平衡性
2026-04-06 09:44:54 +08:00
walkpan
93e5bb9fcd fix(gui): 调整通知和角色控制器的UI布局与样式
- 更新通知弹窗的宽度、位置和背景图,使其更美观
- 调整角色控制器按钮的位置、尺寸和边距,优化操作体验
- 修复角色控制器中一个组件的激活状态
2026-04-06 09:37:37 +08:00
28 changed files with 7352 additions and 4402 deletions

View File

@@ -105,7 +105,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"y": -4.773,
"z": 0
},
"_lrot": {
@@ -145,7 +145,7 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 680,
"width": 760,
"height": 80
},
"_anchorPoint": {
@@ -182,7 +182,7 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "6165ffc9-a838-4a33-b569-bdbaaab0e6b4@7f50d",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@eb418",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
@@ -198,7 +198,7 @@
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "6165ffc9-a838-4a33-b569-bdbaaab0e6b4",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
@@ -221,8 +221,8 @@
},
"_alignFlags": 40,
"_target": null,
"_left": 10,
"_right": 10,
"_left": -30,
"_right": -30,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,

View File

@@ -28,17 +28,17 @@
"_active": true,
"_components": [
{
"__id__": 120
"__id__": 136
},
{
"__id__": 122
"__id__": 138
},
{
"__id__": 124
"__id__": 140
}
],
"_prefab": {
"__id__": 126
"__id__": 142
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -197,17 +197,17 @@
"_active": true,
"_components": [
{
"__id__": 113
"__id__": 129
},
{
"__id__": 115
"__id__": 131
},
{
"__id__": 117
"__id__": 133
}
],
"_prefab": {
"__id__": 119
"__id__": 135
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -257,17 +257,17 @@
"__id__": 70
},
{
"__id__": 104
"__id__": 120
}
],
"_active": true,
"_components": [
{
"__id__": 110
"__id__": 126
}
],
"_prefab": {
"__id__": 112
"__id__": 128
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -1100,6 +1100,8 @@
"__id__": 0
},
"fileId": "afC1QRFbdIJrdpKczcbi3p",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -1719,19 +1721,22 @@
},
{
"__id__": 95
},
{
"__id__": 99
}
],
"_active": true,
"_components": [
{
"__id__": 99
"__id__": 115
},
{
"__id__": 101
"__id__": 117
}
],
"_prefab": {
"__id__": 103
"__id__": 119
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2378,6 +2383,212 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_objFlags": 0,
"_parent": {
"__id__": 70
},
"_prefab": {
"__id__": 100
},
"__editorExtras__": {}
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 99
},
"asset": {
"__uuid__": "0f2aeee0-d590-4c36-9f20-a93b058d5b91",
"__expectedType__": "cc.Prefab"
},
"fileId": "5622mxbS1PNqMFP0FH5Mir",
"instance": {
"__id__": 101
},
"targetOverrides": null
},
{
"__type__": "cc.PrefabInstance",
"fileId": "f99PWIj1JPJqRhcUsVZf3M",
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 102
},
{
"__id__": 104
},
{
"__id__": 105
},
{
"__id__": 106
},
{
"__id__": 107
},
{
"__id__": 109
},
{
"__id__": 110
},
{
"__id__": 111
},
{
"__id__": 112
},
{
"__id__": 114
}
],
"removedComponents": []
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 103
},
"propertyPath": [
"_name"
],
"value": "mskills"
},
{
"__type__": "cc.TargetInfo",
"localID": [
"5622mxbS1PNqMFP0FH5Mir"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 103
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": -180,
"y": 314.571,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 103
},
"propertyPath": [
"_lrot"
],
"value": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 103
},
"propertyPath": [
"_euler"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 108
},
"propertyPath": [
"_alignFlags"
],
"value": 10
},
{
"__type__": "cc.TargetInfo",
"localID": [
"baKsIzXktAyqqNBZKVQXO+"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 108
},
"propertyPath": [
"_left"
],
"value": -360
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 108
},
"propertyPath": [
"_right"
],
"value": -130
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 108
},
"propertyPath": [
"_originalWidth"
],
"value": 360
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 113
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 360,
"height": 100
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"4dNsDtN3ZGiZ+om9xELLFQ"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 108
},
"propertyPath": [
"_verticalCenter"
],
"value": 264.571
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -2388,7 +2599,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 100
"__id__": 116
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2416,7 +2627,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 102
"__id__": 118
},
"_id": ""
},
@@ -2449,14 +2660,14 @@
"_active": true,
"_components": [
{
"__id__": 105
"__id__": 121
},
{
"__id__": 107
"__id__": 123
}
],
"_prefab": {
"__id__": 109
"__id__": 125
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2493,11 +2704,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 104
"__id__": 120
},
"_enabled": true,
"__prefab": {
"__id__": 106
"__id__": 122
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2521,11 +2732,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 104
"__id__": 120
},
"_enabled": true,
"__prefab": {
"__id__": 108
"__id__": 124
},
"light": null,
"_id": ""
@@ -2557,7 +2768,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 111
"__id__": 127
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2598,7 +2809,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 114
"__id__": 130
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2626,7 +2837,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 116
"__id__": 132
},
"camera": {
"__id__": 3
@@ -2641,10 +2852,10 @@
"__id__": 66
},
"entityLayer": {
"__id__": 101
"__id__": 117
},
"SkillLayer": {
"__id__": 107
"__id__": 123
},
"isFollowPlayer": true,
"_id": ""
@@ -2663,7 +2874,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 118
"__id__": 134
},
"_id": ""
},
@@ -2694,7 +2905,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 121
"__id__": 137
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2722,7 +2933,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 123
"__id__": 139
},
"_cameraComponent": {
"__id__": 3
@@ -2744,7 +2955,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 125
"__id__": 141
},
"_alignFlags": 45,
"_target": null,
@@ -2780,6 +2991,11 @@
},
"fileId": "32qENLKwZHV5S2IyZfpNx2",
"instance": null,
"targetOverrides": null
"targetOverrides": null,
"nestedPrefabInstanceRoots": [
{
"__id__": 99
}
]
}
]

View File

@@ -0,0 +1,265 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "buff",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 12,
"speed": 1,
"wrapMode": 2,
"enableTrsBlending": false,
"_duration": 0.9166666666666666,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
}
],
"_exoticAnimation": null,
"_events": [],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 12
},
"_auxiliaryCurveEntries": []
},
{
"__type__": "cc.animation.VectorTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 2
},
"proxy": null
},
"_channels": [
{
"__id__": 4
},
{
"__id__": 6
},
{
"__id__": 8
},
{
"__id__": 10
}
],
"_nComponents": 3
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 3
},
"eulerAngles"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "skill"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 5
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.6666666865348816,
0.9166666865348816
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 7
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.6666666865348816,
0.9166666865348816
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 9
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.6666666865348816,
0.9166666865348816
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -270,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -360,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"tangentMode": 0
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 11
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.AnimationClipAdditiveSettings",
"enabled": false,
"refClip": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "2.0.3",
"importer": "animation-clip",
"imported": true,
"uuid": "a01d304c-f707-471f-a9bf-20a4d6ed5591",
"files": [
".cconb"
],
"subMetas": {},
"userData": {
"name": "buff"
}
}

View File

@@ -1,7 +1,7 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "up_ap",
"_name": "dun_jin",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []

View File

@@ -0,0 +1,274 @@
[
{
"__type__": "cc.Prefab",
"_name": "buff",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "buff",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
}
],
"_prefab": {
"__id__": 12
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "skill",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 10,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.7,
"y": 0.7,
"z": 1
},
"_mobility": 0,
"_layer": 1,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 183,
"height": 183
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "b4sNQPJWFKha7x75SWXmRj"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_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@ce362",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 1,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0.2,
"_fillRange": 1,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "9a+65VIghBm4HQxuHPQ/mg"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "7a5LvbdlxEc6FKa7cun2oB",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"_contentSize": {
"__type__": "cc.Size",
"width": 80,
"height": 110
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "a01d304c-f707-471f-a9bf-20a4d6ed5591",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "a01d304c-f707-471f-a9bf-20a4d6ed5591",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "c6LOemuvJKyYCqlF/yUJcr"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -2,12 +2,12 @@
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "4489091b-29ab-4906-972b-978bd4279a8e",
"uuid": "8f2f502b-e1de-4d35-9ebc-21d6a8fecc0a",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "hui"
"syncNodeName": "buff"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[
{
"__type__": "cc.Prefab",
"_name": "hui",
"_name": "mskills",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
@@ -13,7 +13,7 @@
},
{
"__type__": "cc.Node",
"_name": "hui",
"_name": "mskills",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
@@ -28,15 +28,18 @@
},
{
"__id__": 6
},
{
"__id__": 8
}
],
"_prefab": {
"__id__": 8
"__id__": 10
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 14.007,
"x": -180,
"y": 990,
"z": 0
},
"_lrot": {
@@ -48,12 +51,12 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": -1.2,
"y": 1.2,
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_layer": 1,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
@@ -76,22 +79,22 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 128,
"height": 128
"width": 360,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.25
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "cewibQgK1ALbFhMuuspMmN"
"fileId": "4dNsDtN3ZGiZ+om9xELLFQ"
},
{
"__type__": "cc.Sprite",
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
@@ -102,38 +105,32 @@
"__prefab": {
"__id__": 5
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": null,
"_type": 0,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_alignFlags": 10,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 350,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ed71tIQFtJtbSwrQYOmOPP"
"fileId": "baKsIzXktAyqqNBZKVQXO+"
},
{
"__type__": "cc.Animation",
"__type__": "68387wnH45AVo6Nco+pXtG5",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
@@ -144,14 +141,53 @@
"__prefab": {
"__id__": 7
},
"playOnLoad": true,
"_clips": [],
"_defaultClip": null,
"skill_box": {
"__uuid__": "d19cde30-f5d0-47de-a0d5-3a272b696343",
"__expectedType__": "cc.Prefab"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "3dBJqUbmFPGIhxjbVKe/vR"
"fileId": "d4AyZdLXNB07iSaFvEU6BB"
},
{
"__type__": "cc.Layout",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"_resizeMode": 0,
"_layoutType": 1,
"_cellSize": {
"__type__": "cc.Size",
"width": 40,
"height": 40
},
"_startAxis": 0,
"_paddingLeft": 10,
"_paddingRight": 0,
"_paddingTop": 0,
"_paddingBottom": 0,
"_spacingX": 10,
"_spacingY": 0,
"_verticalDirection": 1,
"_horizontalDirection": 0,
"_constraint": 0,
"_constraintNum": 2,
"_affectedByScale": false,
"_isAlign": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "786tQWlIxKHLEpV0oUK9dj"
},
{
"__type__": "cc.PrefabInfo",
@@ -161,8 +197,7 @@
"asset": {
"__id__": 0
},
"fileId": "9anvHdhj5D1opINsPLBLgs",
"instance": null,
"fileId": "5622mxbS1PNqMFP0FH5Mir",
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "0f2aeee0-d590-4c36-9f20-a93b058d5b91",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "mskills"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "d19cde30-f5d0-47de-a0d5-3a272b696343",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "sbox"
}
}

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 282 KiB

View File

@@ -36,6 +36,14 @@ export interface CardConfig {
pool_lv: CardLV
hero_lv?: number
card_lv?:number
// 技能卡扩展属性
name?: string // 卡牌名称
info?: string // 卡牌描述信息
is_inst?: boolean // 是否即时起效
t_times?: number // 触发次数
t_inv?: number // 触发间隔(秒)
d_rds?: number // 持续回合数
}
export const CardsUpSet: Record<number, number> = {
1: 50,
@@ -80,12 +88,15 @@ export const CardPoolList: CardConfig[] = [
{ uuid: 5105, type: CardType.Hero, cost: 3, weight: 25, pool_lv: 5, kind: CKind.Hero, hero_lv: 1 },
{ uuid: 5304, type: CardType.Hero, cost: 3, weight: 25, pool_lv: 5, kind: CKind.Hero, hero_lv: 1 },
// 技能卡牌
{ uuid: 6005, type: CardType.Skill, cost: 2, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1 },
{ uuid: 6104, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1 },
{ uuid: 6205, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1 },
{ uuid: 6304, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1 }, // 群体治疗
{ uuid: 6406, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 4, kind: CKind.Skill, card_lv: 1 }, // 群体全能
// 技能卡牌 (以增益/辅助为主,因为在备战期没有敌人)
{ uuid: 6401, type: CardType.Skill, cost: 2, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: "单体攻击", info: "随机1个友方+5攻击", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 6402, type: CardType.Skill, cost: 2, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: "单体生命", info: "随机1个友方+20最大生命值", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 6403, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: "单体全能", info: "随机1个友方+2攻击,+10最大生命值", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 6404, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1, name: "群体攻击", info: "随机3个友方+2攻击", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 6405, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1, name: "群体生命", info: "随机3个友方+10最大生命值", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 6406, type: CardType.Skill, cost: 5, weight: 20, pool_lv: 4, kind: CKind.Skill, card_lv: 1, name: "群体全能", info: "为随机3个友方单位增加攻击力和生命上限", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 6304, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: "神圣治疗", info: "恢复场上随机3个友方单位的生命值", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 6305, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 4, kind: CKind.Skill, card_lv: 1, name: "群体护盾", info: "随机3个友方获得2次伤害免疫", is_inst: true, t_times: 1, t_inv: 0, d_rds: 1 },
{ uuid: 7001, type: CardType.SpecialUpgrade, cost: 6, weight: 16, pool_lv: 1 ,kind: CKind.Card },
{ uuid: 7002, type: CardType.SpecialUpgrade, cost: 6, weight: 14, pool_lv: 2 ,kind: CKind.Card },
@@ -117,20 +128,6 @@ export interface SpecialRefreshCardConfig extends CardConfig {
refreshHeroType: SpecialRefreshHeroType
}
/** 技能卡牌配置补充 */
export interface SkillCardConfig extends CardConfig {
name: string
info: string
}
export const SkillCardList: Record<number, SkillCardConfig> = {
6005: { uuid: 6005, type: CardType.Skill, cost: 2, weight: 20, pool_lv: 1, kind: CKind.Skill, card_lv: 1, name: "半月斩", info: "施放一道半月剑气,对触碰到的敌人造成伤害" },
6104: { uuid: 6104, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: "闪光箭雨", info: "施放闪光箭雨,对多个敌人造成伤害并暴击" },
6205: { uuid: 6205, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 3, kind: CKind.Skill, card_lv: 1, name: "月光波", info: "施放月波,对触碰到的多个敌人造成高额伤害" },
6304: { uuid: 6304, type: CardType.Skill, cost: 3, weight: 20, pool_lv: 2, kind: CKind.Skill, card_lv: 1, name: "神圣治疗", info: "恢复场上随机3个友方单位的生命值" },
6406: { uuid: 6406, type: CardType.Skill, cost: 4, weight: 20, pool_lv: 4, kind: CKind.Skill, card_lv: 1, name: "全体鼓舞", info: "为随机3个友方单位增加攻击力和生命上限" },
}
/** 功能卡定义表 */

View File

@@ -97,47 +97,47 @@ export interface HSkillInfo {
export const HeroInfo: Record<number, heroInfo> = {
// ========== 近战英雄 ==========
5001:{uuid:5001,name:"盾战士",path:"hk1", fac:FacSet.HERO,cards_lv:1,lv:1,type:HType.Melee,hp:450,ap:25,call:6305,dead:6305,speed:480,
skills:{6001:{uuid:6001,lv:1,cd:0.75,ccd:0},6301:{uuid:6301,lv:1,cd:5,ccd:0}},info:"近战,魔法盾 坦克"},
skills:{6001:{uuid:6001,lv:1,cd:1.5,ccd:0},6301:{uuid:6301,lv:1,cd:5,ccd:0}},info:"近战,魔法盾 坦克"},
5002:{uuid:5002,name:"圣骑士",path:"hk3", fac:FacSet.HERO,cards_lv:3,lv:1,type:HType.Melee,hp:1350,ap:75,speed:480,
skills:{6001:{uuid:6001,lv:1,cd:0.75,ccd:0},6305:{uuid:6305,lv:1,cd:5,ccd:0}},info:"近战,群体护盾 坦克"},
skills:{6001:{uuid:6001,lv:1,cd:1.5,ccd:0},6305:{uuid:6305,lv:1,cd:5,ccd:0}},info:"近战,群体护盾 坦克"},
5003:{uuid:5003,name:"风行剑士",path:"hk4", fac:FacSet.HERO,cards_lv:2,lv:1,type:HType.Melee,hp:500,ap:100,speed:480,
skills:{6001:{uuid:6001,lv:1,cd:1.1,ccd:0},6014:{uuid:6014,lv:1,cd:5,ccd:0}},info:"近战,闪击 近战dps"},
skills:{6001:{uuid:6001,lv:1,cd:1.5,ccd:0},6014:{uuid:6014,lv:1,cd:5,ccd:0}},info:"近战,闪击 近战dps"},
5004:{uuid:5004,name:"刺客",path:"hc1", fac:FacSet.HERO,cards_lv:4,lv:1,type:HType.Melee,hp:1000,ap:200,speed:480,
skills:{6001:{uuid:6001,lv:1,cd:1.1,ccd:0},6104:{uuid:6104,lv:1,cd:5,ccd:0}},info:"近战,火焰击 近战dps"},
skills:{6001:{uuid:6001,lv:1,cd:0.7,ccd:0},6104:{uuid:6104,lv:1,cd:5,ccd:0}},info:"近战,火焰击 近战dps"},
5005:{uuid:5005,name:"自然骑士",path:"hk2", fac:FacSet.HERO,cards_lv:4,lv:1,type:HType.Melee,hp:1000,ap:200,speed:480,
skills:{6001:{uuid:6001,lv:1,cd:1.1,ccd:0},6104:{uuid:6104,lv:1,cd:5,ccd:0}},info:"治疗近战,火焰击 近战dps"},
skills:{6001:{uuid:6001,lv:1,cd:1.5,ccd:0},6104:{uuid:6104,lv:1,cd:5,ccd:0}},info:"治疗近战,火焰击 近战dps"},
// ========== 法师英雄 ==========
5101:{uuid:5101,name:"奥术法师",path:"hm2", fac:FacSet.HERO,cards_lv:1,lv:1,type:HType.Long,hp:150,ap:60,speed:480,
skills:{6201:{uuid:6201,lv:1,cd:1.5,ccd:0},6012:{uuid:6012,lv:1,cd:5,ccd:0}},info:"冰球,冰锥 远法dps"},
skills:{6201:{uuid:6201,lv:1,cd:1,ccd:0},6012:{uuid:6012,lv:1,cd:5,ccd:0}},info:"冰球,冰锥 远法dps"},
5102:{uuid:5102,name:"火焰法师",path:"hm1", fac:FacSet.HERO,cards_lv:2,lv:1,type:HType.Long,hp:300,ap:120,speed:480,
skills:{6203:{uuid:6203,lv:1,cd:1.5,ccd:0},6013:{uuid:6013,lv:1,cd:5,ccd:0}},info:"火击,火球 远法dps"},
skills:{6203:{uuid:6203,lv:1,cd:1,ccd:0},6013:{uuid:6013,lv:1,cd:5,ccd:0}},info:"火击,火球 远法dps"},
5103:{uuid:5103,name:"冰法法师",path:"hm9", fac:FacSet.HERO,cards_lv:3,lv:1,type:HType.Long,hp:450,ap:180,speed:480,
skills:{6201:{uuid:6201,lv:1,cd:1.5,ccd:0},6012:{uuid:6012,lv:1,cd:5,ccd:0}},info:"冰击,冰锥 远法dps"},
skills:{6201:{uuid:6201,lv:1,cd:1,ccd:0},6012:{uuid:6012,lv:1,cd:5,ccd:0}},info:"冰击,冰锥 远法dps"},
5104:{uuid:5104,name:"寒霜术士",path:"hm4", fac:FacSet.HERO,cards_lv:4,lv:1,type:HType.Long,hp:600,ap:240,speed:480,
skills:{6201:{uuid:6201,lv:1,cd:1.5,ccd:0},6202:{uuid:6202,lv:1,cd:5,ccd:0}},info:"冰锥,冰刺 远法dps"},
skills:{6201:{uuid:6201,lv:1,cd:1,ccd:0},6202:{uuid:6202,lv:1,cd:5,ccd:0}},info:"冰锥,冰刺 远法dps"},
5105:{uuid:5105,name:"炎爆法师",path:"hm3", fac:FacSet.HERO,cards_lv:5,lv:1,type:HType.Long,hp:750,ap:300,speed:480,
skills:{6203:{uuid:6203,lv:1,cd:1.5,ccd:0},6201:{uuid:6201,lv:1,cd:5,ccd:0}},info:"火球,陨石术 远法dps" },
skills:{6203:{uuid:6203,lv:1,cd:1,ccd:0},6201:{uuid:6201,lv:1,cd:5,ccd:0}},info:"火球,陨石术 远法dps" },
// ========== 远程英雄 ==========
5201:{uuid:5201,name:"射手",path:"ha1", fac:FacSet.HERO,cards_lv:1,lv:1,type:HType.Long,hp:150,ap:60,speed:480,
skills:{6101:{uuid:6101,lv:1,cd:1.3,ccd:0},6011:{uuid:6011,lv:1,cd:5,ccd:0}},info:"普通射击,暴射 远dps"},
skills:{6101:{uuid:6101,lv:1,cd:0.9,ccd:0},6011:{uuid:6011,lv:1,cd:5,ccd:0}},info:"普通射击,暴射 远dps"},
5202:{uuid:5202,name:"游侠",path:"ha2", fac:FacSet.HERO,cards_lv:3,lv:1,type:HType.Long,hp:450,ap:180,speed:480,
skills:{6011:{uuid:6101,lv:1,cd:1.3,ccd:0},6101:{uuid:6101,lv:1,cd:5,ccd:0}},info:"暴射,光箭 远dps"},
skills:{6011:{uuid:6101,lv:1,cd:0.9,ccd:0},6101:{uuid:6101,lv:1,cd:5,ccd:0}},info:"暴射,光箭 远dps"},
5203:{uuid:5203,name:"游侠",path:"ha3", fac:FacSet.HERO,cards_lv:3,lv:1,type:HType.Long,hp:450,ap:180,speed:480,
skills:{6011:{uuid:6101,lv:1,cd:1.3,ccd:0},6101:{uuid:6101,lv:1,cd:5,ccd:0}},info:"暴射,光箭 远dps"},
skills:{6011:{uuid:6101,lv:1,cd:0.9,ccd:0},6101:{uuid:6101,lv:1,cd:5,ccd:0}},info:"暴射,光箭 远dps"},
// ========== 腐竹英雄 ==========
5301:{uuid:5301,name:"牧师",path:"hh1", fac:FacSet.HERO,cards_lv:1,lv:1,type:HType.Long,hp:150,ap:20,speed:480,
skills:{6202:{uuid:6202,lv:1,cd:2,ccd:0},6302:{uuid:6302,lv:1,cd:5,ccd:0}},info:"冰锥1,治疗 远辅助" },
skills:{6202:{uuid:6202,lv:1,cd:1.2,ccd:0},6302:{uuid:6302,lv:1,cd:5,ccd:0}},info:"冰锥1,治疗 远辅助" },
5302:{uuid:5302,name:"战地医师",path:"hz1", fac:FacSet.HERO,cards_lv:2,lv:1,type:HType.Long,hp:300,ap:40,speed:480,
skills:{6202:{uuid:6202,lv:1,cd:2,ccd:0},6304:{uuid:6304,lv:1,cd:5,ccd:0}},info:"冰锥1,群体治疗 远辅助"},
skills:{6202:{uuid:6202,lv:1,cd:1.2,ccd:0},6304:{uuid:6304,lv:1,cd:5,ccd:0}},info:"冰锥1,群体治疗 远辅助"},
5303:{uuid:5303,name:"守护祭司",path:"hm6", fac:FacSet.HERO,cards_lv:4,lv:1,type:HType.Long,hp:600,ap:80,speed:480,
skills:{6202:{uuid:6202,lv:1,cd:2,ccd:0},6004:{uuid:6004,lv:1,cd:5,ccd:0}},info:"普通射击,单体攻击buff 射手辅助"},
skills:{6202:{uuid:6202,lv:1,cd:1.2,ccd:0},6004:{uuid:6004,lv:1,cd:5,ccd:0}},info:"普通射击,单体攻击buff 射手辅助"},
5304:{uuid:5304,name:"秘法精灵",path:"hm7", fac:FacSet.HERO,cards_lv:6,lv:1,type:HType.Long,hp:900,ap:120,speed:480,
skills:{6202:{uuid:6202,lv:1,cd:2,ccd:0},6105:{uuid:6105,lv:1,cd:5,ccd:0}},info:"普通射击,群体攻击buff 射手辅助"},
skills:{6202:{uuid:6202,lv:1,cd:1.2,ccd:0},6105:{uuid:6105,lv:1,cd:5,ccd:0}},info:"普通射击,群体攻击buff 射手辅助"},

View File

@@ -1,5 +1,5 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3 } from "cc";
import { Vec3, Prefab, instantiate, tween, Node } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { DTType, RType, SkillConfig, SkillKind, SkillSet, SkillUpList, TGroup } from "../common/config/SkillSet";
@@ -88,7 +88,21 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
// 构造一个模拟的 HeroAttrsComp 用于数值计算,只包含基础卡牌伤害计算所需的属性
const mockAttrs = new HeroAttrsComp();
mockAttrs.ap = 100; // 基准攻击力,可以根据设计需求调整
// 动态计算卡牌的虚拟攻击力:
// 1. 根据卡牌等级给予基础成长(同英雄升级公式,基准设为 100
let baseAp = 100 * Math.pow(FightSet.MERGE_NEED, cardLv - 1);
let highestAp = baseAp;
// 2. 获取场上最高攻击力的英雄,保证后期奶量/增益绝对够用
ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => {
const attr = e.get(HeroAttrsComp);
if (attr && attr.fac === FacSet.HERO && !attr.is_dead && attr.ap > highestAp) {
highestAp = attr.ap;
}
});
mockAttrs.ap = highestAp;
mockAttrs.critical = 0;
mockAttrs.freeze_chance = 0;
mockAttrs.back_chance = 0;
@@ -99,7 +113,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (isFriendly) {
const friendlyTargets = this.resolveFriendlyTargets(targetEids, FacSet.HERO);
if (friendlyTargets.length === 0) continue;
this.applyFriendlySkillEffects(s_uuid, cardLv, config, null as any, mockAttrs, friendlyTargets, null);
this.applyFriendlySkillEffects(s_uuid, cardLv, config, null as any, mockAttrs, friendlyTargets, spawnPos);
} else {
const enemyTargetPos = this.resolveRepeatCastTargetPos(new Vec3(spawnPos.x + 300, spawnPos.y, spawnPos.z), i);
this.createSkillEntityForCard(s_uuid, cardLv, mockAttrs, spawnPos, enemyTargetPos, i);
@@ -362,7 +376,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
* 2. 处理 buffs 配置追加
* 3. 保留完整施法信息参数,便于后续扩展更多友方效果
*/
private applyFriendlySkillEffects(_s_uuid: number, _skillLv: number, config: SkillConfig, _heroView: HeroViewComp, _cAttrsComp: HeroAttrsComp, targets: HeroViewComp[], _targetPos: Vec3 | null) {
private applyFriendlySkillEffects(_s_uuid: number, _skillLv: number, config: SkillConfig, _heroView: HeroViewComp, _cAttrsComp: HeroAttrsComp, targets: HeroViewComp[], _targetPos: Vec3 | null, isCardSkill: boolean = false) {
const kind = config.kind ?? SkillKind.Support;
const sUp = SkillUpList[_s_uuid] ?? SkillUpList[1001];
const sAp =config.ap+sUp.ap*_skillLv;
@@ -370,37 +384,102 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const applyTargets = kind === SkillKind.Heal
? this.pickHealTargetsByMostMissingHp(targets, sHit)
: this.pickRandomFriendlyTargets(targets, sHit);
for (const target of applyTargets) {
if (!target.ent) continue;
const model = target.ent.get(HeroAttrsComp);
if (!model || model.is_dead) continue;
if (kind === SkillKind.Heal && sAp !== 0) {
const addHp = Math.floor(sAp*_cAttrsComp.ap/100);//技能的ap是百分值 需要/100
model.add_hp(addHp);
target.health(addHp);
} else if (kind === SkillKind.Shield && sAp !== 0) {
const addShield = Math.max(0, Math.floor(sAp));
model.add_shield(addShield);
}
if (!config.buffs || config.buffs.length === 0) continue;
for (const buffConf of config.buffs) {
if (!buffConf) continue;
const sBuffAp=buffConf.value+sUp.buff_ap
const sBuffHp=buffConf.value+sUp.buff_hp
switch (buffConf.buff){
case Attrs.ap:
model.add_ap(sBuffAp)
//加工动画
break
case Attrs.hp_max:
model.add_hp_max(sBuffHp)
//加最大生命值动画
break
}
const startPos = _heroView?.node?.position || _targetPos;
if (startPos) {
this.playFriendlyCastEffect(startPos, target, isCardSkill, () => {
this.applyActualFriendlyEffect(target, kind, sAp, _cAttrsComp, config, sUp);
});
} else {
this.applyActualFriendlyEffect(target, kind, sAp, _cAttrsComp, config, sUp);
}
}
}
private playFriendlyCastEffect(startPos: Vec3, target: HeroViewComp, isCardSkill: boolean, callback: Function) {
if (!target.node || !target.node.isValid) {
callback();
return;
}
const prefabPath = "game/skill/buff/buff";
const prefab = oops.res.get(prefabPath, Prefab);
if (!prefab) {
oops.res.load(prefabPath, Prefab, (err, res) => {
if (err) {
callback();
return;
}
this.doPlayFriendlyCastEffect(startPos.clone(), target, res as Prefab, isCardSkill, callback);
});
} else {
this.doPlayFriendlyCastEffect(startPos.clone(), target, prefab as Prefab, isCardSkill, callback);
}
}
private doPlayFriendlyCastEffect(startPos: Vec3, target: HeroViewComp, prefab: Prefab, isCardSkill: boolean, callback: Function) {
if (!target.node || !target.node.isValid) {
callback();
return;
}
const scene = smc.map?.MapView?.scene;
const parent = scene?.entityLayer?.node?.getChildByName("SKILL") || target.node.parent;
if (!parent) {
callback();
return;
}
const node = instantiate(prefab);
node.parent = parent;
startPos.y += 15;
node.setPosition(startPos);
const targetPos = target.node.position.clone();
targetPos.y += 15;
const dist = Vec3.distance(startPos, targetPos);
const duration = Math.min(0.6, Math.max(0.3, dist / 800));
// 统一走直线
tween(node)
.to(duration, { position: targetPos, angle: -720 }, { easing: 'sineOut' })
.call(() => {
if (node.isValid) node.destroy();
callback();
})
.start();
}
private applyActualFriendlyEffect(target: HeroViewComp, kind: SkillKind, sAp: number, _cAttrsComp: HeroAttrsComp, config: SkillConfig, sUp: any) {
if (!target.ent) return;
const model = target.ent.get(HeroAttrsComp);
if (!model || model.is_dead) return;
if (kind === SkillKind.Heal && sAp !== 0) {
const addHp = Math.floor(sAp*_cAttrsComp.ap/100);
model.add_hp(addHp);
target.health(addHp);
} else if (kind === SkillKind.Shield && sAp !== 0) {
const addShield = Math.max(0, Math.floor(sAp));
model.add_shield(addShield);
}
if (!config.buffs || config.buffs.length === 0) return;
for (const buffConf of config.buffs) {
if (!buffConf) continue;
const sBuffAp=buffConf.value+sUp.buff_ap
const sBuffHp=buffConf.value+sUp.buff_hp
switch (buffConf.buff){
case Attrs.ap:
model.add_ap(sBuffAp)
break
case Attrs.hp_max:
model.add_hp_max(sBuffHp)
break
}
}
}
private pickRandomFriendlyTargets(targets: HeroViewComp[], hitCount: number): HeroViewComp[] {
if (!targets || targets.length === 0) return [];
const validHitCount = Math.max(1, Math.floor(hitCount));

View File

@@ -2,8 +2,7 @@ import { mLogger } from "../common/Logger";
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources, Light } 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, SkillCardList } from "../common/config/CardSet";
import { CardUseComp } from "./CardUseComp";
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";
@@ -62,7 +61,6 @@ export class CardComp extends CCComp {
private fixedBaseY: number = 0;
private fixedBaseZ: number = 0;
private opacityComp: UIOpacity | null = null;
private cardUseComp: CardUseComp | null = null;
private iconVisualToken: number = 0;
onLoad() {
@@ -70,7 +68,6 @@ export class CardComp extends CCComp {
this.bindEvents();
this.restPosition = this.node.position.clone();
this.opacityComp = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
this.cardUseComp = this.resolveCardUseComp();
this.opacityComp.opacity = 255;
this.updateLockUI();
this.applyEmptyUI();
@@ -217,10 +214,25 @@ export class CardComp extends CCComp {
this.playUseDisappearAnim(() => {
this.clearAfterUse();
this.isUsing = false;
this.cardUseComp?.onCardUsed(used);
this.executeCardEffectEntry(used);
});
return used;
}
private executeCardEffectEntry(payload: CardConfig) {
switch (payload.type) {
case CardType.Hero:
oops.message.dispatchEvent(GameEvent.CallHero, payload);
break;
case CardType.Skill:
oops.message.dispatchEvent(GameEvent.UseSkillCard, payload);
break;
case CardType.SpecialUpgrade:
case CardType.SpecialRefresh:
oops.message.dispatchEvent(GameEvent.UseSpecialCard, payload);
break;
}
}
/** 查询槽位是否有卡 */
hasCard(): boolean {
@@ -414,7 +426,7 @@ export class CardComp extends CCComp {
this.info_node.getChildByName("hp").getChildByName("val").getComponent(Label).string = `${(hero?.hp ?? 0) * heroLv}`;
}else if(this.card_type===CardType.Skill){
const skill = SkillSet[this.card_uuid];
const skillCard = SkillCardList[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}`);
@@ -518,16 +530,6 @@ export class CardComp extends CCComp {
if (label) label.string = value;
}
private resolveCardUseComp(): CardUseComp | null {
let current: Node | null = this.node.parent;
while (current) {
const comp = current.getComponent(CardUseComp);
if (comp) return comp;
current = current.parent;
}
mLogger.log(this.debugMode, "CardComp", "CardUseComp not found for", this.node.name);
return null;
}
private resolveCardIconId(type: CardType, uuid: number): string {
if (type === CardType.Skill) {

View File

@@ -1,70 +0,0 @@
import { mLogger } from "../common/Logger";
import { _decorator } 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 { CardType } from "../common/config/CardSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
const { ccclass, property } = _decorator;
interface CardUsePayload {
uuid: number
type: CardType
cost: number
slotName?: string
}
/** 视图层对象 */
@ccclass('CardUseComp')
@ecs.register('CardUseComp', false)
export class CardUseComp extends CCComp {
private debugMode: boolean = true;
private useCount: number = 0;
onLoad() {
}
start() {
}
onCardUsed(payload: CardUsePayload) {
this.useCount += 1;
mLogger.log(this.debugMode, "CardUseComp", "onCardUsed", {
useCount: this.useCount,
...payload
});
this.executeCardEffectEntry(payload);
}
private executeCardEffectEntry(payload: CardUsePayload) {
const effectTag = this.getEffectEntryTag(payload);
mLogger.log(this.debugMode, "CardUseComp", "executeCardEffectEntry", payload);
mLogger.log(this.debugMode, "CardUseComp", "effect entry tag", effectTag);
}
private getEffectEntryTag(used:CardUsePayload): string {
switch (used.type) {
case CardType.Hero:
oops.message.dispatchEvent(GameEvent.CallHero, used);
return "hero";
case CardType.Skill:
oops.message.dispatchEvent(GameEvent.UseSkillCard, used);
return "skill";
case CardType.SpecialUpgrade:
case CardType.SpecialRefresh:
oops.message.dispatchEvent(GameEvent.UseSpecialCard, used);
return "special";
default:
return "unknown";
}
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.node.destroy();
}
}

View File

@@ -0,0 +1,57 @@
import { mLogger } from "../common/Logger";
import { _decorator, Node, Prefab, instantiate } 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 { SkillBoxComp } from "./SkillBoxComp";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { smc } from "../common/SingletonModuleComp";
const { ccclass, property } = _decorator;
/** 视图层对象 */
@ccclass('MissSkillsComp')
@ecs.register('MissSkillsComp', false)
export class MissSkillsComp extends CCComp {
private debugMode: boolean = true;
@property({type: Prefab})
private skill_box: Prefab = null;
onLoad() {
oops.message.on(GameEvent.UseSkillCard, this.onUseSkillCard, this);
this.node.parent=smc.map.MapView.scene.entityLayer!.node!
}
onDestroy() {
oops.message.off(GameEvent.UseSkillCard, this.onUseSkillCard, this);
}
private onUseSkillCard(event: string, args: any) {
const payload = args ?? event;
const uuid = Number(payload?.uuid ?? 0);
const card_lv = Math.max(1, Math.floor(Number(payload?.card_lv ?? 1)));
if (!uuid) return;
this.addSkill(uuid, card_lv);
}
start() {
}
addSkill(uuid: number, card_lv: number) {
if (!this.skill_box) {
mLogger.error(this.debugMode, "MissSkillsComp", "skill_box prefab not set");
return;
}
const node = instantiate(this.skill_box);
node.parent = this.node;
const comp = node.getComponent(SkillBoxComp) || node.addComponent(SkillBoxComp);
comp.init(uuid, card_lv);
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.node.destroy();
}
}

View File

@@ -2,7 +2,7 @@
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "8edfc7ca-1cda-4725-9a0c-ded8f5f95531",
"uuid": "68387c27-1f8e-4056-8e8d-728fa95ed1b9",
"files": [],
"subMetas": {},
"userData": {}

View File

@@ -49,34 +49,16 @@ export class MissionHeroCompComp extends CCComp {
this.on(GameEvent.MissionEnd,this.clear_heros,this)
/** 全局消息监听 */
oops.message.on(GameEvent.CallHero,this.call_hero,this)
oops.message.on(GameEvent.UseSkillCard,this.onUseSkillCard,this)
}
onDestroy(){
/** 清理监听,避免节点销毁后仍响应消息 */
oops.message.off(GameEvent.CallHero,this.call_hero,this)
oops.message.off(GameEvent.UseSkillCard,this.onUseSkillCard,this)
oops.message.off(GameEvent.FightReady,this.fight_ready,this)
oops.message.off(GameEvent.Zhaohuan,this.zhao_huan,this)
oops.message.off(GameEvent.MissionEnd,this.clear_heros,this)
}
/** 响应卡牌释放技能 */
private onUseSkillCard(event: string, args: any) {
const payload = args ?? event;
const uuid = Number(payload?.uuid ?? 0);
const card_lv = Math.max(1, Math.floor(Number(payload?.card_lv ?? 1)));
if (!uuid) return;
// 分发给 SCastSystem 处理(使用特定的坐标 x=-340, y=30
oops.message.dispatchEvent(GameEvent.TriggerSkill, {
s_uuid: uuid,
isCardSkill: true,
card_lv: card_lv,
targetPos: v3(-340, 30, 0)
});
}
start() {
// this.test_call()
}

View File

@@ -0,0 +1,175 @@
import { mLogger } from "../common/Logger";
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas } 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 { CardPoolList } from "../common/config/CardSet";
import { SkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { smc } from "../common/SingletonModuleComp";
const { ccclass, property } = _decorator;
/** 视图层对象 */
@ccclass('SkillBoxComp')
@ecs.register('SkillBoxComp', false)
export class SkillBoxComp extends CCComp {
private debugMode: boolean = true;
@property({type: Node})
private icon_node:Node= null;
@property(Label)
private info_label: Label = null;
private s_uuid: number = 0;
private card_lv: number = 1;
private is_instant: boolean = true;
private trigger_times: number = 1;
private trigger_interval: number = 0;
private duration_rounds: number = 1;
private current_round: number = 0;
private current_trigger_times: number = 0;
private timer: number = 0;
private in_combat: boolean = false;
private initialized: boolean = false;
onLoad() {
oops.message.on(GameEvent.FightStart, this.onFightStart, this);
oops.message.on(GameEvent.MissionEnd, this.onMissionEnd, this);
this.node.on(GameEvent.NewWave, this.onNewWave, this);
oops.message.on(GameEvent.NewWave, this.onNewWaveGlobal, this);
}
onDestroy() {
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this);
this.node.off(GameEvent.NewWave, this.onNewWave, this);
oops.message.off(GameEvent.NewWave, this.onNewWaveGlobal, this);
}
init(uuid: number, card_lv: number) {
// this.node.parent=smc.map.MapView.scene.entityLayer!.node!
this.s_uuid = uuid;
this.card_lv = card_lv;
const config = CardPoolList.find(c => c.uuid === uuid);
if (config) {
this.is_instant = config.is_inst ?? true;
this.trigger_times = config.t_times ?? 1;
this.trigger_interval = config.t_inv ?? 0;
this.duration_rounds = config.d_rds ?? 1;
}
this.current_round = 0;
this.current_trigger_times = 0;
this.timer = 0;
this.initialized = true;
this.updateUI();
if (this.is_instant) {
// 即时起效:立即触发
this.triggerSkill();
this.current_trigger_times++;
if (this.current_trigger_times >= this.trigger_times) {
this.scheduleOnce(() => {
this.node.destroy();
}, 1.0); // 稍微延迟销毁,保证表现
}
}
}
updateUI() {
if (this.icon_node) {
const iconId = SkillSet[this.s_uuid]?.icon || `${this.s_uuid}`;
resources.load("gui/uicons", SpriteAtlas, (err, atlas) => {
if (err || !atlas) return;
const frame = atlas.getSpriteFrame(iconId);
if (frame && this.icon_node && this.icon_node.isValid) {
const sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite);
sprite.spriteFrame = frame;
}
});
}
if (this.info_label) {
if (!this.is_instant) {
this.info_label.string = `${this.duration_rounds - this.current_round}`;
} else {
this.info_label.string = "";
}
}
}
private onFightStart() {
if (!this.initialized) return;
this.in_combat = true;
if (!this.is_instant) {
// 战斗开始时计时归0重新计时
this.timer = 0;
// 如果这个技能每回合都可以触发 t_times 次,则在每回合开始时重置当前回合触发次数
this.current_trigger_times = 0;
}
}
private onNewWave() {
this.handleNewWave();
}
private onNewWaveGlobal() {
this.handleNewWave();
}
private handleNewWave() {
if (!this.initialized) return;
this.in_combat = false;
if (!this.is_instant) {
this.current_round++;
this.updateUI();
if (this.current_round >= this.duration_rounds) {
this.node.destroy();
}
}
}
private onMissionEnd() {
this.node.destroy();
}
update(dt: number) {
if (!this.initialized || !this.in_combat || this.is_instant) return;
if (!smc.mission.play || smc.mission.pause) return;
if (this.current_trigger_times < this.trigger_times) {
this.timer += dt;
if (this.timer >= this.trigger_interval) {
this.timer = 0; // 触发后重新计时
this.triggerSkill();
this.current_trigger_times++;
}
}
}
private triggerSkill() {
// 获取自身在父节点下的局部坐标
// UI 的局部坐标在 2D 相机中和实际的游戏逻辑坐标存在偏移关系,
// 可以结合自身局部坐标做一次偏移,此处直接读取自身的 localPosition 加上父节点的偏移
let targetPos = new Vec3();
const localPos = this.node.position;
const parentPos = this.node.parent ? this.node.parent.position : new Vec3(0, 0, 0);
targetPos.set(parentPos.x + localPos.x, parentPos.y + localPos.y, 0);
oops.message.dispatchEvent(GameEvent.TriggerSkill, {
s_uuid: this.s_uuid,
isCardSkill: true,
card_lv: this.card_lv,
targetPos: targetPos
});
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.node.destroy();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ca51918b-eae4-486b-82e8-ce89544a6a0d",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,71 +0,0 @@
# 属性变更脏标签优化方案
## 一、现有实现存在的具体问题
### 1.1 性能问题
- **HeroViewComp.ts** 的 `update(dt: number)` 方法中,每帧都调用 `hp_show()``mp_show()`
- 即使血量和魔法值没有变化也会每帧更新UI进度条造成不必要的性能开销
- 缺少属性变更标记机制,采用轮询方式检查
### 1.2 架构问题
- 属性变更与UI更新耦合在 `update` 循环中,违反按需更新原则
- UI更新逻辑分散缺少统一的变更标记管理
- 属性变更通知机制不完善
## 二、需要进行的修改方案
### 2.1 核心设计
- 采用脏标签Dirty Flag模式属性变更时标记对应的脏标签
- UI层在 `update` 中检查脏标签,只在属性变化时更新显示
- 解耦数据层和视图层,提升性能和可维护性
### 2.2 实现步骤
1. **添加脏标签标记**:在 `HeroAttrsComp` 中添加血量、魔法值、护盾的脏标签
2. **修改属性变更方法**:在 `HeroAttrsComp` 的属性变更方法中设置脏标签
3. **实现脏标签检查逻辑**:在 `HeroViewComp``update` 中检查脏标签并按需更新UI
4. **清理脏标签**UI更新完成后清理对应的脏标签
## 三、需要修改的具体文件及对应模块
### 3.1 HeroAttrsComp.ts
**修改模块**
- 类属性:添加脏标签字段 `dirty_hp``dirty_mp``dirty_shield`
- `add_hp()` 方法:设置 `dirty_hp = true`
- `add_mp()` 方法:设置 `dirty_mp = true`
- `add_shield()` 方法:设置 `dirty_shield = true`
- `reset()` 方法:重置所有脏标签
**修改内容**
- 添加脏标签字段:`dirty_hp: boolean = false``dirty_mp: boolean = false``dirty_shield: boolean = false`
- 在属性变更方法中设置对应的脏标签为 `true`
-`reset` 方法中重置所有脏标签为 `false`
### 3.2 HeroViewComp.ts
**修改模块**
- `update(dt: number)` 方法添加脏标签检查逻辑按需调用UI更新方法
**修改内容**
-`update` 中检查 `this.model.dirty_hp``this.model.dirty_mp``this.model.dirty_shield`
- 只在脏标签为 `true` 时调用对应的 `hp_show()``mp_show()``show_shield()`
- UI更新完成后清理脏标签为 `false`
### 3.3 HeroAttrEventSystem.ts
**修改模块**
- 无需修改
**修改内容**
- 脏标签方案不需要事件系统,保持现有实现即可
### 3.4 HeroAttrEvent.ts
**修改模块**
- 无需修改
**修改内容**
- 脏标签方案不需要事件组件,保持现有实现即可
## 四、预期效果
- **性能提升**消除每帧不必要的UI更新只在属性变化时更新减少CPU开销
- **架构优化**:实现按需更新模式,解耦数据层和视图层
- **可维护性提升**:脏标签逻辑简单清晰,易于理解和维护
- **代码简洁**:无需引入复杂的事件系统,实现成本低

File diff suppressed because it is too large Load Diff