16 Commits

Author SHA1 Message Date
panFD
4fee02d82e refactor(hero): 移除英雄等级显示相关逻辑
1. 从HeroTopBarComp中移除等级Label组件、lvShow方法以及等级显示调用
2. 从HeroViewComp的update逻辑中移除等级脏数据监听
3. 调整英雄顶栏预制体的布局参数并隐藏等级节点
2026-08-13 23:16:44 +08:00
panFD
15d14f7d59 refactor(hero&monster): 调整英雄与怪物的攻速配置与近战停止距离
1.  全局重构攻击攻速分级配置,统一放缓慢攻速档位上限为1.10s以紧凑战斗节奏
2.  调整近战单位停止追击距离从60提升至80,优化近战英雄作战手感
3.  批量更新所有英雄与怪物的技能攻击间隔,匹配新的攻速配置
2026-08-13 23:07:09 +08:00
panFD
9579c4f75e refactor(monster): 重构怪物分层显示与战斗逻辑
1.  重构怪物挂载逻辑,按Boss/小怪分别挂载到对应场景层
2.  调整怪物出生落点逻辑,移除额外Y轴偏移
3.  优化场景节点清理范围,新增MON/BOSS层清理
4.  重构渲染排序逻辑,分层管理怪物渲染层级
5.  新增5套攻击技能预制体与动画资源
6.  修复技能配置参数冗余注释与类型错误
2026-08-13 23:05:51 +08:00
pan
d54e6ce1d6 fix: 调整游戏地平线高度与UI、场景位置
统一修正游戏内角色控制器UI位置、地图物体Y轴偏移
调整游戏地平线基准值,修复场景显示偏移问题
2026-08-13 17:29:53 +08:00
pan
d06c99e2f1 refactor(hero): 重构英雄头顶状态栏逻辑到独立组件
将原HeroViewComp中的头顶血条、等级、护盾UI逻辑抽离为HeroTopBarComp,
统一管理头顶状态栏的初始化、数据更新、动画和显隐逻辑,减少HeroViewComp代码冗余,
提升代码可维护性和复用性。同时更新预制体关联,适配新的组件结构。
2026-08-13 17:20:38 +08:00
pan
76082d8e1f refactor: 重构护盾机制为数值减免型
1. 调整护盾逻辑:从单次抵挡改为按伤害量减免
2. 更新配置、注释与UI提示文本,统一术语表述
3. 修复prefab布局适配新的护盾显示样式
2026-08-13 16:53:15 +08:00
pan
695a9e2b66 refactor(skill): 重构技能配置加载逻辑,将参数下沉至prefab配置
1. 新增SkillView组件属性runType/endType/speed/time,替换原配置表字段
2. 批量同步所有技能prefab的配置参数
3. 优化技能运行逻辑,优先使用prefab配置,配置表仅作兜底
4. 新增同步脚本用于批量迁移技能配置
2026-08-13 16:26:19 +08:00
pan
c6b5e1431f fix(skill): 修复技能相关的显示与逻辑问题
1. 调整技能预制体的锚点和攻击偏移参数
2. 修复SkillView.ts的代码格式与导入错误
3. 补充fixed类型技能的动画结束销毁逻辑
2026-08-13 15:36:57 +08:00
pan
7fa18329d6 fix(skill): 调整普攻技能配置与表现
1.  修改普攻预制体的缩放、尺寸、锚点与精灵贴图
2.  更新普攻动画的采样率、时长与帧动画帧
3.  调整技能配置,将默认普攻替换为6006并修正攻击类型
4.  统一全英雄与怪物的默认技能为6006普攻
2026-08-13 15:11:11 +08:00
panFD
a2c397120c docs(skill): 更新技能4的图标资源
替换了assets/resources/game/skill/skill4.png的图片资源
2026-08-13 14:22:41 +08:00
panFD
cb2a72da52 refactor(map): 重构rogue战斗数值生成逻辑
重构了Rogue模式的怪物数值计算方式,移除了基于英雄实时强度的动态缩放算法,改为统一使用基础属性乘以可调系数的固定缩放方案:
1.  更新了怪物配置基础属性,对齐1级英雄面板作为基准
2.  简化了数值公式,仅保留三个可调节的全局系数用于平衡难度
3.  删除了冗余的ECS查询、强度计算和类型系数逻辑
4.  更新了全部注释文档,明确数值校准目标和调整方式
2026-08-13 14:12:05 +08:00
pan
9da3e2d9fd fix(map): adjust global monster power multiplier to 1
更新全局怪物强度系数配置,将原本的2/3调整为1,让怪物强度和英雄能力持平
2026-08-13 11:17:29 +08:00
pan
eb4b370533 refactor: 重构击退机制,将概率改为距离增量
1. 调整近战默认攻击距离从150改为100
2. 将击退相关属性从概率改为击退距离增量,同步更新所有配置、UI显示和技能描述
3. 移除旧的击退概率判定逻辑,改为直接应用击退距离增量
4. 优化受击击退的处理流程,统一参数传递
2026-08-13 11:09:37 +08:00
pan
a3375ec3b6 refactor(hero): 统一英雄攻击距离默认值并优化队友间距逻辑
1. 将英雄/怪物默认攻击距离调整为近战150、中程300、远程450,通过HeroDisVal统一管理
2. 新增同阵营单位移动和待机时的间距避让逻辑,避免单位重叠穿模,添加自然蠕动效果
2026-08-13 10:23:14 +08:00
pan
011e7b3dde refactor(MissionHeroComp): 重构英雄登场点位逻辑
1.  将动态生成的英雄站位改为硬编码固定3列位置
2.  更新站位分配优先级适配新的点位数组
3.  优化注释说明,明确点位规则和分配逻辑
2026-08-13 09:31:40 +08:00
pan
038abdc5d2 refactor(monster): 重构移动组件体系,统一英雄与怪物移动逻辑
1. 删除废弃的 MonMoveComp,将功能合并到 MoveComp 中
2. 移除 MissionComp 中冗余的怪物最大数量配置计算
3. 优化英雄站位分配逻辑,近战优先靠前站位
4. 统一移动系统的边界约束与渲染排序逻辑
5. 清理 Hero.ts 中未使用的导入项
2026-08-13 09:20:27 +08:00
68 changed files with 8511 additions and 1500 deletions

View File

@@ -38,10 +38,13 @@
"_components": [
{
"__id__": 110
},
{
"__id__": 112
}
],
"_prefab": {
"__id__": 112
"__id__": 114
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -95,7 +98,7 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 9.114,
"x": 0,
"y": 10,
"z": 0
},
@@ -217,7 +220,7 @@
"__id__": 17
}
],
"_active": false,
"_active": true,
"_components": [
{
"__id__": 25
@@ -235,7 +238,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -9.738,
"y": -7.84,
"z": 0
},
"_lrot": {
@@ -287,7 +290,7 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -35,
"x": -20,
"y": 0,
"z": 0
},
@@ -328,8 +331,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 70,
"height": 10
"width": 40,
"height": 5
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -506,8 +509,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 240,
"height": 40
"width": 140,
"height": 23.333333333333336
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -631,8 +634,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 70,
"height": 10
"width": 40,
"height": 5
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -859,7 +862,7 @@
"_contentSize": {
"__type__": "cc.Size",
"width": 40,
"height": 11
"height": 9
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -1037,7 +1040,7 @@
"_contentSize": {
"__type__": "cc.Size",
"width": 140,
"height": 43.333333333333336
"height": 36.66666666666667
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -1162,7 +1165,7 @@
"_contentSize": {
"__type__": "cc.Size",
"width": 40,
"height": 11
"height": 9
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -1347,7 +1350,7 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -25,
"x": -20,
"y": 0,
"z": 0
},
@@ -1388,8 +1391,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 50,
"height": 10
"width": 40,
"height": 9
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -1691,8 +1694,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 50,
"height": 10
"width": 40,
"height": 9
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -1813,7 +1816,7 @@
"__id__": 99
}
],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 105
@@ -2520,8 +2523,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 70,
"height": 20
"width": 40,
"height": 11
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -2534,6 +2537,33 @@
"__type__": "cc.CompPrefabInfo",
"fileId": "cdvV5hAkdEaY6AT6urHKuf"
},
{
"__type__": "c4181/FEWhKbY0D0PLdCyKi",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 113
},
"hpProgressBar": {
"__id__": 53
},
"lvLabel": null,
"shieldProgressBar": {
"__id__": 77
},
"hpBarSprite": {
"__id__": 36
},
"_id": "a77QVRkP5NX4b+LyqmW1KJ"
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "3eCKBFOJhLHKqcnak/mi3d"
},
{
"__type__": "cc.PrefabInfo",
"root": {

View File

@@ -1398,7 +1398,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21.504,
"y": -31.947,
"z": 0
},
"_lrot": {
@@ -1875,7 +1875,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21.504,
"y": -50.813,
"z": 0
},
"_lrot": {
@@ -2352,7 +2352,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"y": -150,
"z": 0
},
"_lrot": {
@@ -2618,6 +2618,8 @@
"__id__": 0
},
"fileId": "6bE0ieRMBGyYHdxvuJ03FA",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -3432,6 +3434,8 @@
"__id__": 0
},
"fileId": "256TxMVzhHQopA08O7v8nq",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -3471,6 +3475,8 @@
"__id__": 0
},
"fileId": "5dWwAe3IFHRJuoSicXBEs8",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -3710,6 +3716,8 @@
"__id__": 0
},
"fileId": "b9zLdCeJlE5pPiK++dLgBX",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -4388,6 +4396,8 @@
"__id__": 0
},
"fileId": "9d2p/canpFGof2O/w8JyTj",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -4427,6 +4437,8 @@
"__id__": 0
},
"fileId": "6bkL20JyRDOI6ycVYKl/ie",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -4495,6 +4507,8 @@
"__id__": 0
},
"fileId": "14GnCNU65BbJPIzLWE/Oiz",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -7363,6 +7377,8 @@
"__id__": 0
},
"fileId": "c4IqMKgXNExa5w4EAciRxE",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -8202,7 +8218,7 @@
},
{
"__type__": "cc.Node",
"_name": "LINE2",
"_name": "BOSS",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -8273,7 +8289,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ea767WaZRHo4s0bZQfN8zP"
"fileId": "3fVB8r2UFAB5QujD6LOw6s"
},
{
"__type__": "cc.PrefabInfo",
@@ -8283,14 +8299,14 @@
"asset": {
"__id__": 0
},
"fileId": "60UdPtJn9ALLHCveLwT1bT",
"fileId": "f4iFaXCXNKorfGJFH32POa",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "HERO",
"_name": "MON",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -8361,7 +8377,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "dasmpuTSVM7qA3QW9/3XjQ"
"fileId": "ea767WaZRHo4s0bZQfN8zP"
},
{
"__type__": "cc.PrefabInfo",
@@ -8371,14 +8387,14 @@
"asset": {
"__id__": 0
},
"fileId": "eaiasKNEFO6qvodGhL4a0b",
"fileId": "60UdPtJn9ALLHCveLwT1bT",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "LINE1",
"_name": "HERO",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -8449,7 +8465,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "b8C5CFg5lM4oUHMeSrc/sV"
"fileId": "dasmpuTSVM7qA3QW9/3XjQ"
},
{
"__type__": "cc.PrefabInfo",
@@ -8459,14 +8475,14 @@
"asset": {
"__id__": 0
},
"fileId": "4dcNJaDM1CLI9q3fIqHZ/U",
"fileId": "eaiasKNEFO6qvodGhL4a0b",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "LINE3",
"_name": "LINE1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -8537,7 +8553,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "a5LMIIbHtOKK10K5do5dSQ"
"fileId": "b8C5CFg5lM4oUHMeSrc/sV"
},
{
"__type__": "cc.PrefabInfo",
@@ -8547,14 +8563,14 @@
"asset": {
"__id__": 0
},
"fileId": "a6CBSkrzZIurlX0D/HzXDp",
"fileId": "4dcNJaDM1CLI9q3fIqHZ/U",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "BOOS",
"_name": "LINE3",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -8625,7 +8641,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "3fVB8r2UFAB5QujD6LOw6s"
"fileId": "a5LMIIbHtOKK10K5do5dSQ"
},
{
"__type__": "cc.PrefabInfo",
@@ -8635,7 +8651,7 @@
"asset": {
"__id__": 0
},
"fileId": "f4iFaXCXNKorfGJFH32POa",
"fileId": "a6CBSkrzZIurlX0D/HzXDp",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
@@ -8893,6 +8909,8 @@
"__id__": 0
},
"fileId": "35iAcUUYpE8akTtTCX1ITo",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -8932,6 +8950,8 @@
"__id__": 0
},
"fileId": "68Q5vEQWBINrggngtTZ7wh",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{

View File

@@ -7,15 +7,26 @@
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 8,
"sample": 24,
"speed": 1,
"wrapMode": 2,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0,
"_hash": 500763545,
"_tracks": [],
"_exoticAnimation": null,
"_events": [],
"_events": [
{
"frame": 0.08333333333333333,
"func": "",
"params": []
},
{
"frame": 0.08333333333333333,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 1

View File

@@ -0,0 +1,116 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "atk01",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 24,
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.20833333333333334,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.08333333333333333,
"func": "",
"params": []
},
{
"frame": 0.08333333333333333,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
},
"_auxiliaryCurveEntries": []
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 2
},
"proxy": null
},
"_channel": {
"__id__": 5
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 3
},
{
"__id__": 4
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 6
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.041666666666666664,
0.08333333333333333,
0.125,
0.16666666666666666
],
"_values": [
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
"__expectedType__": "cc.SpriteFrame"
}
]
},
{
"__type__": "cc.AnimationClipAdditiveSettings",
"enabled": false,
"refClip": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "2.0.3",
"importer": "animation-clip",
"imported": true,
"uuid": "6b9c9f74-0168-4b57-824d-bfcc2cd1785d",
"files": [
".cconb"
],
"subMetas": {},
"userData": {
"name": "atk02"
}
}

View File

@@ -0,0 +1,116 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "atk01",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 24,
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.20833333333333334,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.08333333333333333,
"func": "",
"params": []
},
{
"frame": 0.08333333333333333,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
},
"_auxiliaryCurveEntries": []
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 2
},
"proxy": null
},
"_channel": {
"__id__": 5
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 3
},
{
"__id__": 4
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 6
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.041666666666666664,
0.08333333333333333,
0.125,
0.16666666666666666
],
"_values": [
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
"__expectedType__": "cc.SpriteFrame"
}
]
},
{
"__type__": "cc.AnimationClipAdditiveSettings",
"enabled": false,
"refClip": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "2.0.3",
"importer": "animation-clip",
"imported": true,
"uuid": "26398731-5541-44ca-b2b7-4947dc9bf7f7",
"files": [
".cconb"
],
"subMetas": {},
"userData": {
"name": "atk03"
}
}

View File

@@ -0,0 +1,116 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "atk01",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 24,
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.20833333333333334,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.08333333333333333,
"func": "",
"params": []
},
{
"frame": 0.08333333333333333,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
},
"_auxiliaryCurveEntries": []
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 2
},
"proxy": null
},
"_channel": {
"__id__": 5
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 3
},
{
"__id__": 4
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 6
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.041666666666666664,
0.08333333333333333,
0.125,
0.16666666666666666
],
"_values": [
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
"__expectedType__": "cc.SpriteFrame"
}
]
},
{
"__type__": "cc.AnimationClipAdditiveSettings",
"enabled": false,
"refClip": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "2.0.3",
"importer": "animation-clip",
"imported": true,
"uuid": "0ad94bf2-3b2d-4f80-8500-68c5e33e2ed1",
"files": [
".cconb"
],
"subMetas": {},
"userData": {
"name": "atk04"
}
}

View File

@@ -0,0 +1,116 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "atk01",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 24,
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.20833333333333334,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.08333333333333333,
"func": "",
"params": []
},
{
"frame": 0.08333333333333333,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
},
"_auxiliaryCurveEntries": []
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 2
},
"proxy": null
},
"_channel": {
"__id__": 5
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 3
},
{
"__id__": 4
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 6
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.041666666666666664,
0.08333333333333333,
0.125,
0.16666666666666666
],
"_values": [
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
"__expectedType__": "cc.SpriteFrame"
}
]
},
{
"__type__": "cc.AnimationClipAdditiveSettings",
"enabled": false,
"refClip": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "2.0.3",
"importer": "animation-clip",
"imported": true,
"uuid": "ba0a95bf-2ea1-4d27-9c4f-cbd970ca558c",
"files": [
".cconb"
],
"subMetas": {},
"userData": {
"name": "atk05"
}
}

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -1,7 +1,7 @@
[
{
"__type__": "cc.Prefab",
"_name": "atk",
"_name": "atk1",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
@@ -13,7 +13,7 @@
},
{
"__type__": "cc.Node",
"_name": "atk",
"_name": "atk1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
@@ -108,8 +108,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": -0.5,
"y": 0.5,
"x": 1.3,
"y": 1.1,
"z": 1
},
"_mobility": 0,
@@ -136,8 +136,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 139,
"height": 128
"width": 95,
"height": 96
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -186,7 +186,7 @@
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "3d46f945-3f07-477e-a95a-b49557d552c6",
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
@@ -222,8 +222,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 30,
"height": 50
"width": 120,
"height": 120
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -248,8 +248,12 @@
"__prefab": {
"__id__": 11
},
"atk_x": 0,
"atk_y": 0,
"atk_x": 20,
"atk_y": 40,
"runType": 2,
"endType": 0,
"speed": 720,
"time": 0,
"_id": ""
},
{
@@ -315,8 +319,8 @@
},
"_size": {
"__type__": "cc.Size",
"width": 30,
"height": 50
"width": 120,
"height": 120
},
"_id": ""
},
@@ -336,7 +340,7 @@
"__prefab": {
"__id__": 17
},
"playOnLoad": false,
"playOnLoad": true,
"_clips": [
{
"__uuid__": "45017156-064b-43f2-a24e-176b6ce17ad8",

View File

@@ -8,6 +8,6 @@
],
"subMetas": {},
"userData": {
"syncNodeName": "atk"
"syncNodeName": "atk1"
}
}

View File

@@ -0,0 +1,375 @@
[
{
"__type__": "cc.Prefab",
"_name": "atk2",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "atk2",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
}
],
"_prefab": {
"__id__": 18
},
"_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": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "Node",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_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.3,
"y": 1.1,
"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__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 96
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93pNmvtQlLSqtTgIepyEmA"
},
{
"__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__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "12eKc8gltBz50frJCS5+ww"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
"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": 120,
"height": 120
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
},
{
"__type__": "57aabs7TE1J5obTAZczc+64",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"atk_x": 20,
"atk_y": 40,
"runType": 2,
"endType": 0,
"speed": 720,
"time": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"enabledContactListener": true,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 1,
"_allowSleep": false,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": true,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_size": {
"__type__": "cc.Size",
"width": 120,
"height": 120
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "6b9c9f74-0168-4b57-824d-bfcc2cd1785d",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "6b9c9f74-0168-4b57-824d-bfcc2cd1785d",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "ff6e9a75-b1b9-448e-9cbd-d30de98fd21b",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "atk2"
}
}

View File

@@ -0,0 +1,375 @@
[
{
"__type__": "cc.Prefab",
"_name": "atk3",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "atk3",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
}
],
"_prefab": {
"__id__": 18
},
"_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": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "Node",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_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.3,
"y": 1.1,
"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__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 96
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93pNmvtQlLSqtTgIepyEmA"
},
{
"__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__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "12eKc8gltBz50frJCS5+ww"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
"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": 120,
"height": 120
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
},
{
"__type__": "57aabs7TE1J5obTAZczc+64",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"atk_x": 20,
"atk_y": 40,
"runType": 2,
"endType": 0,
"speed": 720,
"time": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"enabledContactListener": true,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 1,
"_allowSleep": false,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": true,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_size": {
"__type__": "cc.Size",
"width": 120,
"height": 120
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "26398731-5541-44ca-b2b7-4947dc9bf7f7",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "26398731-5541-44ca-b2b7-4947dc9bf7f7",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "16ca6f33-f910-4202-add4-2862be6c9127",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "atk3"
}
}

View File

@@ -0,0 +1,375 @@
[
{
"__type__": "cc.Prefab",
"_name": "atk4",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "atk4",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
}
],
"_prefab": {
"__id__": 18
},
"_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": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "Node",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_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.3,
"y": 1.1,
"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__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 96
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93pNmvtQlLSqtTgIepyEmA"
},
{
"__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__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "12eKc8gltBz50frJCS5+ww"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
"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": 120,
"height": 120
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
},
{
"__type__": "57aabs7TE1J5obTAZczc+64",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"atk_x": 20,
"atk_y": 40,
"runType": 2,
"endType": 0,
"speed": 720,
"time": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"enabledContactListener": true,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 1,
"_allowSleep": false,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": true,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_size": {
"__type__": "cc.Size",
"width": 120,
"height": 120
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "0ad94bf2-3b2d-4f80-8500-68c5e33e2ed1",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "0ad94bf2-3b2d-4f80-8500-68c5e33e2ed1",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "7f30cd3c-57d1-476a-96bb-02c4eb082576",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "atk4"
}
}

View File

@@ -0,0 +1,375 @@
[
{
"__type__": "cc.Prefab",
"_name": "atk5",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "atk5",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
}
],
"_prefab": {
"__id__": 18
},
"_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": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "Node",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_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.3,
"y": 1.1,
"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__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 96
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93pNmvtQlLSqtTgIepyEmA"
},
{
"__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__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "12eKc8gltBz50frJCS5+ww"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
"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": 120,
"height": 120
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
},
{
"__type__": "57aabs7TE1J5obTAZczc+64",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"atk_x": 20,
"atk_y": 40,
"runType": 2,
"endType": 0,
"speed": 720,
"time": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"enabledContactListener": true,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 1,
"_allowSleep": false,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": true,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_size": {
"__type__": "cc.Size",
"width": 120,
"height": 120
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "ba0a95bf-2ea1-4d27-9c4f-cbd970ca558c",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "ba0a95bf-2ea1-4d27-9c4f-cbd970ca558c",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "7227b42b-9cc7-41bd-8858-9d05c4cc758b",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "atk5"
}
}

View File

@@ -282,7 +282,11 @@
},
"atk_x": 10,
"atk_y": 25,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -368,4 +372,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -282,7 +282,11 @@
},
"atk_x": 10,
"atk_y": 25,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -368,4 +372,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -282,7 +282,11 @@
},
"atk_x": 10,
"atk_y": 25,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -368,4 +372,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -253,7 +253,11 @@
},
"atk_x": 0,
"atk_y": 0,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -368,4 +372,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -389,7 +389,11 @@
},
"atk_x": 0,
"atk_y": 0,
"_id": ""
"_id": "",
"runType": 0,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -504,4 +508,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

View File

@@ -250,7 +250,11 @@
},
"atk_x": 10,
"atk_y": 15,
"_id": ""
"_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
},
{
"__type__": "cc.CompPrefabInfo",
@@ -336,4 +340,4 @@
"instance": null,
"targetOverrides": null
}
]
]

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: 926 KiB

After

Width:  |  Height:  |  Size: 982 KiB

View File

@@ -232,7 +232,7 @@
"_top": 0,
"_bottom": 546.004,
"_horizontalCenter": 0,
"_verticalCenter": 143,
"_verticalCenter": -7,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
@@ -276,7 +276,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 783,
"y": 633,
"z": 0
}
},

View File

@@ -104,12 +104,12 @@ export const BuffList: Record<number, BuffConfig> = {
modifiers: [{ attr: Attrs.critical, op: ModOp.Flat, value: 30 }],
info: "暴击率提升 30%,持续 5 秒",
},
// 击退概率强化(限时)
// 击退距离强化(限时)
7013: {
id: 7013, name: "击退爆发", icon: "Stat_Stun_01",
category: BuffCategory.Buff, duration: 5, max_stack: 1,
modifiers: [{ attr: Attrs.knockback_chance, op: ModOp.Flat, value: 30 }],
info: "击退概率提升 30%,持续 5 秒",
info: "击退距离提升 30持续 5 秒",
},
// 穿透强化(限时)
7014: {

View File

@@ -13,7 +13,7 @@ export enum BoxSet {
LETF_END = -360,
RIGHT_END = 360,
//游戏地平线
GAME_LINE = 160,
GAME_LINE = 10,
/** 技能/装备各自独立的上限 */
BOX_MAX = 6,
}
@@ -39,12 +39,12 @@ export enum FightSet {
// BACK_RANG=30,//后退范围
BACK_RANG = 30,//后退范围
FiIGHT_TIME = 30,//战斗时间
// BACK_CHANCE=40,//击退概率
FROST_TIME = 3,//冰冻时间
STUN_TIME = 2,//击晕时间
SKILL_CAST_DELAY = 0.15,
CSKILL_START_X = -340,
CSKILL_START_Y = 30,
/** 护盾数值上限数值减免型1 点护盾吸收 1 点伤害) */
SHIELD_MAX = 5,
WAVE_HEAL_RATE = 0.4, // 回合结束时所有英雄恢复最大生命值的比例(与 DDA 放水兜底分层:回血保节奏,放水防崩盘)
PUNCTURE_DOWN = 50,

View File

@@ -31,7 +31,7 @@ export enum Attrs {
freeze_res = "freeze_res", // 冰冻抗性
stun_chance = "stun_chance", // 击晕概率
stun_res = "stun_res", // 击晕抗性
knockback_chance = "knockback_chance", // 击退概率
knockback_chance = "knockback_chance", // 击退强化(原击退概率,现转换为击退距离增量)
knockback_distance = "knockback_distance", // 击退距离强化
knockback_res = "knockback_res", // 击退抗性
invincible_time = "invincible_time",// 无敌时间

View File

@@ -38,7 +38,7 @@ const ItemCardData: ItemCardRaw[] = [
// ==================== 计时性强化(统一 5s ====================
{ uuid: 9102, skill: 6503, icon: "Sub_Item-FA_WP_Sub_Potion_001_Red", name: "攻击药水", info: "全体友方攻击力提升", cost: 0, weight: 15 },
{ uuid: 9103, skill: 6504, icon: "Sub_Item-FA_WP_Sub_Potion_001_Yellow", name: "暴击药水", info: "全体友方暴击率提升", cost: 0, weight: 15 },
{ uuid: 9104, skill: 6505, icon: "Sub_Item-FA_WP_Sub_Potion_001_Purple", name: "击退药水", info: "全体友方击退概率提升", cost: 0, weight: 12 },
{ uuid: 9104, skill: 6505, icon: "Sub_Item-FA_WP_Sub_Potion_001_Purple", name: "击退药水", info: "全体友方击退距离提升", cost: 0, weight: 12 },
{ uuid: 9105, skill: 6506, icon: "Sub_Item-FA_WP_Sub_Potion_001_Blue", name: "穿透药水", info: "全体友方穿透概率提升", cost: 0, weight: 12 },
{ uuid: 9106, skill: 6507, icon: "Sub_Item-FA_WP_Sub_Potion_001_Red", name: "攻速药水", info: "全体友方攻击速度提升", cost: 0, weight: 15 },
{ uuid: 9107, skill: 6508, icon: "Sub_Item-FA_WP_Sub_Potion_001_Purple", name: "风怒药水", info: "全体友方风怒概率提升", cost: 0, weight: 12 },

View File

@@ -221,7 +221,7 @@ export const SkillDescSet: Record<number, SkillDescConfig> = {
templTimed: "{target}{verb}{value}{unit},持续{dur}秒",
},
6505: {
verb: "击退概率提升", unit: "%", permField: "ap", timedField: "buff_value",
verb: "击退距离提升", unit: "", permField: "ap", timedField: "buff_value",
templPerm: "{target}{verb}{value}{unit}",
templTimed: "{target}{verb}{value}{unit},持续{dur}秒",
},

View File

@@ -1,4 +1,4 @@
// ========== 从 HeroAttrs.ts 导入属性相关定义 ==========
// ========== 从 HeroAttrs.ts 导入属性相关定义 ==========
import { Attrs } from "./HeroAttrs";
export enum HSSet {
@@ -152,7 +152,7 @@ export interface SkillConfig {
crt?: number, // 额外暴击率
frz?: number, // 额外冰冻概率
stun?: number, // 额外击晕概率
bck?: number, // 额外击退概率
bck?: number, // 额外击退强化(击退距离增量)
buff_type?: Attrs, // Buff 类型 (单一职责)
timed_buff_id?: number, // 计时性 buff 配置 id指向 BuffList存在时走 buffManager 而非永久 add_*
buff_value?: number, // 覆写计时 buff 的修饰数值modifiers.value / tick.damage_or_heal由药水卡自定义档位
@@ -363,7 +363,7 @@ export const SkillSet: Record<number, SkillConfig> = {
6406: {
uuid: 6406, name: "击退强化", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
DTType: DTType.single, kind: SkillKind.Support, ap: 1, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退概率限时提升, 持续5秒",
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退距离限时提升, 持续5秒",
},
6407: {
uuid: 6407, name: "距推强化", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
@@ -416,7 +416,7 @@ export const SkillSet: Record<number, SkillConfig> = {
6505: {
uuid: 6505, name: "击退爆发", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
DTType: DTType.single, kind: SkillKind.Support, ap: 0, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退概率提升30%, 持续5秒",
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退距离提升30, 持续5秒",
},
6506: {
uuid: 6506, name: "穿透爆发", sp_name: "buff_wind", icon: "Stat_Tripleshot", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",

View File

@@ -42,6 +42,7 @@ export const MonTypeName: Record<number, string> = {
* 英雄攻击速度分级配置 (数值为攻击间隔cd越小越快)
* 每档3个速度级别Lv1(最快) → Lv2(中间) → Lv3(最慢)
* 6档共18级覆盖所有英雄/怪物的攻击间隔
* 注:最慢档 Slow3/VerySlow 系列上限 1.10s,保证战斗节奏紧凑
*/
export enum AtkSpeedLv {
VeryFast1 = 1, VeryFast2 = 2, VeryFast3 = 3,
@@ -55,22 +56,22 @@ export enum AtkSpeedLv {
export const AtkSpeedSet = {
[AtkSpeedLv.VeryFast1]: { name: "极速++", cd: 0.15 },
[AtkSpeedLv.VeryFast2]: { name: "极速+", cd: 0.20 },
[AtkSpeedLv.VeryFast3]: { name: "极速", cd: 0.30 },
[AtkSpeedLv.Fast1]: { name: "快速++", cd: 0.40 },
[AtkSpeedLv.Fast2]: { name: "快速+", cd: 0.50 },
[AtkSpeedLv.Fast3]: { name: "快速", cd: 0.70 },
[AtkSpeedLv.Normal1]: { name: "中速++", cd: 0.80 },
[AtkSpeedLv.Normal2]: { name: "中速+", cd: 0.90 },
[AtkSpeedLv.Normal3]: { name: "中速", cd: 1.00 },
[AtkSpeedLv.Mid1]: { name: "一般+", cd: 1.10 },
[AtkSpeedLv.Mid2]: { name: "一般", cd: 1.20 },
[AtkSpeedLv.Mid3]: { name: "一般-", cd: 1.30 },
[AtkSpeedLv.Slow1]: { name: "慢", cd: 1.50 },
[AtkSpeedLv.Slow2]: { name: "慢+", cd: 1.60 },
[AtkSpeedLv.Slow3]: { name: "慢++", cd: 1.80 },
[AtkSpeedLv.VerySlow1]: { name: "很慢", cd: 2.30 },
[AtkSpeedLv.VerySlow2]: { name: "很慢+", cd: 2.50 },
[AtkSpeedLv.VerySlow3]: { name: "很慢++", cd: 2.80 },
[AtkSpeedLv.VeryFast3]: { name: "极速", cd: 0.28 },
[AtkSpeedLv.Fast1]: { name: "快速++", cd: 0.36 },
[AtkSpeedLv.Fast2]: { name: "快速+", cd: 0.44 },
[AtkSpeedLv.Fast3]: { name: "快速", cd: 0.52 },
[AtkSpeedLv.Normal1]: { name: "中速++", cd: 0.60 },
[AtkSpeedLv.Normal2]: { name: "中速+", cd: 0.68 },
[AtkSpeedLv.Normal3]: { name: "中速", cd: 0.76 },
[AtkSpeedLv.Mid1]: { name: "一般+", cd: 0.84 },
[AtkSpeedLv.Mid2]: { name: "一般", cd: 0.92 },
[AtkSpeedLv.Mid3]: { name: "一般-", cd: 1.00 },
[AtkSpeedLv.Slow1]: { name: "慢", cd: 1.05 },
[AtkSpeedLv.Slow2]: { name: "慢+", cd: 1.08 },
[AtkSpeedLv.Slow3]: { name: "慢++", cd: 1.10 },
[AtkSpeedLv.VerySlow1]: { name: "很慢", cd: 1.10 },
[AtkSpeedLv.VerySlow2]: { name: "很慢+", cd: 1.10 },
[AtkSpeedLv.VerySlow3]: { name: "很慢++", cd: 1.10 },
};
export const HeroPos = {
@@ -85,10 +86,11 @@ export const FormationPointX = {
[HType.Long]: 100,
} as const;
/** 各攻击定位的默认攻击距离像素近战100 / 中程300 / 远程450英雄配置了 dis 时优先取配置值 */
export const HeroDisVal: Record<HType.Melee | HType.Mid | HType.Long, number> = {
[HType.Melee]: 120,
[HType.Mid]: 720,
[HType.Long]: 720,
[HType.Melee]: 100,
[HType.Mid]: 300,
[HType.Long]: 450,
}
export const resolveFormationTargetX = (fac: FacSet, type: HType): number => {
@@ -403,7 +405,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5001 铁壁战士 — atked 护盾标杆原5011
5001: {
uuid: 5001, name: "铁壁战士", path: "hk1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 300, ap: 22,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
atked: {
6301: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 2 } },
@@ -416,7 +418,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5002 影袭刺客 — atking 自身攻击
5002: {
uuid: 5002, name: "影袭刺客", path: "hc1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 300, ap: 22,
skills: { 6003: { uuid: 6003, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
atking: {
6401: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 1 } },
@@ -429,7 +431,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5003 遗志将军 — dead 全队攻击
5003: {
uuid: 5003, name: "遗志将军", path: "hk2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 300, ap: 22,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
dead: {
6401: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
@@ -442,7 +444,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5004 破晓先知 — fstart 全队攻击
5004: {
uuid: 5004, name: "破晓先知", path: "hm1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 143, ap: 32,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
fstart: {
6401: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
@@ -455,7 +457,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5005 战歌法师 — field 攻击光环
5005: {
uuid: 5005, name: "战歌法师", path: "hm2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 143, ap: 32,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
field: [
{ lv: 1, uuids: [{ uuid: 7002, value: 0.1 }] },
{ lv: 3, uuids: [{ uuid: 7002, value: 0.2 }] },
@@ -469,7 +471,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5101 不死斗士 — atked 回血(更高数值)
5101: {
uuid: 5101, name: "不死斗士", path: "hk3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 600, ap: 46,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
atked: {
6302: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 200 } },
@@ -482,7 +484,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5102 致命杀手 — atking 暴击(更高数值)
5102: {
uuid: 5102, name: "致命杀手", path: "hc2", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 600, ap: 46,
skills: { 6003: { uuid: 6003, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
atking: {
6403: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
@@ -495,7 +497,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5103 殉道勇士 — dead 全队护盾(更高数值)
5103: {
uuid: 5103, name: "殉道勇士", path: "hk4", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 600, ap: 46,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
dead: {
6301: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
@@ -508,7 +510,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5104 圣光祭司 — fstart 全队护盾(更高数值)
5104: {
uuid: 5104, name: "圣光祭司", path: "hm3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 287, ap: 64,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow1].cd, ccd: 0 } },
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
fstart: {
6301: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
@@ -532,7 +534,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5106 破甲射手 — atking 自身穿透伤害计时buff增强
5106: {
uuid: 5106, name: "破甲射手", path: "ha1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 287, ap: 64,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
atking: {
6512: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7022, buff_value: 5, buff_duration: 5 } },
@@ -548,7 +550,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5201 怒血勇士 — atked 全队攻击(范围扩大)
5201: {
uuid: 5201, name: "怒血勇士", path: "hk5", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 900, ap: 68,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
atked: {
6401: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 1 } },
@@ -574,7 +576,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5203 传承卫士 — dead 全队生命(范围扩大)
5203: {
uuid: 5203, name: "传承卫士", path: "hk1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 900, ap: 68,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
dead: {
6402: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 10 } },
@@ -587,7 +589,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5204 晨光贤者 — fstart 全队暴击(范围扩大)
5204: {
uuid: 5204, name: "晨光贤者", path: "hm2", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 430, ap: 96,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
fstart: {
6403: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
@@ -614,7 +616,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5301 复仇守护者 — atked 全队暴击(范围+数值)
5301: {
uuid: 5301, name: "复仇守护者", path: "hk2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 1200, ap: 90,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow1].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
atked: {
6403: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
@@ -627,7 +629,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5302 破灭游侠 — atking 队友暴伤(范围+数值)
5302: {
uuid: 5302, name: "破灭游侠", path: "ha2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 573, ap: 128,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal1].cd, ccd: 0 } },
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
atking: {
6404: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 2, timed_buff_id: 7020, buff_value: 5, buff_duration: 5 } },
@@ -640,7 +642,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5303 亡语刺客 — dead 全队暴击+暴伤(双效果)
5303: {
uuid: 5303, name: "亡语刺客", path: "hc3", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 1200, ap: 90,
skills: { 6003: { uuid: 6003, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
dead: {
6403: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
@@ -690,7 +692,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5401 不灭战魂 — atked 攻击 + revive双技能
5401: {
uuid: 5401, name: "不灭战魂", path: "hk3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 1500, ap: 114,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
atked: {
6401: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 2 } },
@@ -726,7 +728,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5403 凤凰祭司 — dead 攻击+护盾 + revive双技能
5403: {
uuid: 5403, name: "凤凰祭司", path: "hk4", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 1500, ap: 114,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
dead: {
6401: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 5 } },
@@ -749,7 +751,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 5404 涅槃贤者 — fstart 护盾+攻击 + revive双技能
5404: {
uuid: 5404, name: "涅槃贤者", path: "hm3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 717, ap: 160,
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } },
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
fstart: {
6301: [
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 5 } },
@@ -789,76 +791,76 @@ export const HeroInfo: Record<number, heroInfo> = {
},
/*
*=============怪物配置列表================
* 基础近战型(lv:1) SPEED:800 |AP:12 | HP:360 | skills[0].cd=0.65
* 重型坦克型(lv:1) SPEED:800 |AP:30 | HP:1050 | skills[0].cd=2
* 远程dps(lv:1) SPEED:800 |AP:45 | HP:240 | skills[0].cd=1.5
* 远程辅助(lv:1) SPEED:800 |AP:20 | HP:240 | skills[0].cd=1
* 精英 (lv:1) SPEED:800 |AP:20 | HP:4500 | skills[0].cd=1
*=============怪物配置列表================
* 性能基准1 级怪物与 1 级英雄card_lv=1单体性能持平 ——
* 近战怪镜像 5001hp300/ap22/cd0.92),远程怪镜像 5004hp160/ap28/cd0.84
* 重型/Boss 以更高 hp 换取更低 DPS承担"墙"与"压场"定位。
* 运行时最终属性 = 本表基础值 × RogueConfig.MON_HP_MUL / MON_AP_MUL
* Boss 额外 × BOSS_HP_BONUS_MUL校准目标整场战斗双方对砍 ≈ 30s。
*/
// 基础怪物 (全部固定点位站桩攻击HType仅决定是前排还是后排)
// 前排怪物 (站在前排,承受更多伤害) — v5: TD节奏CD多而弱爽感设计
// 前排怪物 — 近战镜像 1 级英雄基准hp300/ap22/cd0.92),变体微分定位
6001: {
uuid: 6001, name: "兽人战士", path: "m1", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 286, ap: 10, speed: 70,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "基础前排怪"
uuid: 6001, name: "兽人战士", path: "m1", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 300, ap: 22, speed: 70,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "基础前排怪, 与1级近战英雄持平"
},
6002: {
uuid: 6002, name: "兽人精锐战士", path: "m2", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 390, ap: 14, speed: 110,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "进阶前排怪, 更快更痛"
uuid: 6002, name: "兽人精锐战士", path: "m2", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 340, ap: 26, speed: 110,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } }, info: "进阶前排怪, 更快更痛"
},
6003: {
uuid: 6003, name: "兽人重装兵", path: "m3", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Heavy, hp: 1105, ap: 20, speed: 50,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "重型坦克怪, 高HP慢攻"
uuid: 6003, name: "兽人重装兵", path: "m3", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Heavy, hp: 800, ap: 14, speed: 50,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "重型坦克怪, 高HP低攻承伤位"
},
// 后排怪物 (站在后排,输出更高)
// 后排怪物 — 远程镜像 1 级法师基准hp160/ap28/cd0.84),变体微分定位
6004: {
uuid: 6004, name: "兽人射手", path: "m4", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Long, hp: 247, ap: 35, speed: 70,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排高DPS怪"
uuid: 6004, name: "兽人射手", path: "m4", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Long, hp: 160, ap: 28, speed: 70,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } }, info: "后排输出怪, 与1级远程英雄持平"
},
6005: {
uuid: 6005, name: "兽人刺客", path: "m5", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Assassin, hp: 273, ap: 38, speed: 130,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "高AP快速攻击刺客"
uuid: 6005, name: "兽人刺客", path: "m5", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Assassin, hp: 130, ap: 26, speed: 130,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } }, info: "高攻速低HP刺客"
},
// 特殊位怪物
6006: {
uuid: 6006, name: "骷髅领主", path: "m6", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.MeleeBoss, hp: 6500, ap: 20, speed: 60,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排MiniBoss级坦克"
uuid: 6006, name: "骷髅领主", path: "m6", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.MeleeBoss, hp: 900, ap: 20, speed: 60,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "前排MiniBoss级坦克"
},
6007: {
uuid: 6007, name: "兽人术士", path: "m7", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Support, hp: 390, ap: 24, speed: 70,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排法师怪, 魔法攻击"
uuid: 6007, name: "兽人术士", path: "m7", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Support, hp: 240, ap: 18, speed: 70,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } }, info: "后排辅助法师怪"
},
6008: {
uuid: 6008, name: "兽人火法", path: "m8", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Summoner, hp: 351, ap: 32, speed: 70,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "后排高输出法师怪"
uuid: 6008, name: "兽人火法", path: "m8", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Summoner, hp: 220, ap: 24, speed: 70,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "后排召唤系法师怪"
},
// BOSS怪物 — Boss节奏1.2-1.5s删除不存在的6206技能
// BOSS怪物 — 与6006同基准以高HP压场血量经 BOSS_HP_BONUS_MUL 单独放大
6101: {
uuid: 6101, name: "兽人首领-双刀战士", path: "mb1", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 2470, ap: 30, speed: 120,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排Boss, 攻速"
uuid: 6101, name: "兽人首领-双刀战士", path: "mb1", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1200, ap: 24, speed: 120,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } }, info: "前排Boss, 攻速"
},
6102: {
uuid: 6102, name: "兽人首领-斧头战士", path: "mb2", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 9750, ap: 26, speed: 60,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "前排Boss, 超高HP"
uuid: 6102, name: "兽人首领-斧头战士", path: "mb2", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1500, ap: 18, speed: 60,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "前排Boss, 超高HP"
},
6103: {
uuid: 6103, name: "兽人首领-魔法师", path: "mb3", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 2925, ap: 38, speed: 110,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "后排法系Boss, 高AP"
uuid: 6103, name: "兽人首领-魔法师", path: "mb3", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 28, speed: 110,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "后排法系Boss"
},
6104: {
uuid: 6104, name: "兽人首领-射手", path: "mb4", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 8840, ap: 30, speed: 70,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "后排位Boss, 均衡型"
uuid: 6104, name: "兽人首领-射手", path: "mb4", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1100, ap: 24, speed: 70,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } }, info: "后排位Boss, 均衡型"
},
6105: {
uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 3380, ap: 42, speed: 110,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排高AP Boss"
uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 30, speed: 110,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "后排高AP Boss"
},
6106: {
uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 11700, ap: 26, speed: 130,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排终极Boss, 最高HP+高速"
uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1800, ap: 20, speed: 130,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "前排终极Boss, 最高HP+高速"
},
@@ -1160,7 +1162,9 @@ function calcSupportEffectValue(config: SkillConfig, apEff: number, cd: number,
return (value / 100) * (apEff / cd) * 0.05 * weights.W_DPS_PER_SEC * seconds;
case Attrs.stun_chance:
case Attrs.freeze_chance:
return value * weights.W_control;
case Attrs.knockback_chance:
// 击退强化已改为击退距离增量,按距离价值折算
return value * weights.W_control;
case Attrs.speed:
return (value / 100) * (apEff / cd) * weights.W_DPS_PER_SEC * seconds;
@@ -1182,7 +1186,8 @@ function calcSkillEffectValue(skill: SkillConfig, overrides: SkillOverrides | un
case SkillKind.Heal:
return ((config.ap || 0) / 100) * apEff * weights.W_heal;
case SkillKind.Shield:
return (config.ap || 0) * weights.expectedEnemyDmg * weights.W_shield;
// 护盾为数值减免型1 点护盾 ≈ 1 点预期伤害减免
return (config.ap || 0) * weights.W_shield;
case SkillKind.Support:
return calcSupportEffectValue(config, apEff, cd, seconds);
default:

View File

@@ -5,7 +5,7 @@ import { smc } from "../common/SingletonModuleComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
import { HeroInfo, HeroPos, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, calcGrowBonus } from "../common/config/heroSet";
import { HeroInfo, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, calcGrowBonus, HeroDisVal } from "../common/config/heroSet";
import { GameEvent } from "../common/config/GameEvent";
import { SkillTriggerType, HType } from "../common/config/heroSet";
import { SkillTriggerHelper } from "./SkillTriggerHelper";
@@ -122,8 +122,8 @@ export class Hero extends ecs.Entity {
model.grow_type = hero.grow_type;
model.role = hero.role;
model.fac = FacSet.HERO;
// 攻击距离:配置优先;未配置按攻击定位默认值近战180 / 远程600
model.dis = hero.dis ?? (hero.type === HType.Melee ? 180 : 600);
// 攻击距离:配置优先;未配置按攻击定位默认值近战100 / 中程300 / 远程450
model.dis = hero.dis ?? HeroDisVal[hero.type as HType.Melee | HType.Mid | HType.Long];
model.posIndex = posIndex;
if (posIndex >= 0) {
smc.mission.heroGrid[posIndex] = this.eid;

View File

@@ -146,15 +146,15 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
}
}
mLogger.log(this.debugMode, 'HeroAtkSystem', " after crit", damage)
// 护盾吸收
// 护盾数值减免1 点护盾吸收 1 点伤害,扣完为止,溢出部分继续扣血)
const shieldResult = this.absorbShield(TAttrsComp, damage);
damage = shieldResult.remainingDamage;
if (shieldResult.absorbedDamage > 0) {
// 【评分系统 - 防御分】统计护盾成功抵挡伤害的次数
smc.vmdata.scores.shield_block_count += 1;
// 【评分系统 - 防御分】统计护盾累计吸收的伤害量
smc.vmdata.scores.shield_block_count += Math.floor(shieldResult.absorbedDamage);
}
mLogger.log(this.debugMode, 'HeroAtkSystem', " after shield", damage)
// 显示护盾格挡飘字
// 显示护盾减免飘字
if (shieldResult.absorbedDamage > 0 && targetView) {
targetView.shield_tip(shieldResult.absorbedDamage);
}
@@ -175,11 +175,6 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
TAttrsComp.atked_count++;
this.checkAndTriggerAtkedSkills(TAttrsComp, targetView);
// 受击者产生击退效果
// if (damage > 0 && targetView) {
// targetView.back();
// }
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 英雄${TAttrsComp.hero_name} (uuid: ${TAttrsComp.hero_uuid}) 受到 eid:${casterEid} 的 伤害 ${damage},${isCrit ? "暴击" : "普通"}攻击,技能ID ${damageEvent.s_uuid}`);
// 冰冻判定
@@ -190,13 +185,14 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
const stunChance = (damageEvent.Attrs[Attrs.stun_chance] || 0) - TAttrsComp.getFinalStunRes();
const isStun = !TAttrsComp.isStun() && this.checkChance(stunChance);
// 击退判定
const knockbackChance = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.knockback_res || 0);
const isKnockback = this.checkChance(knockbackChance);
// 击退:受击默认后退,击退属性不再作为概率,而是转换为后退距离的增量
// 击退增量 = 施法者击退强化(原击退概率)- 被攻击者击退抗性 + 击退距离强化
const knockbackBonus = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.getFinalKnockbackRes() || 0);
const knockbackExtra = Math.max(0, knockbackBonus + (damageEvent.Attrs[Attrs.knockback_distance] || 0));
// ✅ 触发视图层表现(伤害数字、受击动画、冰冻、击晕、击退)
if (targetView) {
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, false);
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, knockbackExtra);
targetView.playEnd(skillConf.endAnm);
if (isFrost) {
TAttrsComp.toFrost();
@@ -212,9 +208,6 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
smc.vmdata.scores.stun_count++;
}
}
if (isKnockback) {
targetView.back(damageEvent.Attrs[Attrs.knockback_distance] || 0);
}
}
@@ -395,7 +388,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
* 用于所有概率相关的判定:
* - 暴击率判定
* - 闪避率判定
* - 击退概率判定
* - 冰冻/击晕概率判定
* - 其他特殊效果概率
*
* @param rate 概率值0-100的百分比
@@ -420,29 +413,26 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
/**
* 护盾吸收伤害
*
* 护盾吸收逻辑
* 1. 护盾值大于 0 时,抵挡本次伤害(免伤)
* 2. 每次抵挡消耗 1 点护盾值
* 3. 护盾归零时,重置护盾最大值属性
*
* 护盾数值减免伤害
*
* 护盾为数值减免型
* 1. 1 点护盾吸收 1 点伤害
* 2. 护盾扣减实际吸收量,溢出部分继续结算到血量
*
* @param TAttrsComp 被攻击者的属性组件(包含当前护盾值)
* @param damage 原始伤害值
* @returns {remainingDamage, absorbedDamage} 剩余伤害值和被格挡的伤害值
* @returns {remainingDamage, absorbedDamage} 剩余伤害值和被护盾减免的伤害值
*/
private absorbShield(TAttrsComp: HeroAttrsComp, damage: number): { remainingDamage: number, absorbedDamage: number } {
if (TAttrsComp.shield <= 0) {
mLogger.log(this.debugMode, 'HeroAtkSystem', " 护盾值小于等于0无法吸收伤害");
return { remainingDamage: damage, absorbedDamage: 0 };
}
TAttrsComp.shield = Math.max(0, TAttrsComp.shield - 1);
if (TAttrsComp.shield <= 0) {
TAttrsComp.shield = 0;
}
const absorbed = Math.min(TAttrsComp.shield, damage);
TAttrsComp.shield = Math.max(0, TAttrsComp.shield - absorbed);
TAttrsComp.dirty_shield = true;
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 护盾抵挡1次伤害,剩余次数 ${TAttrsComp.shield}`);
return { remainingDamage: 0, absorbedDamage: damage };
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 护盾减免 ${absorbed.toFixed(1)}伤害,剩余护盾 ${TAttrsComp.shield.toFixed(1)}`);
return { remainingDamage: damage - absorbed, absorbedDamage: absorbed };
}
/**

View File

@@ -82,9 +82,9 @@ export class HeroAttrsComp extends ecs.Comp {
freeze_res: number = 0; // 冰冻抗性
stun_chance: number = 0; // 击晕概率
stun_res: number = 0; // 击晕抗性
knockback_chance: number = 0; // 击退概率
knockback_chance: number = 0; // 击退强化(原击退概率,现转换为击退距离增量)
knockback_distance: number = 0; // 击退距离强化
knockback_res: number = 0; // 击退抗性
knockback_res: number = 0; // 击退抗性(削减击退距离增量)
crit_damage: number = 0; // 额外暴击伤害
puncture_chance: number = 0; // 穿透概率
puncture_dmg_bonus: number = 0; // 穿透后伤害增量(百分点,乘算加成)
@@ -153,11 +153,11 @@ export class HeroAttrsComp extends ecs.Comp {
const addValue = Math.max(0, Math.floor(value));
if (addValue <= 0) return;
this.shield += addValue;
this.shield = Math.min(this.shield, FightSet.SHIELD_MAX); // 限制护盾最大层数
this.shield = Math.min(this.shield, FightSet.SHIELD_MAX); // 限制护盾值上限(数值减免型)
if (this.shield < 0) this.shield = 0;
this.dirty_shield = true; // 标记护盾需要更新
if (this.debugMode) {
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾次数变更: ${this.hero_name}, 变化=${addValue}, ${Math.floor(oldShield)} -> ${Math.floor(this.shield)}`);
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾变更: ${this.hero_name}, 变化=${addValue}, ${Math.floor(oldShield)} -> ${Math.floor(this.shield)}`);
}
}
add_hp_max(value: number) {
@@ -449,7 +449,7 @@ export class HeroAttrsComp extends ecs.Comp {
return base + mods.flatSum + mods.pctSum;
}
/** 最终击退概率 = 基础击退 + 限时修饰(加法模型) */
/** 最终击退强化 = 基础击退强化 + 限时修饰(加法模型,转换为击退距离增量 */
public getFinalKnockbackChance(): number {
const mods = this.aggregateTimedMods(Attrs.knockback_chance);
return this.knockback_chance + mods.flatSum + mods.pctSum;

View File

@@ -0,0 +1,173 @@
import { _decorator, ProgressBar, Sprite, UIOpacity, Vec3, v3, tween, Tween, Color, clamp } 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 { HeroAttrsComp } from "./HeroAttrsComp";
import { FacSet, FightSet } from "../common/config/GameSet";
const { ccclass, property } = _decorator;
/**
* 英雄头顶状态栏组件
*
* 挂载在英雄节点下的 "top" 子节点上,独立管理:
* - 血条进度ProgressBar
* - 护盾数值条ProgressBartop/shield 节点,显示 shield / SHIELD_MAX
* - 透明度激活 / 闲置
* - 受击抖动动画
*
* 数据流HeroViewComp 将 HeroAttrsComp 引用传入,本组件通过 model 读取属性。
* 注意:英雄根节点下的 "shielded" 是护盾特效动画节点,由 HeroViewComp 管理,不在本组件职责内。
*/
@ccclass('HeroTopBarComp')
@ecs.register('HeroTopBar', false)
export class HeroTopBarComp extends CCComp {
// ==================== @property 绑定(编辑器拖拽) ====================
@property({ type: ProgressBar, tooltip: "血条进度条" })
private hpProgressBar: ProgressBar = null!;
@property({ type: ProgressBar, tooltip: "护盾数值条top 下的 shield 节点)" })
private shieldProgressBar: ProgressBar = null!;
@property({ type: Sprite, tooltip: "血条填充 Sprite用于设置阵营颜色" })
private hpBarSprite: Sprite = null!;
// ==================== 内部引用 ====================
private topOpacity: UIOpacity = null!;
private basePos: Vec3 = v3();
// ==================== 配置 ====================
private hp_height: number = 90;
private boss_hp_height: number = 160;
// ==================== 透明度 ====================
private readonly barIdleOpacity: number = 153;
private readonly barActiveOpacity: number = 255;
private readonly idleOpacityDelay: number = 0.25;
/** 外部赋值:英雄数据模型 */
private _model: HeroAttrsComp = null!;
public get model(): HeroAttrsComp { return this._model; }
public set model(value: HeroAttrsComp) { this._model = value; }
private readonly restoreBarIdleOpacity = () => {
this.setTopBarOpacity(false);
};
/**
* 初始化 / 对象池复用时重置
* @param model 英雄属性组件
* @param direction 显示方向1 或 -1
*/
public init(model: HeroAttrsComp, direction: number = 1): void {
this._model = model;
// UIOpacity 无 @property 绑定,运行时获取或添加
this.topOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
// 方向取 abs避免对象池复用时 scale 累乘导致血条反转
this.node.setScale(direction * Math.abs(this.node.scale.x), 1 * this.node.scale.y);
// 位置
const height = model.is_boss ? this.boss_hp_height : this.hp_height;
this.node.setPosition(0, height, 0);
this.basePos = this.node.position.clone();
// 血条颜色(英雄阵营为绿色)
if (model.fac === FacSet.HERO && this.hpBarSprite) {
this.hpBarSprite.color = new Color("#2ECC71");
}
// 确保血条等 UI 始终在最上层
this.node.setSiblingIndex(999);
// 重置显示状态
this.node.getChildByName("hp")!.active = true;
this.node.getChildByName("cd")!.active = false;
this.node.getChildByName("lv")!.active = false;
this.node.active = true;
this.setTopBarOpacity(false);
this.hpShow();
this.shieldShow(model.shield);
}
/** 更新血量进度条 */
public hpShow(): void {
if (!this._model || !this.hpProgressBar) return;
const hp = this._model.hp;
const hpMax = this._model.hp_max;
let targetProgress = hpMax > 0 ? hp / hpMax : 0;
targetProgress = clamp(targetProgress, 0, 1);
const prevProgress = this.hpProgressBar.progress;
if (targetProgress < prevProgress) {
this.activateTopBar();
this.playHpBarShake();
}
this.hpProgressBar.progress = targetProgress;
if (targetProgress > prevProgress) {
this.setTopBarOpacity(false);
}
}
/** 更新护盾数值条(百分比 = shield / SHIELD_MAX */
public shieldShow(shield: number = 0): void {
if (!this.shieldProgressBar) return;
const progress = FightSet.SHIELD_MAX > 0 ? shield / FightSet.SHIELD_MAX : 0;
this.shieldProgressBar.progress = clamp(progress, 0, 1);
// 护盾节点显隐
this.shieldProgressBar.node.active = shield > 0;
}
/** 激活顶栏(高亮 + 延迟恢复闲置透明度) */
public activateTopBar(): void {
this.setTopBarOpacity(true);
this.unschedule(this.restoreBarIdleOpacity);
this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay);
}
/** 设置顶栏透明度 */
public setTopBarOpacity(isActive: boolean): void {
if (!this.node || !this.node.isValid) return;
if (this.topOpacity) {
this.topOpacity.opacity = isActive ? this.barActiveOpacity : this.barIdleOpacity;
}
this.node.active = true;
}
/** 显示顶栏 */
public show(): void {
if (this.node && this.node.isValid) this.node.active = true;
}
/** 隐藏顶栏 */
public hide(): void {
if (this.node && this.node.isValid) this.node.active = false;
}
/** 血条受击抖动动画 */
private playHpBarShake(): void {
if (!this.node || !this.node.isValid) return;
Tween.stopAllByTarget(this.node);
this.node.setPosition(this.basePos);
tween(this.node)
.by(0.04, { position: v3(-3, 0, 0) })
.by(0.04, { position: v3(6, 0, 0) })
.by(0.04, { position: v3(-5, 0, 0) })
.by(0.04, { position: v3(2, 0, 0) })
.call(() => {
this.node.setPosition(this.basePos);
})
.start();
}
/** 对象池回收 / 组件重置时调用 */
public reset(): void {
this.unscheduleAllCallbacks();
if (this.node && this.node.isValid) {
Tween.stopAllByTarget(this.node);
this.node.setPosition(this.basePos);
}
}
}

View File

@@ -2,7 +2,7 @@
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "a3113d69-645e-4a4b-bf68-a434fcbd6d8d",
"uuid": "c4181fc5-1168-4a6d-8d03-d0f2dd0b22a2",
"files": [],
"subMetas": {},
"userData": {}

View File

@@ -1,8 +1,9 @@
import { Vec3, _decorator, v3, Collider2D, Contact2DType, Label, Node, Prefab, instantiate, ProgressBar, Component, Material, Sprite, math, clamp, Game, tween, Tween, Color, BoxCollider2D, UITransform, UIOpacity } from "cc";
import { Vec3, _decorator, v3, Collider2D, Contact2DType, Node, Prefab, instantiate, tween, Tween, BoxCollider2D, UITransform } 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 { mLogger } from "../common/Logger";
import { HeroSpine } from "./HeroSpine";
import { HeroTopBarComp } from "./HeroTopBarComp";
import { BoxSet, FacSet, FightSet, NumberFormatter, TooltipTypes } from "../common/config/GameSet";
import { smc } from "../common/SingletonModuleComp";
import { SkillSet, } from "../common/config/SkillSet";
@@ -37,21 +38,10 @@ export class HeroViewComp extends CCComp {
realDeadTime: number = 0.1
deadCD: number = 0
monDeadTime: number = 0.1
hp_height: number = 90
boss_hp_height: number = 160
// 血条显示相关
lastBarUpdateTime: number = 0; // 最后一次血条/蓝条/护盾更新时间
// ==================== UI 节点引用 ====================
private top_node: Node = null!;
private topOpacity: UIOpacity = null!;
private topBasePos: Vec3 = v3();
private lvLabel: Label | null = null; // 等级显示文本
private readonly barIdleOpacity: number = 153;
private readonly barActiveOpacity: number = 255;
private readonly idleOpacityDelay: number = 0.25;
private readonly restoreBarIdleOpacity = () => {
this.setTopBarOpacity(false);
};
private topBar: HeroTopBarComp = null!;
// ==================== 直接访问 HeroAttrsComp ====================
get model() {
@@ -96,67 +86,26 @@ export class HeroViewComp extends CCComp {
this.lastBarUpdateTime = 0;
this.status_change("idle");
// 初始化 UI 节点
this.initUINodes();
// 初始化头顶状态栏
const topNode = this.node.getChildByName("top");
this.topBar = topNode.getComponent(HeroTopBarComp) || topNode.addComponent(HeroTopBarComp);
this.topBar.init(this.model, this.scale);
/** 方向 */
this.node.setScale(this.scale * Math.abs(this.node.scale.x), 1 * this.node.scale.y); // 确保 scale.x 为正后再乘方向
// top_node 取 abs 避免对象池复用时 scale 累乘导致血条方向反转
this.top_node.setScale(this.scale * Math.abs(this.top_node.scale.x), 1 * this.top_node.scale.y);
/* 显示角色血*/
this.top_node.getChildByName("hp").active = true;
this.top_node.getChildByName("cd").active = false;
this.top_node.getChildByName("shield").active = false;
this.top_node.getChildByName("lv").active = true;
this.top_node.active = true;
this.setTopBarOpacity(false);
// 初始化等级显示
this.lv_show();
// 🔥 重置血条 UI 显示状态
// 🔥 重置描边
if (this.model) {
this.hp_show();
// 根据英雄模型中的等级数据设置描边
// 注意HeroViewComp 是挂在 node 上的,而 FlashSprite 可能挂在子节点(如 Sprite/anm 节点)
let flashSprite = this.node.getComponent(FlashSprite);
if (!flashSprite) {
// 如果当前节点没有,尝试在所有子节点中查找
flashSprite = this.node.getComponentInChildren(FlashSprite);
}
if (flashSprite) {
flashSprite.setOutlineByLevel(this.model.lv);
}
}
}
/** 初始化 UI 节点引用 */
private initUINodes() {
this.top_node = this.node.getChildByName("top");
this.topOpacity = this.top_node.getComponent(UIOpacity) || this.top_node.addComponent(UIOpacity);
this.top_node.setPosition(0, this.hp_height, 0);
if (this.model.is_boss) this.top_node.setPosition(0, this.boss_hp_height, 0);
this.topBasePos = this.top_node.position.clone();
const hpNode = this.top_node.getChildByName("hp");
if (this.model.fac == FacSet.HERO) {
hpNode.getChildByName("Bar").getComponent(Sprite).color = new Color("#2ECC71")
}
// 确保血条等UI始终在最上层显示
this.top_node.setSiblingIndex(999);
// 缓存等级显示 Labellv 节点下的 Label 子节点)
const lvNode = this.top_node.getChildByName("lv");
if (lvNode) {
this.lvLabel = lvNode.getChildByName("Label")?.getComponent(Label) ?? null;
}
}
/**
* View 层每帧更新
* 注意:数据更新逻辑已移到 HeroAttrSystem这里只负责显示
@@ -181,103 +130,21 @@ export class HeroViewComp extends CCComp {
// ✅ 按需更新 UI脏标签模式- 只在属性变化时更新
if (this.model.dirty_hp) {
this.hp_show();
this.topBar.hpShow();
this.model.dirty_hp = false;
}
if (this.model.dirty_shield) {
this.show_shield(this.model.shield);
this.topBar.shieldShow(this.model.shield);
this.model.dirty_shield = false;
}
if (this.model.dirty_lv) {
this.lv_show();
this.model.dirty_lv = false;
}
}
public cd_show() {
return;
}
/** 显示护盾 */
private show_shield(shield: number = 0) {
this.lastBarUpdateTime = Date.now() / 1000;
this.node.getChildByName("shielded").active = shield > 0;
}
/** 显示血量 */
private hp_show() {
this.lastBarUpdateTime = Date.now() / 1000;
// 不再基于血量是否满来决定显示状态,只更新进度条
let hp = this.model.hp;
let hp_max = this.model.hp_max;
// mLogger.log(this.debugMode, 'HeroViewComp', "hp_show",hp,hp_max)
let targetProgress = hp_max > 0 ? hp / hp_max : 0;
targetProgress = clamp(targetProgress, 0, 1);
let hpNode = this.top_node.getChildByName("hp");
let hpProgressBar = hpNode.getComponent(ProgressBar);
const prevProgress = hpProgressBar.progress;
if (targetProgress < hpProgressBar.progress) {
this.activateTopBar();
this.playHpBarShake();
}
hpProgressBar.progress = targetProgress;
if (targetProgress > prevProgress) {
this.setTopBarOpacity(false);
}
}
private isFullHpAndNoShield(): boolean {
if (!this.model) return false;
if (this.model.hp_max <= 0) return false;
return this.model.hp >= this.model.hp_max;
}
/** 更新等级显示 */
private lv_show() {
if (!this.lvLabel || !this.model) return;
this.lvLabel.string = String(this.model.lv);
}
private setTopBarOpacity(isActive: boolean) {
if (!this.top_node || !this.top_node.isValid) return;
if (this.topOpacity) {
this.topOpacity.opacity = this.barActiveOpacity;
}
if (isActive) {
this.top_node.active = true;
return;
}
this.top_node.active = true;
}
private activateTopBar() {
this.setTopBarOpacity(true);
this.unschedule(this.restoreBarIdleOpacity);
this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay);
}
private playHpBarShake() {
if (!this.top_node || !this.top_node.isValid) return;
Tween.stopAllByTarget(this.top_node);
this.top_node.setPosition(this.topBasePos);
tween(this.top_node)
.by(0.04, { position: v3(-3, 0, 0) })
.by(0.04, { position: v3(6, 0, 0) })
.by(0.04, { position: v3(-5, 0, 0) })
.by(0.04, { position: v3(2, 0, 0) })
.call(() => {
this.top_node.setPosition(this.topBasePos);
})
.start();
}
/** 升级特效 */
public lv_up() {
this.spawnTimedFx("game/skill/buff/buff_lvup", this.node, 1.0);
@@ -349,7 +216,7 @@ export class HeroViewComp extends CCComp {
Tooltip.load(pos, type, value, s_uuid, this.node.parent, 1, this.model?.fac ?? FacSet.MON);
}
/** 护盾格挡提示 */
/** 护盾减免提示 */
shield_tip(absorbed: number) {
this.hp_tip(TooltipTypes.shield, NumberFormatter.formatNumber(Math.max(0, Math.floor(absorbed))));
}
@@ -432,7 +299,7 @@ export class HeroViewComp extends CCComp {
}
add_shield(shield: number) {
// 护盾数据更新由 Model 层处理,这里只负责视图表现
if (this.model && this.model.shield > 0) this.show_shield(this.model.shield);
if (this.model && this.model.shield > 0) this.topBar.shieldShow(this.model.shield);
}
health(hp: number = 0) {
@@ -458,7 +325,7 @@ export class HeroViewComp extends CCComp {
}
// 恢复UI
this.top_node.active = true;
this.topBar.show();
this.status_change("idle");
@@ -504,7 +371,7 @@ export class HeroViewComp extends CCComp {
}
// 隐藏UI
this.top_node.active = false;
this.topBar.hide();
// 在销毁实体前先禁用碰撞体,从源头减少"尸体"参与碰撞
const collider = this.node.getComponent(Collider2D);
@@ -525,9 +392,10 @@ export class HeroViewComp extends CCComp {
})
.start();
}
do_atked(damage: number, isCrit: boolean, s_uuid: number, isBack: boolean = false) {
/** 受击表现(伤害数字、受击动画、击退) */
do_atked(damage: number, isCrit: boolean, s_uuid: number, knockbackExtra: number = 0) {
// 受到攻击时更新最后更新时间
this.activateTopBar();
this.topBar.activateTopBar();
this.lastBarUpdateTime = Date.now() / 1000;
if (damage <= 0) return;
@@ -542,7 +410,7 @@ export class HeroViewComp extends CCComp {
// 视图层表现
let SConf = SkillSet[s_uuid]
const hitAnm = SConf?.DAnm || "atked";
if (isBack) this.back()
this.back(knockbackExtra)
this.in_atked(hitAnm, this.model.fac == FacSet.HERO ? 1 : -1);
this.showDamage(damage, isCrit);
}
@@ -629,7 +497,7 @@ export class HeroViewComp extends CCComp {
if (!this.model) return;
const damageText = NumberFormatter.formatNumber(Math.max(0, Math.floor(damage)));
this.hp_show();
this.topBar.hpShow();
if (isCrit) {
this.hp_tip(TooltipTypes.crit, damageText);
} else {
@@ -640,10 +508,7 @@ export class HeroViewComp extends CCComp {
// 清理残留的定时器和缓动
this.unscheduleAllCallbacks();
Tween.stopAllByTarget(this.node);
if (this.top_node && this.top_node.isValid) {
Tween.stopAllByTarget(this.top_node);
this.top_node.setPosition(this.topBasePos);
}
this.topBar.reset();
// 清理碰撞器事件监听
const collider = this.getComponent(Collider2D);
@@ -652,7 +517,6 @@ export class HeroViewComp extends CCComp {
}
this.deadCD = 0
this.lastBarUpdateTime = 0
this.unschedule(this.restoreBarIdleOpacity);
// 清理伤害队列
this.damageQueue.length = 0;

View File

@@ -3,11 +3,10 @@ import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/O
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { smc } from "../common/SingletonModuleComp";
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
import { HeroInfo, resolveTriggerByLv, resolveReviveByLv } from "../common/config/heroSet";
import { HeroInfo, resolveTriggerByLv, resolveReviveByLv, HeroDisVal } from "../common/config/heroSet";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { MoveComp } from "./MoveComp";
import { MonMoveComp } from "./MonMoveComp";
import { GameEvent } from "../common/config/GameEvent";
import { SkillTriggerType, HType } from "../common/config/heroSet";
import { SkillTriggerHelper } from "./SkillTriggerHelper";
@@ -19,7 +18,7 @@ export class Monster extends ecs.Entity {
/** 怪物表现组件引用 */
HeroView!: HeroViewComp;
/** 怪物移动组件引用 */
MonMove!: MonMoveComp;
MonMove!: MoveComp;
/** 调试开关,控制生命周期日志输出 */
private debugMode: boolean = false;
@@ -98,7 +97,7 @@ export class Monster extends ecs.Entity {
/** 注册实体必需组件:移动 + 属性 */
protected init() {
this.addComponents<ecs.Comp>(
MonMoveComp,
MoveComp,
HeroAttrsComp,
);
}
@@ -149,8 +148,9 @@ export class Monster extends ecs.Entity {
(node as any)._baseScale = node.scale.clone();
}
// 统一挂到实体显示层 HERO 节点下
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
// 按身份挂载显示层Boss → BOSS 层(最底),小怪 → MON 层(编辑器已配置好层级顺序)
const layerName = is_boss ? "BOSS" : "MON";
node.parent = scene.entityLayer!.node!.getChildByName(layerName)!;
var view = node.getComponent(HeroViewComp)!;
const collider = node.getComponent(BoxCollider2D);
// 入场期间关闭碰撞,防止下落时提前参与战斗
@@ -175,8 +175,8 @@ export class Monster extends ecs.Entity {
model.speed = hero.speed ?? 800;
model.type = hero.type;
model.fac = FacSet.MON;
// 攻击距离:配置优先;未配置按攻击定位默认值近战180 / 远程600
model.dis = hero.dis ?? (hero.type === HType.Melee ? 180 : 600);
// 攻击距离:配置优先;未配置按攻击定位默认值近战100 / 中程300 / 远程450
model.dis = hero.dis ?? HeroDisVal[hero.type as HType.Melee | HType.Mid | HType.Long];
model.posIndex = laneIndex;
if (laneIndex >= 0) {
smc.mission.monGrid[laneIndex] = this.eid;
@@ -249,7 +249,7 @@ export class Monster extends ecs.Entity {
// 广播怪物加载事件,供刷怪与战斗系统联动
oops.message.dispatchEvent("monster_load", this)
// 初始化移动参数:方向、目标 X、站位基准 Y
const move = this.get(MonMoveComp);
const move = this.get(MoveComp);
move.reset();
move.direction = -1;
move.targetX = pos.x;

View File

@@ -1,201 +0,0 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroViewComp } from "./HeroViewComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { Node } from "cc";
@ecs.register('MonMoveComp')
export class MonMoveComp extends ecs.Comp {
/** 朝向1=向右,-1=向左 */
direction: number = -1;
/** 当前移动目标 X */
targetX: number = 0;
/** 是否允许移动(出生落地前会短暂关闭) */
moving: boolean = true;
/** 站位基准 Y */
baseY: number = 0;
/** 出生序,用于同条件渲染排序稳定 */
spawnOrder: number = 0;
reset() {
this.direction = -1;
this.targetX = 0;
this.moving = true;
this.baseY = 0;
this.spawnOrder = 0;
}
}
@ecs.register('MonMoveSystem')
export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
/** 怪物接近英雄后的停船距离:小于该阈值即停下交战 */
private readonly combatHaltDistance = 80;
/** 渲染层级重排节流,避免每帧排序 */
private readonly renderSortInterval = 0.05;
private lastRenderSortAt = 0;
private monMoveMatcher: ecs.IMatcher | null = null;
private heroViewMatcher: ecs.IMatcher | null = null;
private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number }[] = [];
private renderEntryCount = 0;
private getMonMoveMatcher(): ecs.IMatcher {
if (!this.monMoveMatcher) {
this.monMoveMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp, MonMoveComp);
}
return this.monMoveMatcher;
}
private getHeroViewMatcher(): ecs.IMatcher {
if (!this.heroViewMatcher) {
this.heroViewMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
}
return this.heroViewMatcher;
}
filter(): ecs.IMatcher {
return ecs.allOf(MonMoveComp, HeroViewComp, HeroAttrsComp);
}
update(e: ecs.Entity) {
/** 战斗未开始/暂停时不驱动移动 */
if (!smc.mission.play || smc.mission.pause) return;
const model = e.get(HeroAttrsComp);
const move = e.get(MonMoveComp);
const view = e.get(HeroViewComp);
if (!model || !move || !view || !view.node) return;
if (model.fac !== FacSet.MON) return;
if (!move.moving) return;
/** 关卡阶段性冻结怪物行为 */
if (smc.mission.stop_mon_action) {
this.clearCombatTarget(model);
view.status_change("idle");
return;
}
if (model.is_stop || model.is_dead || model.is_reviving || model.isFrost() || model.isStun()) {
this.clearCombatTarget(model);
if (!model.is_reviving) view.status_change("idle");
return;
}
/** 所有移动都锁定在 baseY避免出现“漂移” */
if (view.node.position.y !== move.baseY) {
view.node.setPosition(view.node.position.x, move.baseY, 0);
}
// 渲染层级统交由 MoveSystem 统一处理,避免两个 System 争抢 setSiblingIndex
// 仅在战斗中才处理索敌;非战斗阶段也允许向左推进,让怪物自然逼近英雄阵地
const nearestEnemy = smc.mission.in_fight ? this.findNearestEnemy(e) : null;
if (nearestEnemy) {
/** 有敌人:边移动边攻击 */
this.processCombatLogic(e, move, view, model, nearestEnemy);
this.syncCombatTarget(model, view, nearestEnemy);
} else {
/** 无敌人:清目标并继续向左推进 */
this.clearCombatTarget(model);
model.is_atking = false;
this.moveLeft(view, move, model);
}
}
private clearCombatTarget(model: HeroAttrsComp): void {
model.combat_target_eid = -1;
model.enemy_in_cast_range = false;
}
private syncCombatTarget(model: HeroAttrsComp, selfView: HeroViewComp, enemyView: HeroViewComp): void {
if (!enemyView || !enemyView.node || !enemyView.ent) {
this.clearCombatTarget(model);
return;
}
const enemyAttrs = enemyView.ent.get(HeroAttrsComp);
if (!enemyAttrs || enemyAttrs.is_dead || enemyAttrs.is_reviving || enemyAttrs.fac === model.fac) {
this.clearCombatTarget(model);
return;
}
model.combat_target_eid = enemyView.ent.eid;
model.enemy_in_cast_range = this.isEnemyInAttackRange(model, selfView.node.position.x, enemyView.node.position.x);
}
private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean {
const dist = Math.abs(selfX - enemyX);
const attackRange = model.dis;
return dist <= attackRange;
}
private processCombatLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
const selfX = view.node.position.x;
const enemyX = enemy.node.position.x;
// 停船判定使用固定阈值80不依赖技能攻击范围
const inRange = Math.abs(selfX - enemyX) <= this.combatHaltDistance;
if (inRange) {
// 接触到英雄方:停止移动,进入攻击状态,由 SCastSystem 负责技能释放
model.is_atking = true;
const dir = enemyX > selfX ? 1 : -1;
view.scale = dir;
if (view.status === "move") {
view.status_change("idle");
}
} else {
// 未接触:继续向左推进
model.is_atking = false;
this.moveLeft(view, move, model);
}
}
/**
* 持续向左推进:怪物不再固定站位,而是以 speed/3 的速度向左移动,
* 直到抵达左边界(基地位置)后转入待机,由 SCastSystem 继续负责技能释放。
*/
private moveLeft(view: HeroViewComp, move: MonMoveComp, model: HeroAttrsComp) {
const currentX = view.node.position.x;
if (currentX <= BoxSet.LETF_END) {
if (view.status !== "atk") {
view.status_change("idle");
}
return;
}
const dir = -1;
move.direction = dir;
const speed = model.speed / 3;
const delta = speed * this.dt * dir;
const newX = Math.max(BoxSet.LETF_END, currentX + delta);
if (Math.abs(newX - currentX) >= 0.01) {
view.node.setPosition(newX, view.node.position.y, 0);
view.status_change("move");
}
}
private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null {
const currentView = entity.get(HeroViewComp);
if (!currentView?.node) return null;
const currentPos = currentView.node.position;
const myFac = entity.get(HeroAttrsComp).fac;
let nearest: HeroViewComp | null = null;
let minDis = Infinity;
/** 遍历筛出最近敌人:以 X 轴距离为主Y 轴距离作为同排的决胜权重,使角色优先攻击同路的敌人 */
ecs.query(this.getHeroViewMatcher()).forEach(e => {
const m = e.get(HeroAttrsComp);
if (m.fac !== myFac && !m.is_dead) {
const v = e.get(HeroViewComp);
if (v?.node) {
const dx = Math.abs(currentPos.x - v.node.position.x);
const dy = Math.abs(currentPos.y - v.node.position.y);
const d = dx + dy * 0.1; // Y轴权重较小仅在 X 相近时起决定作用
if (d < minDis) {
minDis = d;
nearest = v;
}
}
}
});
return nearest;
}
}

View File

@@ -1,26 +1,31 @@
/**
* @file MoveComp.ts
* @description 统一移动组件与系统(英雄 + 怪物)
*
* 核心规则:
* 1. 准备阶段:双方原地待命,不推进不索敌。
* 2. 战斗阶段双方向最近敌人推进进入攻击范围model.dis后停止移动并攻击。
* 3. 场上无敌人:停止移动(战斗结束,由 MissionComp 进入结算)。
* 4. 击退/位移不会把单位推出屏幕HERO 不越过 BoxSet.LETF_ENDMON 不越过 BoxSet.RIGHT_END。
* 5. 推进方向无上限限制,直到接触敌人或抵达敌方边界。
*/
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroViewComp } from "./HeroViewComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { HeroDisVal, HType, HRole } from "../common/config/heroSet";
import { BoxCollider2D, Node } from "cc";
import { MonMoveComp } from "./MonMoveComp";
import { Node } from "cc";
@ecs.register('MoveComp')
export class MoveComp extends ecs.Comp {
/** 朝向1=向右-1=向左 */
/** 朝向1=向右HERO-1=向左MON由系统根据 fac 自动维护 */
direction: number = 1;
/** 当前移动目标 X(战斗/回位都会更新) */
/** 当前移动目标 X */
targetX: number = 0;
/** 是否允许移动(出生落地前会短暂关闭) */
moving: boolean = true;
/** 预留:目标 Y当前逻辑主要使用 baseY 锁定地平线) */
targetY: number = 0;
/** 角色所属车道的地平线 Y移动时强制贴地 */
/** 站位基准 Y移动时强制贴地 */
baseY: number = 0;
/** 预留:车道索引 */
lane: number = 0;
/** 出生序,用于同条件渲染排序稳定 */
spawnOrder: number = 0;
@@ -28,68 +33,20 @@ export class MoveComp extends ecs.Comp {
this.direction = 1;
this.targetX = 0;
this.moving = true;
this.targetY = 0;
this.baseY = 0;
this.lane = 0;
this.spawnOrder = 0;
}
}
interface MoveFacConfig {
/** 阵营可前进边界(靠近敌方一侧) */
moveFrontX: number;
/** 阵营可后退边界(靠近己方一侧) */
moveBackX: number;
/** 被逼退时前侧安全边界 */
retreatFrontX: number;
/** 被逼退时后侧安全边界 */
retreatBackX: number;
}
@ecs.register('MoveSystem')
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
private readonly heroFrontAnchorX = -200;
private readonly monFrontAnchorX = 0;
/** 常规同阵营横向最小间距(英雄) */
private readonly heroAllySpacingX = 100;
/** 常规同阵营横向最小间距(怪物) */
private readonly monAllySpacingX = 75;
/** 纵向判定为同排的最大 Y 差 */
private readonly minSpacingY = 30;
/** 渲染层级重排节流,避免每帧排序 */
private readonly renderSortInterval = 0.05;
private lastRenderSortAt = 0;
private heroMoveMatcher: ecs.IMatcher | null = null;
private heroViewMatcher: ecs.IMatcher | null = null;
private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number; laneScore: number }[] = [];
private renderEntryCount = 0;
/**
* 阵营可移动边界配置。
* HERO 在左侧出生并向右推进MON 在右侧出生并向左推进。
*/
private readonly facConfigs: Record<number, MoveFacConfig> = {
[FacSet.HERO]: {
moveFrontX: 999999,
moveBackX: -999999,
retreatFrontX: 999999,
retreatBackX: -999999,
},
[FacSet.MON]: {
moveFrontX: -999999,
moveBackX: 999999,
retreatFrontX: -999999,
retreatBackX: 999999,
}
};
private getHeroMoveMatcher(): ecs.IMatcher {
if (!this.heroMoveMatcher) {
this.heroMoveMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp, MoveComp);
}
return this.heroMoveMatcher;
}
private getHeroViewMatcher(): ecs.IMatcher {
if (!this.heroViewMatcher) {
this.heroViewMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
@@ -109,52 +66,49 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const move = e.get(MoveComp);
const view = e.get(HeroViewComp);
if (!model || !move || !view || !view.node) return;
if (model.fac !== FacSet.HERO) return; // 只处理英雄移动
if (!move.moving) return;
if (model.is_stop || model.is_dead || model.is_reviving || model.isFrost()) {
/** 关卡阶段性冻结怪物行为 */
if (smc.mission.stop_mon_action && model.fac === FacSet.MON) {
this.clearCombatTarget(model);
view.status_change("idle");
return;
}
/** 死亡/复活/控制状态:停止移动与索敌 */
if (model.is_stop || model.is_dead || model.is_reviving || model.isFrost() || model.isStun()) {
this.clearCombatTarget(model);
if (!model.is_reviving) view.status_change("idle");
return;
}
// 1. 获取全局排位目标
const slot = this.getGlobalFormationSlot(e, model);
move.baseY = slot.targetY;
move.targetX = slot.targetX;
// 2. 平滑 Y 轴换路(不分阶段,立即响应编队变化)
let isChangingLane = false;
if (Math.abs(view.node.position.y - move.baseY) > 2) {
const currentY = view.node.position.y;
const deltaY = move.baseY - currentY;
const step = 400 * this.dt; // 换路速度
const newY = currentY + Math.sign(deltaY) * Math.min(Math.abs(deltaY), step);
view.node.setPosition(view.node.position.x, newY, 0);
isChangingLane = true;
} else {
/** 所有移动都锁定在 baseY避免出现“漂移” */
if (view.node.position.y !== move.baseY) {
view.node.setPosition(view.node.position.x, move.baseY, 0);
}
// 渲染层级重排
this.updateRenderOrder();
/** 准备阶段:双方原地待命,不推进不索敌 */
if (!smc.mission.in_fight) {
this.clearCombatTarget(model);
model.is_atking = false;
if (view.status !== "atk") view.status_change("idle");
return;
}
/** 战斗阶段:索敌并推进 */
const nearestEnemy = this.findNearestEnemy(e);
if (nearestEnemy) {
/** 有敌人:进入战斗位移逻辑(立即向编队目标移动) */
this.processCombatLogic(e, move, view, model, nearestEnemy);
this.syncCombatTarget(model, view, nearestEnemy);
} else {
/** 无敌人:清目标并回归编队站位 */
/** 无敌人:停止移动,等待战斗结束结算 */
this.clearCombatTarget(model);
this.moveToSlot(view, move, model, move.targetX);
model.is_atking = false;
if (view.status !== "atk") view.status_change("idle");
}
// 如果只在 Y 轴移动,也要播放 move 动画
if (isChangingLane && view.status !== "move" && view.status !== "atk") {
view.status_change("move");
}
/** 渲染层级重排(双阵营统一处理) */
this.updateRenderOrder();
}
private clearCombatTarget(model: HeroAttrsComp): void {
@@ -182,300 +136,55 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return dist <= attackRange;
}
private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
switch (rangeType) {
case HType.Melee:
this.processMeleeLogic(e, move, view, model, enemy);
break;
case HType.Mid:
this.processMidLogic(e, move, view, model, enemy);
break;
case HType.Long:
this.processLongLogic(e, move, view, model, enemy);
break;
default:
this.processMidLogic(e, move, view, model, enemy); // 默认中程
break;
}
}
/**
* 近战交战怪物进入警戒线ALERT_RANGE_X内才向右推进迎敌
* 贴近到固定接触距离后停下攻击;警戒线外则退回后方编队位待命
* Why: 近战手短需主动逼近,但无威胁时不应孤军前压暴露阵型;
* 停止判定用固定接触距离(非攻击距离 dis
* 使英雄进入攻击范围后仍继续贴近,直到贴上目标才站稳输出。
* 怪物 y 与英雄同在 GAME_LINE 附近(微调也在攻击范围内),无需管 Y 轴。
* 战斗位移:向最近敌人推进,进入攻击范围后停止。
* 推进无上限限制,但击退/位移受阵营后退边界保护
*/
private processMeleeLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
if (!enemy?.node) {
this.processFormationCombat(e, move, view, model);
return;
}
private processCombatLogic(_e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
const selfX = view.node.position.x;
const enemyX = enemy.node.position.x;
const dist = Math.abs(selfX - enemyX);
// 已贴近到固定接触距离:停下攻击
if (dist <= MoveSystem.MELEE_ENGAGE_X) {
const attackRange = model.dis;
/** 近战贴身作战:攻击距离 dis(100) 大于技能范围(60),推进到与敌人相隔 60 才停下,保证技能可命中且不过度贴身 */
const stopRange = model.type === 0 ? 80 : attackRange;
const inRange = dist <= stopRange;
if (inRange) {
/** 进入攻击范围:停止移动,进入攻击状态,由 SCastSystem 负责技能释放 */
model.is_atking = true;
if (view.status !== "atk") view.status_change("idle");
return;
}
// 怪物在警戒线之外:不主动迎敌,退回后方编队位待命
if (dist > MoveSystem.MELEE_ALERT_X) {
this.clearCombatTarget(model);
this.moveToSlot(view, move, model, move.targetX);
const dir = enemyX > selfX ? 1 : -1;
view.scale = dir;
if (view.status === "move") {
view.status_change("idle");
}
} else {
/** 未接触:向敌人方向推进 */
model.is_atking = false;
return;
const dir = enemyX > selfX ? 1 : -1;
move.direction = dir;
const speed = model.speed / 3;
const delta = speed * this.dt * dir;
let newX = selfX + delta;
/** 后退边界钳制:防止被击退/位移推出屏幕 */
newX = this.clampByFacBoundary(newX, model.fac);
if (Math.abs(newX - selfX) >= 0.01) {
view.node.setPosition(newX, view.node.position.y, 0);
view.status_change("move");
}
}
// 警戒线内未贴近:向怪物方向推进(攻击范围内也继续移动),于接触距离处停下
const dir = enemyX > selfX ? 1 : -1;
move.direction = dir;
// 终止点取接触距离与推进上限的较小者,防止越过 150 继续追击
let stopX = Math.min(enemyX - dir * MoveSystem.MELEE_ENGAGE_X, MoveSystem.HERO_ADVANCE_LIMIT_X);
// 同阵营最小间距约束:防止近战推进时穿越前方队友
stopX = this.clampAllySpacing(e, view, move, model, stopX);
const speed = model.speed / 3;
this.moveEntity(view, dir, speed, stopX);
model.is_atking = false;
}
private processMidLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
this.processFormationCombat(e, move, view, model);
}
private processLongLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
this.processFormationCombat(e, move, view, model);
}
private processFormationCombat(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
this.moveToSlot(view, move, model, move.targetX);
model.is_atking = true;
}
private getGlobalFormationSlot(self: ecs.Entity, model: HeroAttrsComp): { targetX: number, targetY: number } {
const allAllies: ecs.Entity[] = [];
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
const attrs = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
const move = e.get(MoveComp);
if (!attrs || !view?.node || !move) return;
if (attrs.is_dead || attrs.is_reviving) return;
if (attrs.fac !== model.fac) return;
allAllies.push(e);
});
allAllies.sort((a, b) => {
const attrsA = a.get(HeroAttrsComp);
const attrsB = b.get(HeroAttrsComp);
const priorityA = attrsA ? this.getFormationPriority(attrsA) : 0;
const priorityB = attrsB ? this.getFormationPriority(attrsB) : 0;
if (priorityA !== priorityB) return priorityB - priorityA;
const lvA = attrsA?.lv ?? 1;
const lvB = attrsB?.lv ?? 1;
if (lvA !== lvB) return lvB - lvA;
const moveA = a.get(MoveComp);
const moveB = b.get(MoveComp);
const orderA = moveA?.spawnOrder ?? 0;
const orderB = moveB?.spawnOrder ?? 0;
if (orderA !== orderB) return orderA - orderB;
return a.eid - b.eid;
});
// allAllies 按优先级降序(索引 0 = 最高优先级 = 最靠右/最先接敌),
// 反转为"从左侧起的排位"后再分配 X最高优先级 → 最右
const priorityRank = Math.max(0, allAllies.findIndex(entity => entity === self));
const rankFromLeft = allAllies.length - 1 - priorityRank;
const targetX = this.calcSlotX(rankFromLeft, allAllies.length);
return { targetX, targetY: BoxSet.GAME_LINE };
}
// ==================== 编队站位(方案 A单行横向分布 ====================
/** 编队左端起点(最靠后/最后接敌) */
private static readonly FORMATION_LEFT_X = -320;
/** 编队右端终点(最靠前/最先接敌) */
private static readonly FORMATION_RIGHT_X = 100;
/** 固定步进间距(人数较少时按此从左侧逐位右排) */
private static readonly FORMATION_STEP_X = 60;
/** 近战贴近怪物的固定接触距离:小于攻击距离 dis使英雄进攻击范围后仍继续贴近到此间距才停 */
private static readonly MELEE_ENGAGE_X = 60;
/** 近战警戒线:怪物进入该距离内才主动向右推进迎敌,否则退回后方编队位待命 */
private static readonly MELEE_ALERT_X = 200;
/** 英雄向敌人推进的最远终止点 X防止近战追敌时无限右移脱离战场 */
private static readonly HERO_ADVANCE_LIMIT_X = 100;
/** 同阵营英雄间最小间距:小于该值时低优先级一方需让位 */
private static readonly ALLY_MIN_GAP_X = 30;
/**
* 计算某个槽位的目标 X。
* Why: 单行站位避免横版拥挤——人少时按 60 固定步进从 -320 起步逐位右排(保持松散),
* 人多到 60 步进装不下时,改为在 [-320, 100] 区间均分(利用全宽)。
* @param rankFromLeft 从左侧起的排位0=最靠左/最后,越大越靠右/越先接敌)
* @param total 当前存活英雄总数
*/
private calcSlotX(rankFromLeft: number, total: number): number {
const left = MoveSystem.FORMATION_LEFT_X;
const right = MoveSystem.FORMATION_RIGHT_X;
const step = MoveSystem.FORMATION_STEP_X;
if (total <= 1) return left;
// 固定步进能容纳的最大人数:相邻 60且最右不超过 right
const maxStepCount = Math.floor((right - left) / step) + 1;
if (total <= maxStepCount) {
return left + rankFromLeft * step;
}
// 超出后均分整段区间(含左右端点)
return left + (right - left) * (rankFromLeft / (total - 1));
}
private moveToSlot(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, targetX: number) {
const currentX = view.node.position.x;
const currentY = view.node.position.y;
// 当 X 和 Y 都到达目标时,才算真正到达
if (Math.abs(currentX - targetX) <= 2 && Math.abs(currentY - move.baseY) <= 2) {
view.node.setPosition(targetX, move.baseY, 0);
view.status_change("idle");
return;
}
const dir = targetX > currentX ? 1 : -1;
move.direction = dir;
const speed = model.speed / 3;
// 同阵营最小间距约束:防止编队挤压时穿越前方队友
const clampedTargetX = this.clampAllySpacing(view.ent, view, move, model, targetX);
this.moveEntity(view, dir, speed, clampedTargetX);
}
/**
* 阵营横向最小间距约束
* Why: 编队/追击时英雄可能挤到一起,低优先级一方需要保持在高优先级身后 ALLY_MIN_GAP_X 处,
* 避免视觉重叠与站位混乱。
* 优先级编队优先级高者在前同级时召唤早spawnOrder 小)者在前。
* @returns 裁剪后的目标 X保证不越过身前队友
* 阵营裁剪 X 坐标,防止被击退/位移推出屏幕
* HERO 后退不越过 BoxSet.LETF_ENDMON 后退不越过 BoxSet.RIGHT_END。
*/
private clampAllySpacing(self: ecs.Entity, view: HeroViewComp, selfMove: MoveComp, model: HeroAttrsComp, targetX: number): number {
const selfX = view.node.position.x;
const selfY = view.node.position.y;
let clamped = targetX;
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
if (e === self) return;
const attrs = e.get(HeroAttrsComp);
const v = e.get(HeroViewComp);
const mv = e.get(MoveComp);
if (!attrs || !v?.node || !mv) return;
if (attrs.is_dead || attrs.is_reviving) return;
if (attrs.fac !== model.fac) return;
// 只约束同排Y 差在阈值内)的队友
if (Math.abs(v.node.position.y - selfY) > this.minSpacingY) return;
const otherX = v.node.position.x;
// 判断对方是否在自己前方(以目标方向为前)
const isAhead = clamped > selfX ? otherX > selfX : otherX < selfX;
if (!isAhead) return;
// 判定谁该在前:编队优先级高者在前;同级时 spawnOrder 小者在前
const myPriority = this.getFormationPriority(model);
const otherPriority = this.getFormationPriority(attrs);
let selfIsFront = false;
if (myPriority !== otherPriority) {
selfIsFront = myPriority > otherPriority;
} else {
selfIsFront = selfMove.spawnOrder <= mv.spawnOrder;
}
if (!selfIsFront) {
// 自己靠后:不能越过 otherX - ALLY_MIN_GAP_X朝右或 otherX + ALLY_MIN_GAP_X朝左
const limitX = clamped > selfX ? otherX - MoveSystem.ALLY_MIN_GAP_X : otherX + MoveSystem.ALLY_MIN_GAP_X;
clamped = clamped > selfX ? Math.min(clamped, limitX) : Math.max(clamped, limitX);
}
});
return clamped;
}
private moveEntity(view: HeroViewComp, direction: number, speed: number, stopAtX?: number) {
const model = view.ent.get(HeroAttrsComp);
const move = view.ent.get(MoveComp);
if (!model || !move) return;
/** 按阵营边界裁剪,防止跑出战场可移动区域 */
const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO];
const moveMinX = Math.min(cfg.moveBackX, cfg.moveFrontX);
const moveMaxX = Math.max(cfg.moveBackX, cfg.moveFrontX);
const currentX = view.node.position.x;
const currentY = view.node.position.y;
const delta = speed * this.dt * direction;
let newX = view.node.position.x + delta;
if (currentX < moveMinX && direction < 0) {
// X 轴到底了,如果 Y 轴还在换路,继续维持 move 状态
if (Math.abs(currentY - move.baseY) > 2) {
view.status_change("move");
} else {
view.status_change("idle");
}
return;
private clampByFacBoundary(x: number, fac: number): number {
if (fac === FacSet.HERO) {
return Math.max(BoxSet.LETF_END, x);
}
if (currentX > moveMaxX && direction > 0) {
if (Math.abs(currentY - move.baseY) > 2) {
view.status_change("move");
} else {
view.status_change("idle");
}
return;
}
newX = Math.max(moveMinX, Math.min(moveMaxX, newX));
if (stopAtX !== undefined) {
/** 指定停止点时,限制不越过 stopAtX */
newX = direction > 0 ? Math.min(newX, stopAtX) : Math.max(newX, stopAtX);
}
if (Math.abs(newX - currentX) < 0.01) {
// X轴虽然没变化但如果Y轴还在移动依然是move状态
if (Math.abs(currentY - move.baseY) > 2) {
view.status_change("move");
} else {
view.status_change("idle");
}
return;
}
view.node.setPosition(newX, view.node.position.y, 0); // 注意:这里只更新 XY 在外部平滑逻辑更新
view.status_change("move");
}
/**
* 编队站位优先级(数值越大越靠前/越靠右)。
* Why: 阵容职能决定承伤顺序——死亡触发英雄需最先阵亡以触发遗志,
* 其次坦克承伤,再次战士/刺客,远程射手/法师/辅助依次靠后保护。
* 排序档位:死亡触发 > 坦克 > 战士 > 刺客 > 射手 > 法师 > 辅助
*/
private getFormationPriority(model: HeroAttrsComp): number {
// 死亡触发英雄最高优先:需要先死以发动遗志/献祭
if (model.dead && Object.keys(model.dead).length > 0) return 100;
if (model.role !== undefined) {
switch (model.role) {
case HRole.Tank: return 90;
case HRole.Warrior: return 80;
case HRole.Assassin: return 70;
case HRole.Archer: return 60;
case HRole.Mage: return 50;
case HRole.Support: return 40;
}
}
// 兜底:未配置 role 时按攻击定位(近战 > 中程 > 远程)
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
if (rangeType === HType.Melee) return 30;
if (rangeType === HType.Mid) return 20;
return 10;
}
private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] {
const minRange = model.getCachedMinSkillDistance();
const maxRange = model.getCachedMaxSkillDistance();
if (maxRange <= 0) return [defaultMin, defaultMax];
const safeMin = Math.max(0, Math.min(minRange, maxRange - 20));
return [safeMin, maxRange];
return Math.min(BoxSet.RIGHT_END, x);
}
private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null {
@@ -509,13 +218,19 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
private updateRenderOrder() {
const scene = smc.map?.MapView?.scene;
const actorRoot = scene?.entityLayer?.node?.getChildByName("HERO");
if (!actorRoot) return;
const layerNode = scene?.entityLayer?.node;
if (!layerNode) return;
const now = Date.now() / 1000;
if (now - this.lastRenderSortAt < this.renderSortInterval) return;
this.lastRenderSortAt = now;
const heroRoot = layerNode.getChildByName("HERO");
const monRoot = layerNode.getChildByName("MON");
const bossRoot = layerNode.getChildByName("BOSS");
/** 分层重排同层节点:各阵营层内按 Y 轴路次 -> 前后位置 -> 出生序 -> eid 排序
* Boss 已有独立 BOSS 层固定在最底,无需 bossPriority 参与排序) */
this.renderEntryCount = 0;
ecs.query(this.getHeroViewMatcher()).forEach(e => {
const attrs = e.get(HeroAttrsComp);
@@ -523,24 +238,23 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (!attrs || !actorView?.node || attrs.is_dead) return;
if (attrs.fac !== FacSet.HERO && attrs.fac !== FacSet.MON) return;
if (actorView.node.parent !== actorRoot) {
actorView.node.parent = actorRoot;
/** 按阵营/身份归位到对应显示层 */
let targetRoot: Node | null | undefined;
if (attrs.fac === FacSet.HERO) {
targetRoot = heroRoot;
} else {
targetRoot = attrs.is_boss ? (bossRoot ?? monRoot ?? heroRoot) : (monRoot ?? heroRoot);
}
if (!targetRoot) return;
if (actorView.node.parent !== targetRoot) {
actorView.node.parent = targetRoot;
}
// 获取 spawnOrder由于可能挂载 MoveComp 或 MonMoveComp我们需要动态获取
let spawnOrder = 0;
const heroMove = e.get(MoveComp);
if (heroMove) {
spawnOrder = heroMove.spawnOrder;
} else {
const monMove = e.get(MonMoveComp);
if (monMove) {
spawnOrder = monMove.spawnOrder;
}
}
/** 统一从 MoveComp 获取 spawnOrder */
const move = e.get(MoveComp);
const spawnOrder = move?.spawnOrder ?? 0;
// 按 Y 轴计算渲染层级权重Y 越小,说明越靠近屏幕下方,应该渲染在越前面)
// 之前的 isFly 逻辑已被移除,统一按 Y 轴处理三路渲染
const laneScore = -actorView.node.position.y;
// X 轴权重:站在前排的(交战处)优先渲染
const frontScore = attrs.fac === FacSet.HERO ? actorView.node.position.x : -actorView.node.position.x;
@@ -552,7 +266,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
this.renderEntries.push(entry);
}
entry.node = actorView.node;
entry.bossPriority = attrs.is_boss ? 1 : 0;
entry.laneScore = laneScore;
entry.frontScore = frontScore;
entry.spawnOrder = spawnOrder;
@@ -561,9 +274,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
});
this.renderEntries.length = this.renderEntryCount;
/** 定时重排同层节点Boss 优先级 -> Y 轴路次 -> 前后位置 -> 出生序 -> eid */
this.renderEntries.sort((a, b) => {
if (a.bossPriority !== b.bossPriority) return a.bossPriority - b.bossPriority;
// Y轴靠下的laneScore 较大)应该在后面,从而渲染在上面
if (Math.abs(a.laneScore - b.laneScore) > 10) return a.laneScore - b.laneScore;
if (a.frontScore !== b.frontScore) return a.frontScore - b.frontScore;
@@ -571,8 +282,14 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return a.eid - b.eid;
});
this.renderEntries.forEach((item, index) => {
/** 分层应用 siblingIndex各层内部从 0 递增 */
const layerCounters = new Map<Node, number>();
this.renderEntries.forEach((item) => {
const parent = item.node.parent;
if (!parent) return;
const index = layerCounters.get(parent) ?? 0;
item.node.setSiblingIndex(index);
layerCounters.set(parent, index + 1);
});
}
}

View File

@@ -61,10 +61,10 @@ export class HeroMoreCom extends Component {
/** 冰冻率附加值 */
@property(Label)
private freeze_plus_label: Label = null;
/** 击退总值(百分点 */
/** 击退强化总值(击退距离增量 */
@property(Label)
private knockback_all_label: Label = null;
/** 击退附加值 */
/** 击退强化附加值 */
@property(Label)
private knockback_plus_label: Label = null;
/** 穿透率总值(百分点) */
@@ -177,12 +177,13 @@ export class HeroMoreCom extends Component {
const finalCritDmg = this.model.getFinalCritDamage();
this.setStatRow(this.crit_dmg_all_label, this.crit_dmg_plus_label, finalCritDmg, finalCritDmg - (this.model.crit_damage ?? 0), "%");
// ---- 冰冻率 / 击退 / 穿透率 / 风怒率(百分点) ----
// ---- 冰冻率 / 击退强化 / 穿透率 / 风怒率 ----
const finalFreeze = this.model.getFinalFreezeChance();
this.setStatRow(this.freeze_all_label, this.freeze_plus_label, finalFreeze, finalFreeze - (this.model.freeze_chance ?? 0), "%");
// 击退强化已改为击退距离增量,不再按百分比显示
const finalKnockback = this.model.getFinalKnockbackChance();
this.setStatRow(this.knockback_all_label, this.knockback_plus_label, finalKnockback, finalKnockback - (this.model.knockback_chance ?? 0), "%");
this.setStatRow(this.knockback_all_label, this.knockback_plus_label, finalKnockback, finalKnockback - (this.model.knockback_chance ?? 0), "");
const finalPuncture = this.model.getFinalPunctureChance();
this.setStatRow(this.puncture_all_label, this.puncture_plus_label, finalPuncture, finalPuncture - (this.model.puncture_chance ?? 0), "%");

View File

@@ -805,7 +805,6 @@ export class MissionComp extends CCComp {
this.clearTime = 0
smc.vmdata.mission_data.mon_num = 0
smc.vmdata.mission_data.level = 1
smc.vmdata.mission_data.mon_max = Math.max(1, Math.floor(this.maxMonsterCount))
this.currentPhase = MissionPhase.None;
this.currentWave = 1;
this.isBossWave = false;
@@ -1006,21 +1005,17 @@ export class MissionComp extends CCComp {
this.clearBattleSceneNodes();
}
/** 清理战斗场景中的 HERO 和 SKILL 根节点下的所有子节点 */
/** 清理战斗场景中的 HERO/MON/BOSS 和 SKILL 根节点下的所有子节点 */
private clearBattleSceneNodes() {
const scene = smc.map?.MapView?.scene;
const layer = scene?.entityLayer?.node;
if (!layer) return;
const heroRoot = layer.getChildByName("HERO");
const skillRoot = layer.getChildByName("SKILL");
if (heroRoot) {
for (let i = heroRoot.children.length - 1; i >= 0; i--) {
heroRoot.children[i].destroy();
}
}
if (skillRoot) {
for (let i = skillRoot.children.length - 1; i >= 0; i--) {
skillRoot.children[i].destroy();
for (const name of ["HERO", "MON", "BOSS", "SKILL"]) {
const root = layer.getChildByName(name);
if (root) {
for (let i = root.children.length - 1; i >= 0; i--) {
root.children[i].destroy();
}
}
}
}

View File

@@ -53,18 +53,16 @@ const { ccclass } = _decorator;
export class MissionHeroComp extends CCComp {
// ======================== 常量 ========================
/** 硬编码的英雄占位点(数量对齐 FightSet.HERO_MAX_NUM落点 X 统一,英雄落地后由 MoveComp 自行移位到阵型目标) */
/**
* 英雄登场占位点(固定 3 列):
* - index 0/1/2 分别对应 x=-320/-260/-200。
* - 分配规则近战优先靠前x 更大),远程/中程优先靠后x 更小);
* 同类型按召唤先后从该类型的一侧开始依次填充。
*/
public static readonly HERO_POSITIONS: Vec3[] = [
v3(-300, BoxSet.GAME_LINE, 0), // index 0
v3(-300, BoxSet.GAME_LINE, 0), // index 1
v3(-300, BoxSet.GAME_LINE, 0), // index 2
v3(-300, BoxSet.GAME_LINE, 0), // index 3
v3(-300, BoxSet.GAME_LINE, 0), // index 4
v3(-300, BoxSet.GAME_LINE, 0), // index 5
v3(-300, BoxSet.GAME_LINE, 0), // index 6
v3(-300, BoxSet.GAME_LINE, 0), // index 7
v3(-300, BoxSet.GAME_LINE, 0), // index 8
v3(-300, BoxSet.GAME_LINE, 0), // index 9
v3(-320, BoxSet.GAME_LINE, 0),
v3(-260, BoxSet.GAME_LINE, 0),
v3(-200, BoxSet.GAME_LINE, 0),
];
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */
@@ -203,7 +201,7 @@ export class MissionHeroComp extends CCComp {
* 动态分配英雄上场的位置
* @param excludeEids 排除计算的实体ID数组避免复活时把自己算成占据的位置
*/
private pickPositionIndexForHero(excludeEids: number[] = []): number {
private pickPositionIndexForHero(excludeEids: number[] = [], heroType: HType = HType.Melee): number {
const heroes = this.getAllHeroes().filter(h => {
const m = h.get(HeroAttrsComp);
return m && !m.is_dead && !excludeEids.includes(h.eid);
@@ -215,8 +213,10 @@ export class MissionHeroComp extends CCComp {
if (m && m.posIndex >= 0) occupied.add(m.posIndex);
}
// 按索引顺序填充空位0..9),全部落点相同,英雄落地后自行移位
const slotPriority = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// 近战优先靠前x 更大,索引更大);远程/中程优先靠后x 更小,索引更小)
const slotPriority = heroType === HType.Melee
? [2, 1, 0]
: [0, 1, 2];
for (const idx of slotPriority) {
if (!occupied.has(idx) && MissionHeroComp.HERO_POSITIONS[idx]) {
return idx;
@@ -255,7 +255,7 @@ export class MissionHeroComp extends CCComp {
private spawnHeroAt(uuid: number, hero_lv: number, card_lv: number, posIndex: number) {
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
const finalPosIndex = posIndex >= 0 ? posIndex : this.pickPositionIndexForHero();
const finalPosIndex = posIndex >= 0 ? posIndex : this.pickPositionIndexForHero([], HeroInfo[uuid]?.type ?? HType.Melee);
// 兜底:索引越界或点位缺失时复用 index 0保证 landingPos 一定有效
const landingPos = MissionHeroComp.HERO_POSITIONS[finalPosIndex] ?? MissionHeroComp.HERO_POSITIONS[0];
let spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);

View File

@@ -8,7 +8,7 @@
*
* 关键设计:
* - 5 只怪在 X∈[100,320] 内按登场序号均匀铺开Boss/小怪同时登场,玩家看清阵容后三选一。
* - 实际阵型与推进由 MonMoveComp 在战斗中向移动自然形成,不再使用固定网格点。
* - 实际推进由统一 MoveComp 在战斗中向敌人移动自然形成,不再使用固定网格点。
* - 槽位索引仅用于 monGrid 寻路与 SCastSystem 索敌定位。
*/
import { _decorator, v3, Vec3 } from "cc";
@@ -22,7 +22,7 @@ import { GameEvent } from "../common/config/GameEvent";
import { BoxSet } from "../common/config/GameSet";
import { spawningEngine, GeneratedMonster, TestModeConfig, BOSS_COUNT, MINION_COUNT } from "./RogueConfig";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { MonMoveComp } from "../hero/MonMoveComp";
import { MoveComp } from "../hero/MoveComp";
const { ccclass, property } = _decorator;
@@ -142,7 +142,7 @@ export class MissionMonCompComp extends CCComp {
const spawnX = MissionMonCompComp.SPAWN_X_MIN
+ (MissionMonCompComp.SPAWN_X_MAX - MissionMonCompComp.SPAWN_X_MIN)
* (posIndex / Math.max(1, BOSS_COUNT + MINION_COUNT - 1));
const landingY = basePos.y + (monData.isBoss ? 6 : 0);
const landingY = basePos.y;
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
@@ -158,7 +158,7 @@ export class MissionMonCompComp extends CCComp {
oops.message.dispatchEvent(GameEvent.BossSpawn, { pos: spawnPos.clone() });
}
const move = mon.get(MonMoveComp);
const move = mon.get(MoveComp);
if (move) {
move.spawnOrder = this.globalSpawnOrder;
}

View File

@@ -11,23 +11,19 @@
* 设计层次:
* 1. BossComboLibrary - Boss 组合库(小怪构成 + Boss/小怪技能池 + 抽取权重)
* 2. MonSkillPool/BossSkillPool - 怪物技能池atking / atked / dead 等全触发类型)
* 3. RogueSpawningEngine - 生成引擎(按英雄强度反推怪物强度
* 3. RogueSpawningEngine - 生成引擎(基础属性 × 可调系数
*
* 核心公式:
* heroPower = Σ calcHeroPower(HeroInfo[uuid], lv) (场上存活英雄
* targetPower = heroPower × BOSS_TYPE_RATIO × FINAL_HP_MUL × MON_POWER_MUL
* hpScale = targetPower ÷ Σ 怪物基础强度
* 怪物 hp = base_hp × hpScale (走总强度框架,自动匹配玩家)
*
* 怪物 ap 独立挂钩玩家输出(不走总强度,避免被 hp 挤占):
* 怪物 ap = 玩家平均攻击 × AP_GROWTH_FINAL × AP_RELATIVE × 类型系数
* 核心公式(基础属性 × 可调系数)
* 怪物 hp = HeroInfo 基础hp × MON_HP_MULBoss 额外 × BOSS_HP_BONUS_MUL
* 怪物 ap = HeroInfo 基础ap × MON_AP_MUL
* 怪物基础面板与 1 级英雄持平(近战镜像 hp300/ap22/cd1.8),默认系数校准为
* 整场战斗3 英雄 vs 4 小怪 + 1 Boss双方对砍 ≈ WAVE_DURATION(30s)。
* 手感微调只改 MON_HP_MUL / MON_AP_MUL / BOSS_HP_BONUS_MUL 三个系数即可。
*/
import { HeroInfo, MonType, MonTypeName, calcHeroPower, TriggerGrouped, LvReviveEntry } from "../common/config/heroSet";
import { HeroInfo, MonType, MonTypeName, TriggerGrouped, LvReviveEntry } from "../common/config/heroSet";
import { SkillOverrides } from "../common/config/SkillSet";
import { FacSet } from "../common/config/GameSet";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
// ======================== 1. 战斗常量 ========================
@@ -38,17 +34,13 @@ export const WAVE_DURATION = 30;
export const BOSS_COUNT = 1;
export const MINION_COUNT = 4;
/** Boss 战强度系数(原压力回合系数) */
export const BOSS_TYPE_RATIO = 1.2;
/** 最终 Boss 战 HP 乘区(原 wave 20 配置) */
export const FINAL_HP_MUL = 1.8;
/** 全局怪物强度系数2/3 = 强度削弱三分之一,同时作用于 hp 缩放与 ap 挂钩公式 */
export const MON_POWER_MUL = 2 / 3;
/** 怪物 ap 最终递进系数(原 wave 20 满档 3.0 */
export const AP_GROWTH_FINAL = 3.0;
/** 怪物 ap 相对玩家平均攻击的基础系数(<1 保证单怪不至于一击致命,由 Boss/精英类型再上浮) */
export const AP_RELATIVE = 0.6;
/** Boss 血量加成(原 wave 20 bossBonusHpMul = 1.6 */
/** 怪物血量可调系数:最终 hp = HeroInfo 基础hp × MON_HP_MUL。
* 2.6 = 校准后怪物总血量 / 3 英雄 30s 输出≈30 DPS双方对砍 ≈ 30s 分胜负 */
export const MON_HP_MUL = 2.6;
/** 怪物攻击可调系数:最终 ap = HeroInfo 基础ap × MON_AP_MUL。
* 0.9 使 4 小怪+1 Boss 的总 DPS ≈ 2030s 恰好磨光 3 英雄总血量≈600 */
export const MON_AP_MUL = 0.9;
/** Boss 血量额外加成(在 MON_HP_MUL 之上乘算,突出压场定位 */
export const BOSS_HP_BONUS_MUL = 1.6;
/** 战斗超时(秒):超过后强制结算整局(残留怪销毁并扣留存分) */
export const BATTLE_TIMEOUT = 90;
@@ -371,12 +363,12 @@ export const TestModeConfig = {
/**
* Boss 组合生成引擎
* 随机抽一组 Boss 组合,按英雄强度反推怪物强度(最终 Boss 战档位)
* 随机抽一组 Boss 组合,怪物最终属性 = HeroInfo 基础属性 × 可调系数
*/
export class RogueSpawningEngine {
/**
* 生成一场战斗的怪物列表:随机一组 Boss 组合1 Boss + 组合小怪)。
* 生成一场战斗的怪物列表:随机一组 Boss 组合1 Boss + 4 小怪,属性走基础×系数)。
* Boss 压队尾(登场时由 MissionMonComp 统一布位)。
* @returns 怪物列表小怪在前、Boss 在尾)
*/
@@ -406,21 +398,10 @@ export class RogueSpawningEngine {
monsters.push(this.makeBoss());
}
// 属性缩放:直接取最终 Boss 战档位系数
const heroPower = this.getCurrentHeroPower();
const targetPower = heroPower * BOSS_TYPE_RATIO * MON_POWER_MUL;
const totalBasePower = monsters.reduce((sum, m) => {
const info = HeroInfo[m.uuid];
return sum + (info ? calcHeroPower(info, 1) : m.hp + m.ap);
}, 0);
if (totalBasePower > 0 && targetPower > 0) {
const hpScale = (targetPower * FINAL_HP_MUL) / totalBasePower;
const heroAvgAp = this.getCurrentHeroAttack();
for (const m of monsters) {
m.hp = Math.max(1, Math.round(m.hp * hpScale));
// ap = 玩家平均攻击 × 最终递进 × 基础系数 × 全局强度系数 × 类型系数
m.ap = Math.max(1, Math.round(heroAvgAp * AP_GROWTH_FINAL * AP_RELATIVE * MON_POWER_MUL * this.getApTypeFactor(m)));
}
// 属性缩放:基础属性 × 可调系数Boss 血量额外加成),校准目标双方对砍 ≈ 30s
for (const m of monsters) {
m.hp = Math.max(1, Math.round(m.hp * MON_HP_MUL * (m.isBoss ? BOSS_HP_BONUS_MUL : 1)));
m.ap = Math.max(1, Math.round(m.ap * MON_AP_MUL));
}
// 挂载组合技能Boss 专属池 / 小怪普通池)
@@ -443,45 +424,6 @@ export class RogueSpawningEngine {
reset(): void {
}
// ======================== 私有生成子算法 ========================
/**
* 计算场上存活英雄的总强度
* 通过 ECS 查询所有 HeroAttrsComp累加 calcHeroPower
*/
private getCurrentHeroPower(): number {
let total = 0;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const attrs = entity.get(HeroAttrsComp);
if (!attrs || attrs.is_dead || attrs.fac !== FacSet.HERO) return;
const info = HeroInfo[attrs.hero_uuid];
if (!info) return;
// 使用英雄当前等级(升级卡驱动),保证强度评估与实战一致
const lv = Math.max(1, attrs.lv || 1);
total += calcHeroPower(info, lv);
});
// 兜底:无英雄时返回基准强度,避免除零
return Math.max(total, 100);
}
/**
* 计算场上存活英雄的输出强度(用于怪物 ap 挂钩玩家,独立于总强度框架)。
* 取玩家英雄的"实时 ap 平均",怪物 ap 以此为锚,保证玩家输出越高、被反击越痛。
*/
private getCurrentHeroAttack(): number {
let totalAp = 0;
let count = 0;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const attrs = entity.get(HeroAttrsComp);
if (!attrs || attrs.is_dead || attrs.fac !== FacSet.HERO) return;
totalAp += Math.max(1, attrs.ap || 1);
count++;
});
// 兜底:无英雄时返回基准攻击,避免除零
if (count === 0) return 30;
return totalAp / count; // 平均单英雄攻击
}
/** 测试模式:完全绕过引擎逻辑 */
private generateTestWave(): GeneratedMonster[] {
const count = Math.max(1, TestModeConfig.spawnCount || 1);
@@ -536,20 +478,6 @@ export class RogueSpawningEngine {
};
}
/**
* 怪物 ap 类型系数Boss 上浮,远程/召唤稍降(避免玻璃大炮互秒),近战/重甲基准。
*/
private getApTypeFactor(m: GeneratedMonster): number {
if (m.isBoss) return 2.2; // Boss 单发重,符合"压场"定位
if (m.isBossGuard) return 1.2; // 护卫略高于杂鱼
switch (m.type) {
case MonType.Long: return 0.9;
case MonType.Summoner: return 0.7;
case MonType.Assassin: return 1.1;
default: return 1.0; // Melee / Heavy
}
}
/** 生成普通怪物 */
private makeMonster(type: MonType, spawnIndex: number): GeneratedMonster {
let uuids = MonList[type];

View File

@@ -1,4 +1,4 @@
import { v3, Vec3 ,Node} from "cc";
import { v3, Vec3, Node } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { EType, RType, SkillSet } from "../common/config/SkillSet";
import { SMoveDataComp } from "./SMoveComp";
@@ -26,46 +26,46 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
filter(): ecs.IMatcher {
return ecs.allOf(SMoveDataComp, SkillView);
}
entityEnter(entity: ecs.Entity): void {
const moveComp = entity.get(SMoveDataComp);
const skillView = entity.get(SkillView);
if (!moveComp || !skillView || !skillView.node) return;
// 获取技能配置
const skillConfig = SkillSet[moveComp.s_uuid];
if (!skillConfig) {
mLogger.warn(this.debugMode, 'SMoveSystem', `[SMoveSystem] 技能配置不存在: ${moveComp.s_uuid}`);
return;
}
// 根据配置设置移动速度与加速度
if (skillConfig.speed > 0) {
// 移动参数由 prefab 的 SkillView @property 决定speed<=0 时 Skill.load 已回退配置表值)
if (moveComp.speed <= 0) {
moveComp.speed = skillConfig.speed;
}
if (skillConfig.is_accel) {
moveComp.isAccelerate = true;
}
// 根据runType设置初始位置
this.initializePosition(moveComp, skillView);
// 开始移动(除了固定位置类型)
if (moveComp.runType !== RType.fixed && moveComp.runType !== RType.fixedEnd) {
moveComp.startMove();
}
mLogger.log(this.debugMode, 'SMoveSystem', `[SMoveSystem] 技能 ${skillConfig.name} 开始移动,类型: ${moveComp.runType}`);
}
/**
* 根据runType初始化技能位置
*/
private initializePosition(moveComp: SMoveDataComp, skillView: SkillView): void {
const node = skillView.node;
switch(moveComp.runType) {
switch (moveComp.runType) {
case RType.linear:
// linear类型根据atk_x和atk_y调整起始位置
const adjustedStartPos = v3(
@@ -73,35 +73,35 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
moveComp.startPos.y + moveComp.atk_y,
moveComp.startPos.z
);
const originTargetPos = v3(
moveComp.targetPos.x,
moveComp.targetPos.y,
moveComp.targetPos.z
);
const direction = new Vec3();
Vec3.subtract(direction, originTargetPos, adjustedStartPos);
// 延长终止点统一消亡点的x坐标为 +-500
const targetX = moveComp.scale > 0 ? 500 : -500;
let targetY = originTargetPos.y;
if (Math.abs(direction.x) > 0.01) {
const slope = direction.y / direction.x;
targetY = adjustedStartPos.y + slope * (targetX - adjustedStartPos.x);
}
const adjustedTargetPos = v3(
targetX,
targetY,
originTargetPos.z
);
moveComp.startPos.set(adjustedStartPos);
moveComp.targetPos.set(adjustedTargetPos);
node.setPosition(adjustedStartPos);
// 设置旋转角度
const dirForAngle = new Vec3();
Vec3.subtract(dirForAngle, adjustedTargetPos, adjustedStartPos);
@@ -113,12 +113,12 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
node.angle = angle;
}
break;
case RType.fixed:
// 固定起始位置
node.setPosition(moveComp.startPos.x, node.position.y, 0);
break;
case RType.fixedEnd:
// 固定结束位置
node.setPosition(moveComp.targetPos.x > 360 ? 300 : moveComp.targetPos.x, moveComp.targetPos.y, 0);
@@ -154,26 +154,26 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
update(entity: ecs.Entity): void {
if(!smc.mission.play ) return;
if(smc.mission.pause) return
if (!smc.mission.play) return;
if (smc.mission.pause) return
const moveComp = entity.get(SMoveDataComp);
const skillView = entity.get(SkillView);
if (!moveComp || !skillView || !skillView.node) return;
// 更新移动进度
const isMoving = moveComp.updateProgress(this.dt);
if (isMoving) {
// 更新节点位置
skillView.node.setPosition(moveComp.currentPos);
// 处理自动旋转(贝塞尔移动)
if (moveComp.runType === RType.bezier) {
this.updateRotation(skillView.node, moveComp);
}
}
// 检查移动完成
if (moveComp.isCompleted && moveComp.autoDestroy) {
// 根据结束类型决定是否销毁
@@ -185,7 +185,7 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
}
}
/**
* 更新节点旋转(用于贝塞尔移动)
*/
@@ -193,29 +193,29 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (moveComp.progress < 1) {
// 计算下一帧的位置来确定方向
const nextProgress = Math.min(moveComp.progress + 0.01, 1);
const t = moveComp.isAccelerate ?
(nextProgress * nextProgress * 0.7 + nextProgress * 0.3) :
const t = moveComp.isAccelerate ?
(nextProgress * nextProgress * 0.7 + nextProgress * 0.3) :
nextProgress;
const nextPos = v3(0, 0, 0);
// 计算下一个位置
const oneMinusT = 1 - t;
const oneMinusTSquared = oneMinusT * oneMinusT;
const tSquared = t * t;
const twoOneMinusTt = 2 * oneMinusT * t;
nextPos.x = oneMinusTSquared * moveComp.startPos.x +
twoOneMinusTt * moveComp.controlPoint.x +
tSquared * moveComp.targetPos.x;
nextPos.y = oneMinusTSquared * moveComp.startPos.y +
twoOneMinusTt * moveComp.controlPoint.y +
tSquared * moveComp.targetPos.y;
nextPos.x = oneMinusTSquared * moveComp.startPos.x +
twoOneMinusTt * moveComp.controlPoint.x +
tSquared * moveComp.targetPos.x;
nextPos.y = oneMinusTSquared * moveComp.startPos.y +
twoOneMinusTt * moveComp.controlPoint.y +
tSquared * moveComp.targetPos.y;
// 计算方向角度
const direction = new Vec3();
Vec3.subtract(direction, nextPos, moveComp.currentPos);
if (direction.length() > 0.01) {
let angle = Math.atan2(direction.y, direction.x) * (180 / Math.PI);
if (moveComp.scale < 0) {
@@ -236,15 +236,15 @@ export class SMoveHelper {
*/
static setupMoveForSkill(entity: ecs.Entity, startPos: Vec3, targetPos: Vec3, s_uuid: number): void {
let moveComp = entity.get(SMoveDataComp);
if (!moveComp) {
moveComp = entity.add(SMoveDataComp);
}
moveComp.startPos.set(startPos);
moveComp.targetPos.set(targetPos);
moveComp.s_uuid = s_uuid;
// 从技能配置获取默认参数
const skillConfig = SkillSet[s_uuid];
if (skillConfig) {
@@ -253,7 +253,7 @@ export class SMoveHelper {
if (skillConfig.is_accel) moveComp.isAccelerate = true;
}
}
/**
* 检查技能是否正在移动
*/
@@ -261,7 +261,7 @@ export class SMoveHelper {
const moveComp = entity.get(SMoveDataComp);
return moveComp ? moveComp.isMoving : false;
}
/**
* 获取技能移动统计信息
*/
@@ -269,7 +269,7 @@ export class SMoveHelper {
const moveComp = entity.get(SMoveDataComp);
return moveComp ? moveComp.getMoveStats() : null;
}
/**
* 停止技能移动
*/

View File

@@ -146,7 +146,7 @@ export class Skill extends ecs.Entity {
if (node.isValid) node.destroy();
return;
}
if (config.EType != EType.collision) {
if (SView.endType != EType.collision) {
const collider = node.getComponent(BoxCollider2D);
if (collider) {
collider.enabled = false
@@ -174,8 +174,10 @@ export class Skill extends ecs.Entity {
sMoveCom.targetPos.set(targetPos);
sMoveCom.s_uuid = s_uuid;
sMoveCom.scale = caster.node.scale.x < 0 ? -1 : 1;
sMoveCom.runType = config.RType;
sMoveCom.endType = config.EType;
// runType/endType/speed/time 由 prefab 的 SkillView @property 决定,配置表同名字段仅作兜底
sMoveCom.runType = SView.runType;
sMoveCom.endType = SView.endType;
sMoveCom.speed = SView.speed > 0 ? SView.speed : config.speed;
sMoveCom.bezierStartHeight = config.bezier_start_y ?? sMoveCom.bezierStartHeight;
sMoveCom.bezierMidHeight = config.bezier_mid_y ?? sMoveCom.bezierMidHeight;
sMoveCom.bezierArc = config.bezier_arc ?? sMoveCom.bezierArc;
@@ -183,12 +185,12 @@ export class Skill extends ecs.Entity {
sMoveCom.atk_x = SView.atk_x;
sMoveCom.atk_y = SView.atk_y;
if (config.EType === EType.timeEnd) {
if (SView.endType === EType.timeEnd) {
let sTimeCom = this.get(StimeDataComp);
if (!sTimeCom) sTimeCom = this.add(StimeDataComp);
sTimeCom.reset();
sTimeCom.s_uuid = s_uuid;
sTimeCom.totalTime = Math.max(1, config.time ?? 0);
sTimeCom.totalTime = Math.max(1, SView.time > 0 ? SView.time : (config.time ?? 0));
sTimeCom.hitInterval = Math.max(0.5, config.hitcd || 0);
} else {
const sTimeCom = this.get(StimeDataComp);

View File

@@ -1,8 +1,8 @@
import { _decorator, Animation, CCInteger, Collider2D, Contact2DType, UITransform, v3, Vec3 } from "cc";
import { _decorator, Animation, CCFloat, CCInteger, Collider2D, Contact2DType, Enum, UITransform, v3, Vec3 } 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 { HeroViewComp } from "../hero/HeroViewComp";
import { DTType, EType, SkillConfig, SkillSet } from "../common/config/SkillSet";
import { DTType, EType, RType, SkillConfig, SkillSet } from "../common/config/SkillSet";
import { SDataCom } from "./SDataCom";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { DamageQueueHelper } from "../hero/DamageQueueComp";
@@ -22,13 +22,29 @@ export class SkillView extends CCComp {
@property({ type: CCInteger })
atk_y: number = 0
/** 运动类型:直线/贝塞尔/固定起点/固定终点(由 prefab 配置,优先级高于 SkillSet */
@property({ type: Enum(RType), tooltip: "技能特效运动类型" })
runType: RType = RType.linear;
/** 消亡条件:动画结束/计时结束/碰撞次数结束(由 prefab 配置,优先级高于 SkillSet */
@property({ type: Enum(EType), tooltip: "技能特效消亡条件" })
endType: EType = EType.collision;
/** 移动速度(由 prefab 配置,<=0 时回退 SkillSet.speed */
@property({ type: CCFloat, slide: true, range: [0, 2000, 10], tooltip: "技能特效移动速度" })
speed: number = 0;
/** timeEnd 持续时间(秒),仅 endType=timeEnd 时生效(由 prefab 配置,<=0 时回退 SkillSet.time */
@property({ type: CCFloat, slide: true, range: [0, 30, 0.1], tooltip: "timeEnd 持续时间(秒)" })
time: number = 0;
private debugMode: boolean = false;
anim:Animation=null;
group:number=0;
SConf:SkillConfig=null;
sData:SDataCom=null;
s_uuid:number=1001
anim: Animation = null;
group: number = 0;
SConf: SkillConfig = null;
sData: SDataCom = null;
s_uuid: number = 1001
private collider: Collider2D = null; // 缓存碰撞体引用
private pendingDisableCollider: boolean = false;
private isDisposing: boolean = false;
@@ -41,25 +57,25 @@ export class SkillView extends CCComp {
this.pendingDisableCollider = false;
this.isDisposing = false;
this.collider = this.getComponent(Collider2D);
if(this.collider) {
if (this.collider) {
this.collider.group = this.group;
this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
this.collider.enabled = this.SConf?.EType === EType.collision;
this.collider.enabled = this.endType === EType.collision;
}
if(this.node.getComponent(Animation)){
if (this.node.getComponent(Animation)) {
let anim = this.node.getComponent(Animation);
mLogger.log(this.debugMode, 'SkillView', "[SkillCom]:has anim",anim)
mLogger.log(this.debugMode, 'SkillView', "[SkillCom]:has anim", anim)
anim.off(Animation.EventType.FINISHED, this.onAnimationFinished, this);
anim.on(Animation.EventType.FINISHED, this.onAnimationFinished, this);
// 对象池复用时,需要手动播放默认动画(因为 Play On Load 只在首次生效)
if (anim.defaultClip) {
anim.play(anim.defaultClip.name);
}
}
}
onBeginContact (seCol: Collider2D, oCol: Collider2D) {
onBeginContact(seCol: Collider2D, oCol: Collider2D) {
if (!this.sData || !this.SConf) {
mLogger.warn(this.debugMode, 'SkillView', '[SkillView] onBeginContact 缺少 sData 或 SConf忽略此次碰撞');
return;
@@ -126,17 +142,23 @@ export class SkillView extends CCComp {
this.apply_damage(targetView, this.sData.hit_count)
}
onAnimationFinished(){
// animationEnd 类型:动画结束后再关闭碰撞并销毁
if(this.SConf.EType==EType.animationEnd){
onAnimationFinished() {
// animationEnd 类型:动画结束后再关闭碰撞并销毁
if (this.endType == EType.animationEnd) {
this.disable_collider_now();
this.ent.destroy()
}
return;
}
// fixed 类型不移动,无法通过移动完成销毁;动画播完作为兜底销毁时机
if (this.runType == RType.fixed) {
this.disable_collider_now();
this.ent.destroy()
}
}
// 动画帧事件 atk仅负责开启一帧碰撞窗口不负责伤害与计数
public atk(args:any){
if(!this.SConf) return;
if(this.SConf.EType==EType.collision) return
public atk(args: any) {
if (!this.SConf) return;
if (this.endType == EType.collision) return
if (this.enable_collider_safely()) {
mLogger.log(this.debugMode, 'SkillView', `[SkillView] [${this.SConf?.name}] 开启碰撞检测`);
this.scheduleOnce(() => {
@@ -179,7 +201,7 @@ export class SkillView extends CCComp {
finalRatio,
);
}
close_collider(){
close_collider() {
if (!this.collider) return;
if (this.pendingDisableCollider && !this.collider.enabled) return;
this.pendingDisableCollider = true;

View File

@@ -146,10 +146,10 @@ export class TooltipCom extends CCComp {
this.node.setPosition(v3(this.node.position.x, currentY - 60));
this.node.setSiblingIndex(topSiblingIndex);
break;
case TooltipTypes.shield: // 护盾格挡
case TooltipTypes.shield: // 护盾减免
this.node.setPosition(v3(this.node.position.x + offsetX, currentY));
this.node.setSiblingIndex(topSiblingIndex);
this.setupLabel("add_life", "hp", "格挡");
this.setupLabel("add_life", "hp", "减免");
isHeal = true;
break;
}

View File

@@ -0,0 +1,83 @@
/**
* 一次性迁移脚本:把 SkillSet 中的 RType/EType/speed/time 刷入各技能 prefab 的 SkillView 序列化字段。
* 用法: node tools/sync-skill-prefab.js
* 注意: 直接改写 prefab JSON运行前请保证工作区已提交。
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const SKILL_SET = path.join(ROOT, 'assets/script/game/common/config/SkillSet.ts');
const PREFAB_DIR = path.join(ROOT, 'assets/resources/game/skill/atk');
// SkillView 组件在 prefab 中的脚本类 cid
const SKILL_VIEW_CID = '57aabs7TE1J5obTAZczc+64';
const RTYPE = { linear: 0, bezier: 1, fixed: 2, fixedEnd: 3 };
const ETYPE = { animationEnd: 0, timeEnd: 1, collision: 2 };
/** 从 SkillSet.ts 解析 sp_name -> {runType, endType, speed, time} */
function parseSkillSet(src) {
const map = new Map();
// 匹配每个技能条目块
const entryRe = /\d+:\s*\{([\s\S]*?)\n\s*\},/g;
let m;
while ((m = entryRe.exec(src)) !== null) {
const body = m[1];
const sp = body.match(/sp_name:\s*"([^"]+)"/);
if (!sp) continue;
const spName = sp[1];
const rt = body.match(/RType:\s*RType\.(\w+)/);
const et = body.match(/EType:\s*EType\.(\w+)/);
const speed = body.match(/speed:\s*([\d.]+)/);
const time = body.match(/\btime:\s*([\d.]+)/);
const entry = {
runType: rt ? RTYPE[rt[1]] : undefined,
endType: et ? ETYPE[et[1]] : undefined,
speed: speed ? Number(speed[1]) : undefined,
time: time ? Number(time[1]) : 0,
};
// 多个技能可共用同一 sp_name保留首个非 undefined 组合
if (!map.has(spName)) map.set(spName, entry);
}
return map;
}
function main() {
const src = fs.readFileSync(SKILL_SET, 'utf8');
const confMap = parseSkillSet(src);
const files = fs.readdirSync(PREFAB_DIR).filter(f => f.endsWith('.prefab'));
const report = { updated: [], skipped: [], noConf: [] };
for (const file of files) {
const spName = path.basename(file, '.prefab');
const conf = confMap.get(spName);
const fp = path.join(PREFAB_DIR, file);
const json = JSON.parse(fs.readFileSync(fp, 'utf8'));
// 定位 SkillView 组件
const comp = json.find(o => o && o.__type__ === SKILL_VIEW_CID);
if (!comp) { report.skipped.push(file + ' (no SkillView)'); continue; }
if (!conf) { report.noConf.push(file); continue; }
if (conf.runType !== undefined) comp.runType = conf.runType;
if (conf.endType !== undefined) comp.endType = conf.endType;
if (conf.speed !== undefined) comp.speed = conf.speed;
comp.time = conf.time ?? 0;
fs.writeFileSync(fp, JSON.stringify(json, null, 2) + '\n', 'utf8');
report.updated.push(`${file} -> runType=${comp.runType} endType=${comp.endType} speed=${comp.speed} time=${comp.time}`);
}
console.log('=== updated ===');
report.updated.forEach(s => console.log(' ' + s));
console.log('=== no config matched ===');
report.noConf.forEach(s => console.log(' ' + s));
console.log('=== skipped ===');
report.skipped.forEach(s => console.log(' ' + s));
}
main();