2 Commits

Author SHA1 Message Date
panw
9a1d517aa9 refactor(game): 重构怪物波次生成逻辑,移除排队机制
- 移除 IWaveSlot 中的 monCount 字段,改为固定 6 个槽位
- Boss 现在固定占用 2 个槽位且只能放置在 1、3、5 号位
- 每波开始时立即生成所有怪物,不再支持死亡后排队刷怪
- 简化 MissionMonComp 中的槽位管理逻辑,移除 slotSpawnQueues 等复杂结构
2026-04-07 16:36:49 +08:00
panw
c7e46fc591 feat(hero): 为boss单位调整头顶UI位置并添加标识图标
调整HeroViewComp中boss单位的top_node位置,从y=80提升至y=105以更好显示
为mo6、mud4、mud5三个boss预制体添加ItemIcon_Skull_Boss_Red标识图标
更新预制体结构,包括组件引用、材质和颜色属性覆盖
2026-04-07 16:23:08 +08:00
6 changed files with 908 additions and 980 deletions

View File

@@ -37,23 +37,23 @@
"_active": true,
"_components": [
{
"__id__": 54
"__id__": 66
},
{
"__id__": 56
"__id__": 68
},
{
"__id__": 58
"__id__": 70
},
{
"__id__": 60
"__id__": 72
},
{
"__id__": 62
"__id__": 74
}
],
"_prefab": {
"__id__": 64
"__id__": 76
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -337,7 +337,10 @@
"__prefab": {
"__id__": 14
},
"_customMaterial": null,
"_customMaterial": {
"__uuid__": "2fcd55a9-38ca-45aa-9164-68e48aaf51ce",
"__expectedType__": "cc.Material"
},
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
@@ -830,32 +833,202 @@
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [],
"mountedChildren": [
{
"__id__": 48
}
],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 48
"__id__": 56
},
{
"__id__": 50
"__id__": 58
},
{
"__id__": 51
"__id__": 59
},
{
"__id__": 52
"__id__": 60
},
{
"__id__": 53
"__id__": 61
},
{
"__id__": 62
},
{
"__id__": 64
}
],
"removedComponents": []
},
{
"__type__": "CCPropertyOverrideInfo",
"__type__": "cc.MountedChildrenInfo",
"targetInfo": {
"__id__": 49
},
"nodes": [
{
"__id__": 50
}
]
},
{
"__type__": "cc.TargetInfo",
"localID": [
"5fqU0L3/FOhKaco5UkHuWT"
]
},
{
"__type__": "cc.Node",
"_name": "ItemIcon_Skull_Boss_Red",
"_objFlags": 0,
"__editorExtras__": {
"mountedRoot": {
"__id__": 45
}
},
"_parent": {
"__id__": 45
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 51
},
{
"__id__": 53
}
],
"_prefab": {
"__id__": 55
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0.9007692307691286,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.09999999999999999,
"y": 0.09999999999999999,
"z": 1
},
"_mobility": 0,
"_layer": 1,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 50
},
"_enabled": true,
"__prefab": {
"__id__": 52
},
"_contentSize": {
"__type__": "cc.Size",
"width": 126,
"height": 112
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "c9TsqhE4hLzZrjpuZNqOno"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 50
},
"_enabled": true,
"__prefab": {
"__id__": 54
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@eb1e3",
"__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__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "955O5G/ZpDzZMIefIeLnat"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "51mbNeFzBNjL08MItkj3IX",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 57
},
"propertyPath": [
"_name"
],
@@ -870,7 +1043,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 49
"__id__": 57
},
"propertyPath": [
"_lpos"
@@ -885,7 +1058,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 49
"__id__": 57
},
"propertyPath": [
"_lrot"
@@ -901,7 +1074,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 49
"__id__": 57
},
"propertyPath": [
"_euler"
@@ -916,18 +1089,61 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 49
"__id__": 57
},
"propertyPath": [
"_lscale"
],
"value": {
"__type__": "cc.Vec3",
"x": 1.4,
"y": 1.4,
"x": 1.3,
"y": 1.3,
"z": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 63
},
"propertyPath": [
"_color"
],
"value": {
"__type__": "cc.Color",
"r": 255,
"g": 204,
"b": 0,
"a": 255
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"42Iqibz0VOArXYqoFJhNMf"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 65
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"4bGUhFXlZF/Jdkcq0HY9ai"
]
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -938,7 +1154,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 55
"__id__": 67
},
"_contentSize": {
"__type__": "cc.Size",
@@ -966,7 +1182,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 57
"__id__": 69
},
"anm": {
"__id__": 11
@@ -987,7 +1203,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 59
"__id__": 71
},
"debugMode": false,
"_id": ""
@@ -1006,7 +1222,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 61
"__id__": 73
},
"enabledContactListener": true,
"bullet": false,
@@ -1040,7 +1256,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 63
"__id__": 75
},
"tag": 0,
"_group": 4,

View File

@@ -29,10 +29,19 @@
},
{
"__id__": 32
},
{
"__id__": 45
}
],
"_active": true,
"_components": [
{
"__id__": 69
},
{
"__id__": 71
},
{
"__id__": 73
},
@@ -41,16 +50,10 @@
},
{
"__id__": 77
},
{
"__id__": 79
},
{
"__id__": 81
}
],
"_prefab": {
"__id__": 83
"__id__": 79
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -633,10 +636,10 @@
"__id__": 32
},
"asset": {
"__uuid__": "50c3d5e4-49f8-4bd7-a15b-cda359a0ae5c",
"__uuid__": "02e38f62-860e-4bb9-86d9-a59f073e2881",
"__expectedType__": "cc.Prefab"
},
"fileId": "5fqU0L3/FOhKaco5UkHuWT",
"fileId": "54R/aYBglLI4Jn5pm++Jx8",
"instance": {
"__id__": 34
},
@@ -644,67 +647,225 @@
},
{
"__type__": "cc.PrefabInstance",
"fileId": "f4ModMncVDMrUHuqBiagGP",
"fileId": "6eEf1iw+VOYqP0C94fVBmY",
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 35
},
{
"__id__": 37
},
{
"__id__": 38
},
{
"__id__": 39
},
{
"__id__": 40
},
{
"__id__": 41
},
{
"__id__": 43
}
],
"removedComponents": []
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 36
},
"propertyPath": [
"_name"
],
"value": "plus"
},
{
"__type__": "cc.TargetInfo",
"localID": [
"54R/aYBglLI4Jn5pm++Jx8"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 36
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": -3.73,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 36
},
"propertyPath": [
"_lrot"
],
"value": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 36
},
"propertyPath": [
"_euler"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 36
},
"propertyPath": [
"_lscale"
],
"value": {
"__type__": "cc.Vec3",
"x": 1.4,
"y": 1.4,
"z": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 42
},
"propertyPath": [
"_color"
],
"value": {
"__type__": "cc.Color",
"r": 255,
"g": 0,
"b": 0,
"a": 255
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"7fFzhENiBDf5TeBL7e/kJ+",
"9a+65VIghBm4HQxuHPQ/mg"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 44
},
"propertyPath": [
"_color"
],
"value": {
"__type__": "cc.Color",
"r": 255,
"g": 0,
"b": 0,
"a": 255
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"73Bl2rptBKGr6i8cMQId1k",
"a5MYbJnJVJx4g7hgv8+4Vv"
]
},
{
"__type__": "cc.Node",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_prefab": {
"__id__": 46
},
"__editorExtras__": {}
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 45
},
"asset": {
"__uuid__": "50c3d5e4-49f8-4bd7-a15b-cda359a0ae5c",
"__expectedType__": "cc.Prefab"
},
"fileId": "5fqU0L3/FOhKaco5UkHuWT",
"instance": {
"__id__": 47
},
"targetOverrides": null
},
{
"__type__": "cc.PrefabInstance",
"fileId": "2246jD+21K84zKG6dpKPCX",
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [
{
"__id__": 35
"__id__": 48
}
],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 43
"__id__": 56
},
{
"__id__": 45
},
{
"__id__": 46
},
{
"__id__": 47
},
{
"__id__": 48
},
{
"__id__": 49
},
{
"__id__": 51
},
{
"__id__": 53
},
{
"__id__": 55
},
{
"__id__": 57
"__id__": 58
},
{
"__id__": 59
},
{
"__id__": 60
},
{
"__id__": 61
},
{
"__id__": 63
"__id__": 62
},
{
"__id__": 65
"__id__": 64
},
{
"__id__": 67
"__id__": 66
},
{
"__id__": 69
},
{
"__id__": 71
"__id__": 68
}
],
"removedComponents": []
@@ -712,11 +873,11 @@
{
"__type__": "cc.MountedChildrenInfo",
"targetInfo": {
"__id__": 36
"__id__": 49
},
"nodes": [
{
"__id__": 37
"__id__": 50
}
]
},
@@ -728,33 +889,33 @@
},
{
"__type__": "cc.Node",
"_name": "UI_Play_Skull_01",
"_name": "ItemIcon_Skull_Boss_Red",
"_objFlags": 0,
"__editorExtras__": {
"mountedRoot": {
"__id__": 32
"__id__": 45
}
},
"_parent": {
"__id__": 32
"__id__": 45
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 38
"__id__": 51
},
{
"__id__": 40
"__id__": 53
}
],
"_prefab": {
"__id__": 42
"__id__": 55
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 27.672,
"y": 10.812,
"z": 0
},
"_lrot": {
@@ -766,8 +927,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.4,
"y": 0.4,
"x": 0.09999999999999999,
"y": 0.09999999999999999,
"z": 1
},
"_mobility": 0,
@@ -786,16 +947,16 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 37
"__id__": 50
},
"_enabled": true,
"__prefab": {
"__id__": 39
"__id__": 52
},
"_contentSize": {
"__type__": "cc.Size",
"width": 126,
"height": 98
"height": 112
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -806,7 +967,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "21nuE9AOdJyZLOT8zAS8rB"
"fileId": "c9TsqhE4hLzZrjpuZNqOno"
},
{
"__type__": "cc.Sprite",
@@ -814,11 +975,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 37
"__id__": 50
},
"_enabled": true,
"__prefab": {
"__id__": 41
"__id__": 54
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -831,7 +992,7 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "6165ffc9-a838-4a33-b569-bdbaaab0e6b4@6f366",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@eb1e3",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
@@ -846,12 +1007,15 @@
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "94tEZ7yJtHPIcGXAdoMd13"
"fileId": "955O5G/ZpDzZMIefIeLnat"
},
{
"__type__": "cc.PrefabInfo",
@@ -861,7 +1025,7 @@
"asset": {
"__id__": 0
},
"fileId": "10Q5sMPLZM/5G6SM6nQiP+",
"fileId": "51mbNeFzBNjL08MItkj3IX",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
@@ -869,7 +1033,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 44
"__id__": 57
},
"propertyPath": [
"_name"
@@ -885,7 +1049,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 44
"__id__": 57
},
"propertyPath": [
"_lpos"
@@ -893,14 +1057,14 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 204.339,
"y": 105,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 44
"__id__": 57
},
"propertyPath": [
"_lrot"
@@ -916,7 +1080,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 44
"__id__": 57
},
"propertyPath": [
"_euler"
@@ -931,204 +1095,65 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 44
"__id__": 57
},
"propertyPath": [
"_lscale"
],
"value": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"x": 1.3,
"y": 1.3,
"z": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 50
"__id__": 63
},
"propertyPath": [
"_contentSize"
"_color"
],
"value": {
"__type__": "cc.Size",
"width": 100,
"height": 15
"__type__": "cc.Color",
"r": 255,
"g": 204,
"b": 0,
"a": 255
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"01jesPhj9CEZDz66U5IkN4"
"42Iqibz0VOArXYqoFJhNMf"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 52
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 100,
"height": 15
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"73XD/xCtdN56MXQv4d3Zpr"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 54
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 100,
"height": 15
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"6eLgGcjghBaJCJdqSzqDJp"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 56
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 100,
"height": 15
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"47UQtk5rhPMK9KaOrIeeUz"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 58
"__id__": 65
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": -50,
"y": 0,
"x": 0,
"y": 9.911,
"z": 0
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"03VJ/IU/tHh6ufeq0Tr5KW"
"4bGUhFXlZF/Jdkcq0HY9ai"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 60
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 340,
"height": 56.66666666666667
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"48dZn31pVBZIHXQOp8UtqL"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 62
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 100,
"height": 15
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"7f9LGtOY9DM6ZTL5+fc4gA"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 64
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": -50,
"y": 0,
"z": 0
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"61x78YGExIH5sbDV+PvuuK"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 66
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 340,
"height": 56.66666666666667
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"c7Cwn33JNAvp+kuH9WDEb+"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 68
"__id__": 67
},
"propertyPath": [
"_active"
@@ -1144,44 +1169,18 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 70
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 100,
"height": 15
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"57EU4BJPlAgpvAWLFbIF5T"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 72
"__id__": 67
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": -50,
"x": 0,
"y": 0,
"z": 0
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"2cGho1ykJKfpUTHn50V/AJ"
]
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -1192,7 +1191,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 74
"__id__": 70
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1220,7 +1219,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 76
"__id__": 72
},
"anm": {
"__id__": 11
@@ -1241,7 +1240,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 78
"__id__": 74
},
"debugMode": false,
"_id": ""
@@ -1260,7 +1259,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 80
"__id__": 76
},
"enabledContactListener": true,
"bullet": false,
@@ -1294,7 +1293,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 82
"__id__": 78
},
"tag": 0,
"_group": 4,
@@ -1330,6 +1329,9 @@
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": [
{
"__id__": 45
},
{
"__id__": 32
},

File diff suppressed because it is too large Load Diff

View File

@@ -115,6 +115,7 @@ export class HeroViewComp extends CCComp {
this.top_node = this.node.getChildByName("top");
this.topOpacity = this.top_node.getComponent(UIOpacity) || this.top_node.addComponent(UIOpacity);
this.top_node.setPosition(0, 80, 0);
if(this.model.is_boss) this.top_node.setPosition(0, 105, 0);
this.topBasePos = this.top_node.position.clone();
const hpNode = this.top_node.getChildByName("hp");
if(this.model.fac==FacSet.HERO){

View File

@@ -31,16 +31,9 @@ export class MissionMonCompComp extends CCComp {
level: number,
}> = [];
private slotSpawnQueues: Array<Array<{
uuid: number,
isBoss: boolean,
upType: UpType,
monLv: number,
slotsPerMon: number,
}>> = [];
private slotOccupiedEids: Array<number | null> = [];
private slotRangeTypes: Array<number> = [];
private slotSizes: Array<number> = []; // 记录每个槽位原本被配置为多大尺寸的怪,用于后续校验
private static readonly MAX_SLOTS = 6;
private slotOccupiedEids: Array<number | null> = Array(6).fill(null);
/** 全局生成顺序计数器,用于层级管理(预留) */
private globalSpawnOrder: number = 0;
/** 插队刷怪处理计时器 */
@@ -91,7 +84,6 @@ export class MissionMonCompComp extends CCComp {
if(smc.mission.stop_mon_action) return;
if(!smc.mission.in_fight) return;
this.refreshSlotOccupancy();
this.trySpawnFromSlotQueues();
this.tryAdvanceWave();
if(!smc.mission.in_fight) return;
if(smc.mission.stop_spawn_mon) return;
@@ -102,14 +94,35 @@ export class MissionMonCompComp extends CCComp {
if (this.MonQueue.length <= 0) return;
this.queueTimer += dt;
if (this.queueTimer < 0.15) return;
this.queueTimer = 0;
const item = this.MonQueue.shift();
if (!item) return;
const upType = this.getRandomUpType();
const item = this.MonQueue[0];
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) || MonList[MonType.LongBoss].includes(item.uuid);
// 简单推断:如果是 boss 默认给 2 格(你也可以从配置里反查或者加专门的英雄表配置)
const slotsPerMon = isBoss ? 2 : 1;
this.enqueueMonsterRequest(item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)), slotsPerMon, true);
const slotsPerMon = isBoss ? 2 : 1;
let slotIndex = -1;
if (slotsPerMon === 2) {
// Boss 只能放在 0, 2, 4
for (const idx of [0, 2, 4]) {
if (!this.slotOccupiedEids[idx] && !this.slotOccupiedEids[idx + 1]) {
slotIndex = idx;
break;
}
}
} else {
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
if (!this.slotOccupiedEids[i]) {
slotIndex = i;
break;
}
}
}
if (slotIndex !== -1) {
this.MonQueue.shift();
this.queueTimer = 0;
const upType = this.getRandomUpType();
this.addMonsterBySlot(slotIndex, item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)), slotsPerMon);
}
}
private startNextWave() {
@@ -133,7 +146,7 @@ export class MissionMonCompComp extends CCComp {
}
private tryAdvanceWave() {
if (this.hasPendingSlotQueue()) return;
if (this.MonQueue.length > 0) return;
if (this.hasActiveSlotMonster()) return;
if (smc.vmdata.mission_data.mon_num > 0) return;
this.startNextWave();
@@ -171,73 +184,80 @@ export class MissionMonCompComp extends CCComp {
private resetSlotSpawnData(wave: number = 1) {
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
this.slotOccupiedEids = Array(MissionMonCompComp.MAX_SLOTS).fill(null);
let bosses: any[] = [];
let normals: any[] = [];
let totalSlots = 0;
let totalMonsters = 0;
for (const slot of config) {
const slotsPerMon = slot.slotsPerMon || 1;
const monCount = slot.monCount || 1;
totalSlots += slot.count * slotsPerMon;
totalMonsters += slot.count * monCount;
}
this.waveTargetCount = totalMonsters;
this.waveSpawnedCount = 0;
this.slotSpawnQueues = Array.from(
{ length: totalSlots },
() => []
);
this.slotOccupiedEids = Array.from(
{ length: totalSlots },
() => null
);
this.slotRangeTypes = [];
this.slotSizes = [];
let slotIndex = 0;
for (const slot of config) {
const slotsPerMon = slot.slotsPerMon || 1;
const monCount = slot.monCount || 1;
const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss;
for (let i = 0; i < slot.count; i++) {
const currentSlotIndex = slotIndex;
// 设置槽位类型和大小
for (let s = 0; s < slotsPerMon; s++) {
this.slotRangeTypes.push(slot.type);
this.slotSizes.push(slotsPerMon);
slotIndex++;
const uuid = this.getRandomUuidByType(slot.type);
const upType = this.getRandomUpType();
const req = { uuid, isBoss, upType, monLv: wave, slotsPerMon };
if (isBoss || slotsPerMon === 2) {
bosses.push(req);
} else {
normals.push(req);
}
// 根据配置数量,直接在波次开始时把该坑位要刷的所有怪排入队列
for (let m = 0; m < monCount; m++) {
const uuid = this.getRandomUuidByType(slot.type);
const upType = this.getRandomUpType();
const request = { uuid, isBoss, upType, monLv: wave, slotsPerMon };
this.slotSpawnQueues[currentSlotIndex].push(request);
}
}
this.waveTargetCount = bosses.length + normals.length;
this.waveSpawnedCount = 0;
// Boss 只能放在 0, 2, 4 (即 1, 3, 5 号位)
let bossAllowedIndices = [0, 2, 4];
let assignedSlots = new Array(MissionMonCompComp.MAX_SLOTS).fill(null);
for (const boss of bosses) {
let placed = false;
for (const idx of bossAllowedIndices) {
if (!assignedSlots[idx] && !assignedSlots[idx + 1]) {
assignedSlots[idx] = boss;
assignedSlots[idx + 1] = "occupied"; // 占位
placed = true;
break;
}
}
if (!placed) {
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] No slot for boss!");
}
}
for (const normal of normals) {
let placed = false;
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
if (!assignedSlots[i]) {
assignedSlots[i] = normal;
placed = true;
break;
}
}
if (!placed) {
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] No slot for normal monster!");
}
}
// 立即生成本波所有怪物
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
const req = assignedSlots[i];
if (req && req !== "occupied") {
this.addMonsterBySlot(i, req.uuid, req.isBoss, req.upType, req.monLv, req.slotsPerMon);
}
}
}
private hasPendingSlotQueue() {
for (let i = 0; i < this.slotSpawnQueues.length; i++) {
if (this.slotSpawnQueues[i].length > 0) return true;
}
return false;
}
private hasActiveSlotMonster() {
for (let i = 0; i < this.slotOccupiedEids.length; i++) {
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
if (this.slotOccupiedEids[i]) return true;
}
return false;
}
private refreshSlotOccupancy() {
for (let i = 0; i < this.slotOccupiedEids.length; i++) {
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
const eid = this.slotOccupiedEids[i];
if (!eid) continue;
const entity = ecs.getEntityByEid(eid);
@@ -246,118 +266,12 @@ export class MissionMonCompComp extends CCComp {
continue;
}
const attrs = entity.get(HeroAttrsComp);
if (!attrs) {
if (!attrs || attrs.hp <= 0) {
this.slotOccupiedEids[i] = null;
}
}
}
private getSlotQueueLoad(slotIndex: number): number {
const occupied = this.slotOccupiedEids[slotIndex] ? 1 : 0;
return occupied + this.slotSpawnQueues[slotIndex].length;
}
private resolveMonType(uuid: number): number {
for (const key in MonList) {
const list = MonList[key as unknown as number] as number[];
if (list && list.includes(uuid)) {
return Number(key);
}
}
return MonType.Melee;
}
private pickAssignSlotIndex(uuid: number, slotsPerMon: number): number {
const expectedType = this.resolveMonType(uuid);
let bestLoad = Number.MAX_SAFE_INTEGER;
let bestIndex = -1;
// 尝试找到一个连续的、且类型匹配并且 slotSizes 符合的空位
for (let i = 0; i <= this.slotRangeTypes.length - slotsPerMon; i++) {
let valid = true;
let load = 0;
for (let j = 0; j < slotsPerMon; j++) {
if (this.slotRangeTypes[i + j] !== expectedType || this.slotSizes[i + j] !== slotsPerMon) {
valid = false;
break;
}
load += this.getSlotQueueLoad(i + j);
}
if (!valid) continue;
if (load < bestLoad) {
bestLoad = load;
bestIndex = i;
}
// 步进跨过这个怪的槽位,避免重叠判断
i += slotsPerMon - 1;
}
if (bestIndex >= 0) return bestIndex;
// 降级寻找任意能容纳大小的槽位组合
bestLoad = Number.MAX_SAFE_INTEGER;
for (let i = 0; i <= this.slotRangeTypes.length - slotsPerMon; i++) {
let load = 0;
for (let j = 0; j < slotsPerMon; j++) {
load += this.getSlotQueueLoad(i + j);
}
if (load < bestLoad) {
bestLoad = load;
bestIndex = i;
}
}
return Math.max(0, bestIndex);
}
private enqueueMonsterRequest(
uuid: number,
isBoss: boolean,
upType: UpType,
monLv: number = 1,
slotsPerMon: number = 1,
priority: boolean = false,
) {
const slotIndex = this.pickAssignSlotIndex(uuid, slotsPerMon);
const request = { uuid, isBoss, upType, monLv, slotsPerMon };
// 如果怪占用多个槽位,它应该存在于它占据的所有槽位的队列中(这样别的怪才会认为这里很挤)
// 但为了避免在 trySpawnFromSlotQueues 中被多次生成,我们只把实际的 request 放进它的起始槽位
// 其他被占用的槽位可以放一个占位符,或者通过其它方式处理
// 为了简便,我们只将它推入首个槽位,但排队检查的时候只要其中一个满了就算占用
if (priority) {
this.slotSpawnQueues[slotIndex].unshift(request);
return;
}
this.slotSpawnQueues[slotIndex].push(request);
}
private trySpawnFromSlotQueues() {
if (smc.mission.stop_spawn_mon) return;
for (let i = 0; i < this.slotSpawnQueues.length; i++) {
const queue = this.slotSpawnQueues[i];
if (queue.length === 0) continue;
const request = queue[0];
const slotsPerMon = request.slotsPerMon;
// 检查这个怪需要的所有的槽位是否都空闲
let canSpawn = true;
for (let j = 0; j < slotsPerMon; j++) {
if (i + j >= this.slotOccupiedEids.length || this.slotOccupiedEids[i + j]) {
canSpawn = false;
break;
}
}
if (!canSpawn) continue;
// 可以生成了,弹出请求
queue.shift();
this.addMonsterBySlot(i, request.uuid, request.isBoss, request.upType, request.monLv, slotsPerMon);
}
}
private addMonsterBySlot(
slotIndex: number,
uuid: number = 1001,
@@ -379,7 +293,7 @@ export class MissionMonCompComp extends CCComp {
// 将它占用的所有格子都标记为这个 eid
for (let j = 0; j < slotsPerMon; j++) {
if (slotIndex + j < this.slotOccupiedEids.length) {
if (slotIndex + j < MissionMonCompComp.MAX_SLOTS) {
this.slotOccupiedEids[slotIndex + j] = mon.eid;
}
}

View File

@@ -40,49 +40,45 @@ export interface IWaveSlot {
type: number; // 对应 MonType
count: number; // 占位数量
slotsPerMon?: number; // 每个怪占用几个位置,默认 1
monCount: number; // 这个占位排队的怪物总数(每个坑位要刷多少个怪)
}
// =========================================================================================
// 【每波怪物占位与刷怪配置说明】
// 1. 数组顺序:数组中的元素顺序即为战场上怪物从左到右占位的物理顺序。
// 2. 字段说明:
// 1. 字段说明:
// - type: 怪物类型 (参考 MonType如近战 0远程 1Boss 3 等)。
// - count: 该类型在场上同时存在几个并排的占位坑
// - monCount: 每个占位坑需要刷出的怪物总数(即每个坑排队的怪物数量)。当场上该坑位的怪死亡后,排队的下一只才会生成
// - slotsPerMon: (可选) 单个怪物体积占用几个占位坑,默认为 1。如果是大型 Boss 可设为 2 或更多,它会跨占位降落。
// - count: 该类型的怪在场上同时存在几个。
// - slotsPerMon: (可选) 单个怪物体积占用几个占位坑,默认为 1。如果是大型 Boss 可设为 2它会跨占位降落
//
// 举例
// { type: MonType.Melee, count: 2, monCount: 3 }
// 表示:在对应的位置开启 2 个近战占位坑,每个坑要排队刷出 3 只怪,总计该行配置会刷出 6 只近战怪
//
// 【注意】:波次怪物的总数将由所有坑位的 count * monCount 自动累加计算得出。
// 【注意】
// 全场固定 6 个槽位(索引 0-5
// Boss 固定占用 2 个位置,且只能出现在 1、3、5 号位(对应索引 0, 2, 4
// 每波怪物总槽位占用不能超过 6。不再支持排队刷怪。
// =========================================================================================
export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = {
1: [
{ type: MonType.Melee, count: 2, monCount: 3 },
{ type: MonType.Long, count: 2, monCount: 2 }
{ type: MonType.Melee, count: 3 },
{ type: MonType.Long, count: 3 }
],
2: [
{ type: MonType.Melee, count: 2, monCount: 4 },
{ type: MonType.Long, count: 2, monCount: 3 },
{ type: MonType.Support, count: 1, monCount: 2 }
{ type: MonType.Melee, count: 2 },
{ type: MonType.Long, count: 2 },
{ type: MonType.Support, count: 2 }
],
3: [
{ type: MonType.Melee, count: 1, monCount: 5 },
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2, monCount: 1 },
{ type: MonType.Long, count: 2, monCount: 4 }
{ type: MonType.Melee, count: 2 },
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2 },
{ type: MonType.Long, count: 2 }
],
4: [
{ type: MonType.Melee, count: 1, monCount: 5 },
{ type: MonType.Long, count: 1, monCount: 5 },
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2, monCount: 1 }
{ type: MonType.Melee, count: 2 },
{ type: MonType.Long, count: 2 },
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2 }
],
}
// 默认占位配置 (如果在 WaveSlotConfig 中找不到波次,则使用此配置)
export const DefaultWaveSlot: IWaveSlot[] = [
{ type: MonType.Melee, count: 2, monCount: 3 },
{ type: MonType.Long, count: 3, monCount: 3 }
{ type: MonType.Melee, count: 3 },
{ type: MonType.Long, count: 3 }
]