diff --git a/Assets/Prefabs/主角/召唤师主角.prefab b/Assets/Prefabs/主角/召唤师主角.prefab
index e9f70dc..13b34d8 100644
--- a/Assets/Prefabs/主角/召唤师主角.prefab
+++ b/Assets/Prefabs/主角/召唤师主角.prefab
@@ -43,17 +43,17 @@ PrefabInstance:
- target: {fileID: -8679921383154817045, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
type: 3}
propertyPath: m_LocalPosition.x
- value: -0.8
- objectReference: {fileID: 0}
- - target: {fileID: -8679921383154817045, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
- type: 3}
- propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
+ type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0.05
+ objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
type: 3}
propertyPath: m_LocalPosition.z
- value: 0.9
+ value: -20
objectReference: {fileID: 0}
- target: {fileID: -8679921383154817045, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
type: 3}
@@ -98,7 +98,7 @@ PrefabInstance:
- target: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
type: 3}
propertyPath: m_TagString
- value: Player
+ value: Friend
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
@@ -120,6 +120,22 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 6516315820976732023}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 6843810197477671806}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 6944470424331181836}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 6811651768908473498}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 3317273023278999503}
m_SourcePrefab: {fileID: 100100000, guid: 6b859b567d8ba4a7a87c03aa4b87c18c, type: 3}
--- !u!1 &106035464483675767 stripped
GameObject:
@@ -212,6 +228,76 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
+--- !u!114 &6843810197477671806
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 106035464483675767}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
+--- !u!114 &6944470424331181836
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 106035464483675767}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 096beceeef30a2646a85a4203af3cd27, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ minX: -9
+ maxX: 9
+ minZ: -19
+ maxZ: 6
+ moveSpeed: 5
+ stopDistance: 0.2
+ groundMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+--- !u!114 &6811651768908473498
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 106035464483675767}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
+--- !u!136 &3317273023278999503
+CapsuleCollider:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 106035464483675767}
+ m_Material: {fileID: 0}
+ m_IncludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_ExcludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_LayerOverridePriority: 0
+ m_IsTrigger: 0
+ m_ProvidesContacts: 0
+ m_Enabled: 1
+ serializedVersion: 2
+ m_Radius: 0.5
+ m_Height: 2
+ m_Direction: 1
+ m_Center: {x: 0, y: 1, z: 0}
--- !u!4 &1444656758206389486 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 1852576806548013000, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
diff --git a/Assets/Prefabs/第一章/人类士兵.prefab b/Assets/Prefabs/第一章/人类士兵.prefab
index 35daaca..98e6dfa 100644
--- a/Assets/Prefabs/第一章/人类士兵.prefab
+++ b/Assets/Prefabs/第一章/人类士兵.prefab
@@ -148,6 +148,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 2921261421214297749}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 5072af719b9b1401787c6c55f048d8ed,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 7706731248061608703}
m_SourcePrefab: {fileID: 100100000, guid: 5072af719b9b1401787c6c55f048d8ed, type: 3}
--- !u!1 &7945271050725079514 stripped
GameObject:
@@ -211,6 +215,7 @@ MonoBehaviour:
separationRadius: 1.5
separationWeight: 3
targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!114 &2920089167710187078
MonoBehaviour:
@@ -227,7 +232,7 @@ MonoBehaviour:
maxHealth: 220
currentHealth: 220
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.2
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
@@ -238,7 +243,6 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
- damageNumberOutlineWidth: 0.015
--- !u!114 &4111165692813349771
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -290,6 +294,19 @@ NavMeshAgent:
m_BaseOffset: 0
m_WalkableMask: 4294967295
m_ObstacleAvoidanceType: 4
+--- !u!114 &7706731248061608703
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 7945271050725079514}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
--- !u!4 &8878706989061864259 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 1852576806548013000, guid: 5072af719b9b1401787c6c55f048d8ed,
diff --git a/Assets/Prefabs/第一章/弓箭手.prefab b/Assets/Prefabs/第一章/弓箭手.prefab
index 7b4391a..c56ae9f 100644
--- a/Assets/Prefabs/第一章/弓箭手.prefab
+++ b/Assets/Prefabs/第一章/弓箭手.prefab
@@ -143,6 +143,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 196735238731846736}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 7b0dd5b38a9cd144ea135f3743440958,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 7984530604249028412}
m_SourcePrefab: {fileID: 100100000, guid: 7b0dd5b38a9cd144ea135f3743440958, type: 3}
--- !u!4 &2222026796505693691 stripped
Transform:
@@ -229,6 +233,7 @@ MonoBehaviour:
separationRadius: 1.5
separationWeight: 3
targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!114 &7720102017902192686
MonoBehaviour:
@@ -245,7 +250,7 @@ MonoBehaviour:
maxHealth: 150
currentHealth: 150
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.2
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
@@ -256,7 +261,6 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
- damageNumberOutlineWidth: 0.015
--- !u!114 &1449822714228276666
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -294,6 +298,19 @@ NavMeshAgent:
m_BaseOffset: 0
m_WalkableMask: 4294967295
m_ObstacleAvoidanceType: 4
+--- !u!114 &7984530604249028412
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 5200308054099318121}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
--- !u!1001 &6707986314085581781
PrefabInstance:
m_ObjectHideFlags: 0
diff --git a/Assets/Prefabs/第一章/法师.prefab b/Assets/Prefabs/第一章/法师.prefab
index f37ec83..085d7fd 100644
--- a/Assets/Prefabs/第一章/法师.prefab
+++ b/Assets/Prefabs/第一章/法师.prefab
@@ -144,6 +144,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 20042258299677061}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 9d4b54ec2c4f7754bb1fd5ec11a29541,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 7388673629374319643}
m_SourcePrefab: {fileID: 100100000, guid: 9d4b54ec2c4f7754bb1fd5ec11a29541, type: 3}
--- !u!1 &7062617501994129594 stripped
GameObject:
@@ -223,6 +227,7 @@ MonoBehaviour:
separationRadius: 1.5
separationWeight: 3
targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!114 &990449840913200210
MonoBehaviour:
@@ -239,7 +244,7 @@ MonoBehaviour:
maxHealth: 135
currentHealth: 135
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.2
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
@@ -250,7 +255,6 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
- damageNumberOutlineWidth: 0.015
--- !u!114 &3887798621029042906
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -287,6 +291,19 @@ NavMeshAgent:
m_BaseOffset: 0
m_WalkableMask: 4294967295
m_ObstacleAvoidanceType: 4
+--- !u!114 &7388673629374319643
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 7062617501994129594}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
--- !u!4 &8608542904823568931 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 1852576806548013000, guid: 9d4b54ec2c4f7754bb1fd5ec11a29541,
diff --git a/Assets/Prefabs/第一章/盾卫.prefab b/Assets/Prefabs/第一章/盾卫.prefab
index 0404ad1..74351d8 100644
--- a/Assets/Prefabs/第一章/盾卫.prefab
+++ b/Assets/Prefabs/第一章/盾卫.prefab
@@ -199,6 +199,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 4069710806908923055}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 17f7a1b7ae92f0948a5bebde8a11fc2b,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 4719436641987099519}
m_SourcePrefab: {fileID: 100100000, guid: 17f7a1b7ae92f0948a5bebde8a11fc2b, type: 3}
--- !u!4 &309400887928525066 stripped
Transform:
@@ -283,6 +287,7 @@ MonoBehaviour:
separationRadius: 1.5
separationWeight: 3
targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!114 &6884400399417614731
MonoBehaviour:
@@ -299,7 +304,7 @@ MonoBehaviour:
maxHealth: 480
currentHealth: 480
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.35
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
@@ -310,7 +315,6 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
- damageNumberOutlineWidth: 0.015
--- !u!114 &8203338886952913679
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -350,6 +354,19 @@ NavMeshAgent:
m_BaseOffset: 0
m_WalkableMask: 4294967295
m_ObstacleAvoidanceType: 4
+--- !u!114 &4719436641987099519
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1242979668690031507}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
--- !u!4 &5170975053921319681 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 6502182839619065283, guid: 17f7a1b7ae92f0948a5bebde8a11fc2b,
diff --git a/Assets/Prefabs/第一章/精锐士兵.prefab b/Assets/Prefabs/第一章/精锐士兵.prefab
index ebab685..9dbdd45 100644
--- a/Assets/Prefabs/第一章/精锐士兵.prefab
+++ b/Assets/Prefabs/第一章/精锐士兵.prefab
@@ -185,6 +185,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 8773165921290396666}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 62d9f853c067ff443bd2a3aa382ab563,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 897006334484502727}
m_SourcePrefab: {fileID: 100100000, guid: 62d9f853c067ff443bd2a3aa382ab563, type: 3}
--- !u!4 &2585761489654059889 stripped
Transform:
@@ -270,6 +274,7 @@ MonoBehaviour:
separationRadius: 1.5
separationWeight: 3
targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!114 &3773753810590757696
MonoBehaviour:
@@ -286,7 +291,7 @@ MonoBehaviour:
maxHealth: 650
currentHealth: 650
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.2
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
@@ -297,7 +302,6 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
- damageNumberOutlineWidth: 0.015
--- !u!114 &4116279677098275843
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -335,3 +339,16 @@ NavMeshAgent:
m_BaseOffset: 0
m_WalkableMask: 4294967295
m_ObstacleAvoidanceType: 4
+--- !u!114 &897006334484502727
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 3933539425749946856}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
diff --git a/Assets/Prefabs/第二章/弓箭猎人.prefab b/Assets/Prefabs/第二章/弓箭猎人.prefab
index 6747dc2..98a8834 100644
--- a/Assets/Prefabs/第二章/弓箭猎人.prefab
+++ b/Assets/Prefabs/第二章/弓箭猎人.prefab
@@ -251,6 +251,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 7158251723525050232}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 2ac38f68ec4f85341b423b5e8defd47e,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 6065625683664793215}
m_SourcePrefab: {fileID: 100100000, guid: 2ac38f68ec4f85341b423b5e8defd47e, type: 3}
--- !u!1 &4059575966999087028 stripped
GameObject:
@@ -299,7 +303,7 @@ MonoBehaviour:
maxHealth: 100
currentHealth: 100
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.2
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
@@ -310,7 +314,6 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
- damageNumberOutlineWidth: 0.015
--- !u!114 &6486457417548983634
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -342,6 +345,7 @@ MonoBehaviour:
separationRadius: 1.5
separationWeight: 3
targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!114 &4858463350953901710
MonoBehaviour:
@@ -394,6 +398,19 @@ NavMeshAgent:
m_BaseOffset: 0
m_WalkableMask: 4294967295
m_ObstacleAvoidanceType: 4
+--- !u!114 &6065625683664793215
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 4059575966999087028}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
--- !u!4 &7974625701785382694 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 6502182839619065283, guid: 2ac38f68ec4f85341b423b5e8defd47e,
diff --git a/Assets/Prefabs/第二章/狼人.prefab b/Assets/Prefabs/第二章/狼人.prefab
index 6a1b1ec..cc08d51 100644
--- a/Assets/Prefabs/第二章/狼人.prefab
+++ b/Assets/Prefabs/第二章/狼人.prefab
@@ -103,6 +103,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 8144035540121443354}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 3d031ad534e512e45bb7fe10237e6286,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 8968669349745731875}
m_SourcePrefab: {fileID: 100100000, guid: 3d031ad534e512e45bb7fe10237e6286, type: 3}
--- !u!1 &6708744566382593167 stripped
GameObject:
@@ -163,6 +167,10 @@ MonoBehaviour:
projectileSpeed: 20
projectileLifetime: 5
energyPerAttack: 20
+ separationRadius: 1.5
+ separationWeight: 3
+ targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!114 &6730575396005638825
MonoBehaviour:
@@ -179,12 +187,17 @@ MonoBehaviour:
maxHealth: 360
currentHealth: 360
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.2
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
deathEffectDuration: 1.1
deathEffectColor: {r: 0.55, g: 0.9, b: 1, a: 1}
+ showDamageNumbers: 1
+ damageNumberDuration: 0.8
+ damageNumberRiseHeight: 1.2
+ damageNumberSpawnHeight: 1.5
+ damageNumberSize: 0.09
--- !u!114 &2583987946621363416
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -214,3 +227,16 @@ MonoBehaviour:
showStunIndicator: 1
indicatorHeight: 0.35
stunIndicatorPrefab: {fileID: 0}
+--- !u!114 &8968669349745731875
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 6708744566382593167}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
diff --git a/Assets/Prefabs/第二章/石巨人.prefab b/Assets/Prefabs/第二章/石巨人.prefab
index 325f3cf..40543dd 100644
--- a/Assets/Prefabs/第二章/石巨人.prefab
+++ b/Assets/Prefabs/第二章/石巨人.prefab
@@ -113,6 +113,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 8367410358801711549}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: d40daebbc0e47416099e97edbbbeac04,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 507015780925844398}
m_SourcePrefab: {fileID: 100100000, guid: d40daebbc0e47416099e97edbbbeac04, type: 3}
--- !u!1 &4471054466352856454 stripped
GameObject:
@@ -147,6 +151,10 @@ MonoBehaviour:
projectileSpeed: 20
projectileLifetime: 5
energyPerAttack: 20
+ separationRadius: 1.5
+ separationWeight: 3
+ targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 0}
--- !u!143 &7319997191678055287
CharacterController:
@@ -189,12 +197,17 @@ MonoBehaviour:
maxHealth: 820
currentHealth: 820
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.5
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
deathEffectDuration: 1.1
deathEffectColor: {r: 0.55, g: 0.9, b: 1, a: 1}
+ showDamageNumbers: 1
+ damageNumberDuration: 0.8
+ damageNumberRiseHeight: 1.2
+ damageNumberSpawnHeight: 1.5
+ damageNumberSize: 0.09
--- !u!114 &1445493180626066942
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -227,3 +240,16 @@ MonoBehaviour:
skillRadius: 5
stunDuration: 1
skillAnimationDuration: 0
+--- !u!114 &507015780925844398
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 4471054466352856454}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
diff --git a/Assets/Prefabs/第二章/蓝色怪物.prefab b/Assets/Prefabs/第二章/蓝色怪物.prefab
index 5005f6f..6c46621 100644
--- a/Assets/Prefabs/第二章/蓝色怪物.prefab
+++ b/Assets/Prefabs/第二章/蓝色怪物.prefab
@@ -128,6 +128,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 1038858676101729388}
+ - targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: aedf6e849dd924e229c57640f99ff50a,
+ type: 3}
+ insertIndex: -1
+ addedObject: {fileID: 761483117139778608}
m_SourcePrefab: {fileID: 100100000, guid: aedf6e849dd924e229c57640f99ff50a, type: 3}
--- !u!1 &1823520504047441011 stripped
GameObject:
@@ -188,6 +192,10 @@ MonoBehaviour:
projectileSpeed: 20
projectileLifetime: 5
energyPerAttack: 20
+ separationRadius: 1.5
+ separationWeight: 3
+ targetSpreadBias: 6
+ summonerMode: 0
target: {fileID: 1823520504047441011}
--- !u!114 &2631642551191811442
MonoBehaviour:
@@ -204,12 +212,17 @@ MonoBehaviour:
maxHealth: 400
currentHealth: 400
maxEnergy: 100
- currentEnergy: 0
+ _currentEnergy: 0
heightOffset: 0.25
barWidth: 1.2
borderColor: {r: 0.03, g: 0.03, b: 0.03, a: 0.95}
deathEffectDuration: 1.1
deathEffectColor: {r: 0.1, g: 0.55, b: 1, a: 1}
+ showDamageNumbers: 1
+ damageNumberDuration: 0.8
+ damageNumberRiseHeight: 1.2
+ damageNumberSpawnHeight: 1.5
+ damageNumberSize: 0.09
--- !u!114 &8856053484402202215
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -240,3 +253,16 @@ MonoBehaviour:
indicatorHeight: 0.35
stunIndicatorPrefab: {fileID: 2432573200162841629, guid: de288ea16be0c1544a4c6eb71e899570,
type: 3}
+--- !u!114 &761483117139778608
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1823520504047441011}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ teamId: 0
diff --git a/Assets/Scenes/Chapter1/SummonerDuelTest.unity b/Assets/Scenes/Chapter1/SummonerDuelTest.unity
index 7be0d99..d7557cd 100644
Binary files a/Assets/Scenes/Chapter1/SummonerDuelTest.unity and b/Assets/Scenes/Chapter1/SummonerDuelTest.unity differ
diff --git a/Assets/Script/AISummoner.cs b/Assets/Script/AISummoner.cs
new file mode 100644
index 0000000..bffe8c0
--- /dev/null
+++ b/Assets/Script/AISummoner.cs
@@ -0,0 +1,141 @@
+using UnityEngine;
+
+///
+/// AI 召唤师控制器:自动召唤单位攻击玩家
+///
+public class AISummoner : MonoBehaviour
+{
+ [Header("AI 配置")]
+ [Tooltip("召唤间隔(秒)")]
+ [Min(1f)]
+ public float summonInterval = 3f;
+
+ [Tooltip("随机偏移范围(秒),避免太规律")]
+ [Min(0f)]
+ public float summonRandomOffset = 1f;
+
+ [Tooltip("AI 难度:召唤CD缩放(越小越快)")]
+ [Range(0.5f, 2f)]
+ public float difficultyScale = 1f;
+
+ [Header("能量管理")]
+ [Tooltip("AI 初始能量")]
+ [Min(0)]
+ public int startingMana = 150;
+
+ [Tooltip("AI 是否使用无限能量")]
+ public bool unlimitedMana = false;
+
+ private MainCharacterCombat combat;
+ private ManaSystem manaSystem;
+ private float summonCooldown;
+ private bool isActive;
+
+ private void Start()
+ {
+ combat = GetComponent();
+ if (combat == null)
+ {
+ Debug.LogError($"[AISummoner] {name} 缺少 MainCharacterCombat 组件!");
+ enabled = false;
+ return;
+ }
+
+ manaSystem = FindObjectOfType();
+ if (manaSystem == null)
+ {
+ Debug.LogWarning($"[AISummoner] 场景中没有 ManaSystem,AI 将无法检查能量");
+ }
+
+ // 初始延迟,避免开局立刻召唤
+ summonCooldown = summonInterval * difficultyScale;
+ isActive = true;
+
+ Debug.Log($"[AISummoner] {name} AI 初始化完成,召唤间隔: {summonInterval}s");
+ }
+
+ private void Update()
+ {
+ if (!isActive) return;
+
+ // AI 被眩晕时停止召唤
+ if (combat.IsStunned) return;
+
+ summonCooldown -= Time.deltaTime;
+ if (summonCooldown <= 0f)
+ {
+ TrySummon();
+ ResetCooldown();
+ }
+ }
+
+ private void TrySummon()
+ {
+ // 随机选择一个可召唤的角色槽位(1-5)
+ var characters = combat.summonableCharacters;
+ if (characters == null || characters.Length == 0)
+ {
+ Debug.LogWarning($"[AISummoner] {name} 没有可召唤的角色");
+ return;
+ }
+
+ // 过滤出有效的角色数据
+ int[] validSlots = new int[characters.Length];
+ int validCount = 0;
+
+ for (int i = 0; i < characters.Length; i++)
+ {
+ if (characters[i] != null && characters[i].unitPrefab != null)
+ {
+ // 检查能量
+ if (unlimitedMana || manaSystem == null || manaSystem.CurrentManaInt >= characters[i].manaCost)
+ {
+ validSlots[validCount++] = i + 1; // slot 是 1-based
+ }
+ }
+ }
+
+ if (validCount == 0)
+ {
+ // 没有可召唤的角色(能量不足或没有有效角色)
+ return;
+ }
+
+ // 随机选择一个有效槽位
+ int randomIndex = Random.Range(0, validCount);
+ int selectedSlot = validSlots[randomIndex];
+
+ // 执行召唤(使用 MainCharacterCombat.RequestSummon)
+ bool success = combat.RequestSummon(selectedSlot);
+
+ if (success)
+ {
+ var selectedCharacter = characters[selectedSlot - 1];
+ Debug.Log($"[AISummoner] {name} 召唤了 {selectedCharacter.characterName} (槽位: {selectedSlot})");
+ }
+ }
+
+ private void ResetCooldown()
+ {
+ float randomOffset = Random.Range(-summonRandomOffset, summonRandomOffset);
+ summonCooldown = summonInterval * difficultyScale + randomOffset;
+ }
+
+ ///
+ /// 启用/禁用 AI
+ ///
+ public void SetActive(bool active)
+ {
+ isActive = active;
+ Debug.Log($"[AISummoner] {name} AI {(active ? "启用" : "禁用")}");
+ }
+
+ ///
+ /// 调整难度(实时修改召唤速度)
+ ///
+ public void SetDifficulty(float scale)
+ {
+ difficultyScale = Mathf.Clamp(scale, 0.5f, 2f);
+ Debug.Log($"[AISummoner] {name} 难度调整为 {difficultyScale}");
+ }
+}
diff --git a/Assets/Script/AISummoner.cs.meta b/Assets/Script/AISummoner.cs.meta
new file mode 100644
index 0000000..930477f
--- /dev/null
+++ b/Assets/Script/AISummoner.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: CCke4S2lUy3nU786T7yvHZYNAkerlhq9/n+qIWEgtRjk/6EPe8RNOPo=
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Script/Barracks.cs b/Assets/Script/Barracks.cs
index 2985935..87b4e73 100644
--- a/Assets/Script/Barracks.cs
+++ b/Assets/Script/Barracks.cs
@@ -88,6 +88,19 @@ public class Barracks : MonoBehaviour
unit.name = characterData.characterName;
unit.tag = unitTag;
+ // 设置阵营 ID(根据建筑的阵营)
+ var buildingTeam = GetComponent();
+ var unitTeam = unit.GetComponent();
+ if (buildingTeam != null && unitTeam != null)
+ {
+ unitTeam.teamId = buildingTeam.teamId;
+ }
+ else if (unitTeam != null)
+ {
+ // 兜底:根据 tag 判断阵营
+ unitTeam.teamId = unitTag == "Friend" ? 0 : 1;
+ }
+
// 如果单位有UnitBase组件,使用1级属性初始化
var unitBase = unit.GetComponent();
if (unitBase != null)
diff --git a/Assets/Script/BattleSetupManager.cs b/Assets/Script/BattleSetupManager.cs
index 4c03e1f..8aeb4d1 100644
--- a/Assets/Script/BattleSetupManager.cs
+++ b/Assets/Script/BattleSetupManager.cs
@@ -235,6 +235,13 @@ public class BattleSetupManager : MonoBehaviour
unit.name = "Friend_" + data.characterName;
unit.tag = "Friend";
+ // 设置阵营 ID(玩家单位为 team 0)
+ var unitTeam = unit.GetComponent();
+ if (unitTeam != null)
+ {
+ unitTeam.teamId = 0;
+ }
+
var unitBase = unit.GetComponent();
if (unitBase != null)
{
diff --git a/Assets/Script/GameManager.cs b/Assets/Script/GameManager.cs
new file mode 100644
index 0000000..bf1d923
--- /dev/null
+++ b/Assets/Script/GameManager.cs
@@ -0,0 +1,37 @@
+using UnityEngine;
+
+///
+/// 游戏管理器:管理全局游戏状态,包括本地玩家的阵营 ID。
+/// PVE:LocalPlayerTeamId = 0(固定)
+/// PVP:服务器分配后设置 LocalPlayerTeamId(比如 0 或 1)
+///
+public class GameManager : MonoBehaviour
+{
+ public static GameManager Instance { get; private set; }
+
+ [Header("本地玩家阵营")]
+ [Tooltip("PVE: 固定为0; PVP: 服务器分配")]
+ public int LocalPlayerTeamId = 0;
+
+ private void Awake()
+ {
+ // 单例模式
+ if (Instance != null && Instance != this)
+ {
+ Destroy(gameObject);
+ return;
+ }
+
+ Instance = this;
+ DontDestroyOnLoad(gameObject);
+ }
+
+ ///
+ /// 设置本地玩家阵营 ID(PVP 时由服务器调用)
+ ///
+ public void SetLocalPlayerTeam(int teamId)
+ {
+ LocalPlayerTeamId = teamId;
+ Debug.Log($"本地玩家阵营设置为: {teamId}");
+ }
+}
diff --git a/Assets/Script/GameManager.cs.meta b/Assets/Script/GameManager.cs.meta
new file mode 100644
index 0000000..e1d8b63
--- /dev/null
+++ b/Assets/Script/GameManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: CXkdt3ulUS3TPoI5TieIx0QPMa6EXoIKUewqD1fNwBgwo43HfDPsYzY=
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Script/MainCharacterCombat.cs b/Assets/Script/MainCharacterCombat.cs
index 70d24e9..c774501 100644
--- a/Assets/Script/MainCharacterCombat.cs
+++ b/Assets/Script/MainCharacterCombat.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
+[RequireComponent(typeof(StatusEffectController))]
public class MainCharacterCombat : MonoBehaviour
{
[Header("自动攻击")]
@@ -42,6 +43,7 @@ public class MainCharacterCombat : MonoBehaviour
[Min(0f)]
public float projectileLifetime = 5f;
+ private StatusEffectController statusEffectController;
private Animator animator;
private MainCharacterDebugMovement movement;
private GameObject target;
@@ -56,10 +58,18 @@ public class MainCharacterCombat : MonoBehaviour
private bool summonSpawned;
private bool summoning;
+ public bool IsStunned => statusEffectController != null && statusEffectController.IsStunned;
+
private void Start()
{
animator = GetComponent();
movement = GetComponent();
+ statusEffectController = GetComponent();
+
+ if (statusEffectController == null)
+ {
+ statusEffectController = gameObject.AddComponent();
+ }
if (animator != null)
{
@@ -69,6 +79,16 @@ public class MainCharacterCombat : MonoBehaviour
private void Update()
{
+ // 眩晕时停止所有行动
+ if (IsStunned)
+ {
+ if (animator != null)
+ {
+ animator.SetInteger("speed", 0);
+ }
+ return;
+ }
+
if (attackCooldown > 0f)
{
attackCooldown -= Time.deltaTime;
@@ -230,11 +250,12 @@ public class MainCharacterCombat : MonoBehaviour
return;
}
- // 立即停止移动
+ // 立即停止移动,并禁用移动脚本(防止干扰动画)
SummonerClickMovement clickMovement = GetComponent();
if (clickMovement != null)
{
clickMovement.ClearTarget();
+ clickMovement.enabled = false; // 禁用移动脚本,避免它设置 speed 参数
}
pendingSummonPrefab = prefab;
@@ -244,9 +265,17 @@ public class MainCharacterCombat : MonoBehaviour
summonSpawned = false;
summoning = true;
animator.speed = 1f;
+
+ // 强制重置动画状态,避免状态过渡冲突
animator.SetInteger("speed", 0);
animator.ResetTrigger("skill");
+ animator.ResetTrigger("attack");
+
+ // 直接强制播放 skill 动画,绕过状态机的所有过渡条件
+ // Play() 会立即切换,不管当前状态是什么
animator.Play("skill", 0, 0f);
+
+ Debug.Log("强制播放 skill 动画");
}
public void OnSkillHit1()
@@ -272,7 +301,22 @@ public class MainCharacterCombat : MonoBehaviour
pendingSummonPrefab,
spawnPosition,
Quaternion.LookRotation(direction, Vector3.up));
- summoned.tag = "Friend";
+
+ // 召唤的单位继承召唤师的阵营(不再硬编码为 Friend)
+ UnitTeam summonerTeam = GetComponent();
+ UnitTeam summonedTeam = summoned.GetComponent();
+
+ if (summonerTeam != null && summonedTeam != null)
+ {
+ summonedTeam.teamId = summonerTeam.teamId;
+ // 根据阵营设置 tag(向后兼容旧系统)
+ summoned.tag = summonerTeam.teamId == 0 ? "Friend" : "Enemy";
+ }
+ else
+ {
+ // 降级:没有 UnitTeam 时保持原逻辑
+ summoned.tag = "Friend";
+ }
UnitBase unitBase = summoned.GetComponent();
if (unitBase != null && pendingSummonCharacterData != null && pendingSummonPlayerData != null)
@@ -404,16 +448,25 @@ public class MainCharacterCombat : MonoBehaviour
private void CheckSummonFinished()
{
AnimatorStateInfo state = animator.GetCurrentAnimatorStateInfo(0);
+
+ // 正常完成:skill 动画播放完毕
if (state.IsName("skill") && state.normalizedTime >= 1f)
{
FinishSummon();
return;
}
- // skill 状态意外被其他动画打断时,才使用超时兜底,避免永久锁定召唤。
+ // 超时检测:添加调试信息
if (summonElapsed >= summonAnimationTimeout)
{
- Debug.LogWarning("主角召唤动画超时,已解除召唤状态");
+ // 输出当前动画状态,帮助诊断问题
+ string currentState = "unknown";
+ if (state.IsName("skill")) currentState = "skill";
+ else if (state.IsName("idle")) currentState = "idle";
+ else if (state.IsName("run")) currentState = "run";
+ else if (state.IsName("attack")) currentState = "attack";
+
+ Debug.LogWarning($"主角召唤动画超时,已解除召唤状态。当前动画状态: {currentState}, normalizedTime: {state.normalizedTime:F2}");
FinishSummon();
}
}
@@ -427,6 +480,15 @@ public class MainCharacterCombat : MonoBehaviour
animator.speed = 1f;
animator.SetInteger("speed", 0);
animator.Play("idle", 0, 0f);
+
+ // 重新启用移动脚本
+ SummonerClickMovement clickMovement = GetComponent();
+ if (clickMovement != null)
+ {
+ clickMovement.enabled = true;
+ }
+
+ Debug.Log("召唤完成,恢复移动脚本");
}
private void CheckAttackFinished()
@@ -535,18 +597,38 @@ public class MainCharacterCombat : MonoBehaviour
private GameObject FindNearestEnemy()
{
// 索敌范围使用较小的 detectRange,主角不主动远离基地寻找敌人
+ UnitTeam myTeam = GetComponent();
+ if (myTeam == null)
+ {
+ return null;
+ }
+
GameObject nearest = null;
float nearestDistance = detectRange * detectRange;
- foreach (GameObject enemy in GameObject.FindGameObjectsWithTag("Enemy"))
+ // 查找所有带 UnitTeam 的对象(包括单位和召唤师)
+ UnitTeam[] allTeams = FindObjectsOfType();
+
+ foreach (UnitTeam targetTeam in allTeams)
{
- if (enemy == null || enemy.transform.root == transform.root)
+ GameObject enemy = targetTeam.gameObject;
+
+ if (enemy == null || enemy == gameObject || enemy.transform.root == transform.root)
{
continue;
}
+ // 检查是否是敌人(使用阵营 ID)
+ if (!myTeam.IsEnemy(enemy))
+ {
+ continue;
+ }
+
+ // 检查是否有 UnitBase 或 WorldHealthBar(召唤师)
UnitBase enemyUnit = enemy.GetComponentInParent();
- if (enemyUnit == null)
+ WorldHealthBar enemyHealth = enemy.GetComponentInParent();
+
+ if (enemyUnit == null && enemyHealth == null)
{
continue;
}
diff --git a/Assets/Script/Projectile.cs b/Assets/Script/Projectile.cs
index 4707095..c602b38 100644
--- a/Assets/Script/Projectile.cs
+++ b/Assets/Script/Projectile.cs
@@ -116,20 +116,29 @@ public class Projectile : MonoBehaviour
UnitBase targetUnit = hitObject.GetComponentInParent();
BuildingHealth targetBuilding = hitObject.GetComponentInParent();
- if (targetUnit == null && targetBuilding == null)
+ WorldHealthBar targetHealthBar = hitObject.GetComponentInParent();
+
+ if (targetUnit == null && targetBuilding == null && targetHealthBar == null)
{
return;
}
hasHit = true;
+
if (targetUnit != null)
{
targetUnit.ReceiveDamage(damage, owner);
}
- else
+ else if (targetBuilding != null)
{
targetBuilding.ReceiveDamage(damage, owner);
}
+ else if (targetHealthBar != null)
+ {
+ // 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase)
+ targetHealthBar.TakeDamage(damage, owner);
+ }
+
if (registerEnergyOnHit && owner != null)
{
UnitBase unit = owner.GetComponent();
diff --git a/Assets/Script/UnitBase.cs b/Assets/Script/UnitBase.cs
index 32227d5..5985f5c 100644
--- a/Assets/Script/UnitBase.cs
+++ b/Assets/Script/UnitBase.cs
@@ -342,48 +342,50 @@ public class UnitBase : MonoBehaviour
//根据自身Tag找到敌对Tag
private string GetOpponentTag()
{
- if (CompareTag("Friend"))
- {
- return "Enemy";
- }
-
- if (CompareTag("Enemy"))
- {
- return "Friend";
- }
-
+ // 已弃用:改用阵营 ID 系统,不再依赖 Tag 判断敌我
+ // 保留此方法是为了兼容旧代码,但不应再被调用
+ Debug.LogWarning("GetOpponentTag() 已弃用,请使用阵营 ID 系统");
return null;
}
- //查找距离自己最近的敌对目标
+ //查找距离自己最近的敌对目标(使用阵营 ID 判断)
private GameObject FindNearestOpponent()
{
- string opponentTag = GetOpponentTag();
- if (string.IsNullOrEmpty(opponentTag))
+ var myTeam = GetComponent();
+ if (myTeam == null)
{
+ Debug.LogWarning($"{gameObject.name} 缺少 UnitTeam 组件,无法查找敌人");
return null;
}
GameObject nearestOpponent = null;
float nearestSqrDistance = float.PositiveInfinity;
- GameObject[] opponents = GameObject.FindGameObjectsWithTag(opponentTag);
- foreach (var opponent in opponents)
+ // 查找所有 UnitTeam 组件(不再依赖 Tag)
+ UnitTeam[] allUnits = FindObjectsOfType();
+
+ foreach (var unit in allUnits)
{
- //排除自己以及自己所在层级中的对象
- if (opponent == gameObject || opponent.transform.root == transform.root)
+ // 跳过自己和友军
+ if (unit.gameObject == gameObject || !myTeam.IsEnemy(unit.gameObject))
{
continue;
}
- float sqrDistance = (opponent.transform.position - transform.position).sqrMagnitude;
+ //排除自己所在层级中的对象
+ if (unit.transform.root == transform.root)
+ {
+ continue;
+ }
+
+ float sqrDistance = (unit.transform.position - transform.position).sqrMagnitude;
//加入确定性分散偏移:让不同单位偏好不同目标,避免全部涌向同一个敌人
- float bias = ((uint)(gameObject.GetInstanceID() * 7919) ^ (uint)(opponent.GetInstanceID())) % 10u / 10f * targetSpreadBias;
+ float bias = ((uint)(gameObject.GetInstanceID() * 7919) ^ (uint)(unit.gameObject.GetInstanceID())) % 10u / 10f * targetSpreadBias;
float adjustedSqrDistance = sqrDistance + bias;
if (adjustedSqrDistance < nearestSqrDistance)
{
nearestSqrDistance = adjustedSqrDistance;
- nearestOpponent = opponent;
+ nearestOpponent = unit.gameObject;
}
}
@@ -651,6 +653,15 @@ public class UnitBase : MonoBehaviour
if (targetBuilding != null && targetBuilding.ReceiveDamage(attackPower, gameObject))
{
RegisterAttackHit();
+ return;
+ }
+
+ // 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase)
+ WorldHealthBar targetHealthBar = opponent.GetComponentInParent();
+ if (targetHealthBar != null)
+ {
+ targetHealthBar.TakeDamage(attackPower, gameObject);
+ RegisterAttackHit();
}
}
@@ -689,23 +700,33 @@ public class UnitBase : MonoBehaviour
return;
}
- string opponentTag = GetOpponentTag();
- if (string.IsNullOrEmpty(opponentTag))
+ UnitTeam myTeam = GetComponent();
+ if (myTeam == null)
{
return;
}
float clampedRadius = Mathf.Max(0f, radius);
float radiusSquared = clampedRadius * clampedRadius;
- GameObject[] opponents = GameObject.FindGameObjectsWithTag(opponentTag);
+
+ // 查找所有带 UnitTeam 的对象(包括单位和召唤师)
+ UnitTeam[] allTeams = FindObjectsOfType();
HashSet hitHealthBars = new HashSet();
- foreach (GameObject opponent in opponents)
+ foreach (UnitTeam targetTeam in allTeams)
{
+ GameObject opponent = targetTeam.gameObject;
+
if (opponent == null || opponent == gameObject || opponent.transform.root == transform.root)
{
continue;
}
+
+ // 检查是否是敌人
+ if (!myTeam.IsEnemy(opponent))
+ {
+ continue;
+ }
Vector3 offset = opponent.transform.position - transform.position;
offset.y = 0f;
@@ -716,9 +737,21 @@ public class UnitBase : MonoBehaviour
UnitBase targetUnit = opponent.GetComponentInParent();
BuildingHealth targetBuilding = opponent.GetComponentInParent();
- WorldHealthBar healthBar = targetUnit != null
- ? targetUnit.GetComponent()
- : targetBuilding != null ? targetBuilding.GetComponent() : null;
+ WorldHealthBar healthBar = null;
+
+ if (targetUnit != null)
+ {
+ healthBar = targetUnit.GetComponent();
+ }
+ else if (targetBuilding != null)
+ {
+ healthBar = targetBuilding.GetComponent();
+ }
+ else
+ {
+ // 召唤师等特殊单位(有 WorldHealthBar 但没有 UnitBase)
+ healthBar = opponent.GetComponentInParent();
+ }
if (healthBar == null || !hitHealthBars.Add(healthBar))
{
@@ -729,10 +762,15 @@ public class UnitBase : MonoBehaviour
{
targetUnit.ReceiveDamage(damage, gameObject);
}
- else
+ else if (targetBuilding != null)
{
targetBuilding.ReceiveDamage(damage, gameObject);
}
+ else if (healthBar != null)
+ {
+ // 召唤师等特殊单位
+ healthBar.TakeDamage(damage, gameObject);
+ }
}
if (registerEnergy)
@@ -748,23 +786,33 @@ public class UnitBase : MonoBehaviour
return;
}
- string opponentTag = GetOpponentTag();
- if (string.IsNullOrEmpty(opponentTag))
+ UnitTeam myTeam = GetComponent();
+ if (myTeam == null)
{
return;
}
float clampedRadius = Mathf.Max(0f, radius);
float radiusSquared = clampedRadius * clampedRadius;
- GameObject[] opponents = GameObject.FindGameObjectsWithTag(opponentTag);
+
+ // 查找所有带 UnitTeam 的对象(包括单位和召唤师)
+ UnitTeam[] allTeams = FindObjectsOfType();
HashSet hitUnits = new HashSet();
- foreach (GameObject opponent in opponents)
+ foreach (UnitTeam targetTeam in allTeams)
{
+ GameObject opponent = targetTeam.gameObject;
+
if (opponent == null || opponent == gameObject || opponent.transform.root == transform.root)
{
continue;
}
+
+ // 检查是否是敌人
+ if (!myTeam.IsEnemy(opponent))
+ {
+ continue;
+ }
Vector3 offset = opponent.transform.position - transform.position;
offset.y = 0f;
@@ -774,12 +822,18 @@ public class UnitBase : MonoBehaviour
}
UnitBase unit = opponent.GetComponentInParent();
- if (unit == null || !hitUnits.Add(unit))
+ if (unit != null && hitUnits.Add(unit))
{
+ unit.ApplyStun(duration);
continue;
}
-
- unit.ApplyStun(duration);
+
+ // 召唤师等特殊单位(有 StatusEffectController 但没有 UnitBase)
+ StatusEffectController statusEffect = opponent.GetComponentInParent();
+ if (statusEffect != null)
+ {
+ statusEffect.ApplyStun(duration);
+ }
}
}
diff --git a/Assets/Script/UnitTeam.cs b/Assets/Script/UnitTeam.cs
new file mode 100644
index 0000000..2b07315
--- /dev/null
+++ b/Assets/Script/UnitTeam.cs
@@ -0,0 +1,47 @@
+using UnityEngine;
+
+///
+/// 单位阵营标记:用阵营 ID 替代 Unity Tag 判断敌我关系。
+/// 支持 PVP:客户端只需设置 GameManager.LocalPlayerTeamId,所有单位的敌我判断自动正确。
+///
+public class UnitTeam : MonoBehaviour
+{
+ [Header("阵营 ID")]
+ [Tooltip("0 = 玩家1阵营,1 = 玩家2阵营,2+ = 其他阵营")]
+ public int teamId = 0;
+
+ ///
+ /// 判断目标是否为敌人(不同阵营)
+ ///
+ public bool IsEnemy(GameObject other)
+ {
+ if (other == null) return false;
+
+ var otherTeam = other.GetComponent();
+ if (otherTeam == null) return false;
+
+ return otherTeam.teamId != this.teamId;
+ }
+
+ ///
+ /// 判断目标是否为友军(相同阵营)
+ ///
+ public bool IsFriendly(GameObject other)
+ {
+ if (other == null) return false;
+
+ var otherTeam = other.GetComponent();
+ if (otherTeam == null) return false;
+
+ return otherTeam.teamId == this.teamId;
+ }
+
+ ///
+ /// 判断是否属于本地玩家阵营
+ ///
+ public bool IsLocalPlayerTeam()
+ {
+ if (GameManager.Instance == null) return teamId == 0;
+ return teamId == GameManager.Instance.LocalPlayerTeamId;
+ }
+}
diff --git a/Assets/Script/UnitTeam.cs.meta b/Assets/Script/UnitTeam.cs.meta
new file mode 100644
index 0000000..ab5d2ba
--- /dev/null
+++ b/Assets/Script/UnitTeam.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: WnxJsSr/VX1DeBY80HGM2j/amZWAkx8FQCM7bFTomhdecHzVdHd5eOw=
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Script/WorldHealthBar.cs b/Assets/Script/WorldHealthBar.cs
index 19fbfeb..dec374b 100644
--- a/Assets/Script/WorldHealthBar.cs
+++ b/Assets/Script/WorldHealthBar.cs
@@ -484,9 +484,16 @@ public class WorldHealthBar : MonoBehaviour
private Color GetHealthFillColor()
{
- return CompareTag("Enemy")
- ? new Color(0.88f, 0.12f, 0.12f, 1f)
- : new Color(0.1f, 0.8f, 0.18f, 1f);
+ // 使用阵营 ID 判断敌我(支持 PVP)
+ var unitTeam = GetComponent();
+ if (unitTeam != null && unitTeam.IsLocalPlayerTeam())
+ {
+ return new Color(0.1f, 0.8f, 0.18f, 1f); // 绿色(我方)
+ }
+ else
+ {
+ return new Color(0.88f, 0.12f, 0.12f, 1f); // 红色(敌方)
+ }
}
private Color GetEnergyFillColor()
diff --git a/CODELY.md b/CODELY.md
index 1496976..fca5fe4 100644
--- a/CODELY.md
+++ b/CODELY.md
@@ -23,6 +23,7 @@
- [2026-09-14 18:24:56] 用户认可"参考游戏是基地离得远、镜头更远、单位推进慢"的观感方向(2026-09-14 数值改慢后反馈"好多了")。**Why:** 之前双方距离近+移速快,单位两三秒就接触,战斗缺乏观察窗口。**How to apply:** 后续地图/镜头调整优先加长双方距离、拉远摄像机;数值宁慢勿快。
- [2026-09-16 22:18:14] 召唤系统:找不到合适位置时直接在召唤师前方强制生成,不显示警告,不退还能量。**Why:** 用户要求召唤必须成功,不被取消。**How to apply:** MainCharacterCombat.cs 的召唤逻辑
- [2026-09-16 22:18:30] 召唤打断移动:主角点击召唤时必须立即停止移动,调用 SummonerClickMovement.ClearTarget() 来实现。**Why:** 用户要求召唤时即使在移动也要立刻停下。**How to apply:** BeginSummon 开始时停止移动
+- [2026-09-17 10:06:34] 召唤师移动后连续召唤动画超时问题:SummonerClickMovement.HandleMovement() 每帧设置 animator.SetInteger("speed", 1) 干扰召唤动画。**Why:** 即使召唤时调用了 ClearTarget(),下一帧 Update 还是会覆盖动画参数。**How to apply:** 召唤期间禁用移动脚本 (clickMovement.enabled = false),召唤完成后重新启用 (enabled = true)。已在 MainCharacterCombat.Summon() 和 FinishSummon() 中实现。
### Project
- [2026-08-28 14:26:56] 弓箭手远程箭矢的设计要求:保持发射时高度水平飞行,命中目标后直接结算伤害并销毁,不产生实体物理击退。**Why:** 当前玩法希望远程攻击是命中判定而非物理推动。**How to apply:** 后续调整箭矢、Projectile 或弓箭手攻击逻辑时保持这一行为。
@@ -57,6 +58,11 @@
- [2026-09-15 19:33:31] 金币系统已完成:GoldManager 单例管理,初始500金币,右上角UI实时更新。金矿建筑产出50金币/5秒,主角产出30金币/8秒。WorldHealthBar 的 currentEnergy 改为属性(带 setter 自动调用 UpdateEnergyFill),能量条尺寸从8像素增加到12像素(EnergyPixelHeight=12,EnergyPixelWidth=100)。能量条显示金币产出进度,满了产出并重置。已更新核心玩法文档。
- [2026-09-16 14:22:13] 2026-09-16 玩法定稿为"召唤师对决"并已写入《召唤之战-核心玩法.md》:无基地/金矿/兵营/防御塔,玩家手动点地操控召唤师(移动不攻击、站定攻击),限己方半场(三分屏:己方/交战/敌方区),能量直购出兵(单位在召唤师身边生成),击杀敌方召唤师获胜,超时毒圈收缩并解除半场限制,中期混沌裂缝事件,召唤师技能 3~4 种,摇杆留作实测后升级项。**Why:** 用户多轮迭代后的最终方向,旧"建筑生产+自动作战"对攻玩法封存不删。**How to apply:** 新战斗开发一律基于召唤师对决方案;不要在新玩法场景引入金矿/兵营/建筑摆放;BuildingPlacementManager/Barracks/GoldMine 等封存留作后续建筑卡或 PVE 特殊关素材。
- [2026-09-16 14:48:52] 召唤师对决首版场景已完成(SummonerDuelTest.unity,2026-09-16):新场景地面20×80、固定正交相机43°/orthoSize28、召唤师z=0(敌方z=35)均避开底部UI;新增 SummonerClickMovement(点地走位+clamp X[-9,9] Z[-19,6],移动中关攻击、到点恢复)、SummonerDuelManager(点卡片按能量直召,单位在召唤师身边生成)、UnitBase 新增 summonerMode 开关(不追击、站定攻击,默认关闭不影响旧单位);战斗界面UI从 CoreBattleTest 整树搬运复用,SummonCardButton 增加对决模式分支(无布阵/金币管理器时走能量直召+冷却)。主角已存为 Assets/Prefabs/主角/召唤师主角.prefab。**How to apply:** 后续对决玩法迭代(毒圈/胜负判定/召唤师技能)基于该场景与脚本扩展;Play Mode 验证等用户手动执行。
+- [2026-09-17 09:33:46] 阵营 ID 系统已实现(替代 Unity Tag 判断敌我):UnitTeam 组件标记 teamId,GameManager.LocalPlayerTeamId 标识本地玩家阵营,支持 PVP(真人 vs 真人 / 真人 vs 机器人)。**Why:** PVP 需要动态敌我判断,Tag 系统无法支持(双方客户端看到的 tag 矛盾)。**How to apply:** 所有敌我判断改用 UnitTeam.IsEnemy(),血条颜色用 IsLocalPlayerTeam(),新生成单位必须设置 teamId(Barracks/BattleSetupManager 已修改)。
+- [2026-09-17 09:33:58] 召唤师组件精简:移除了 NavMeshAgent、UnitBase、StatusEffectController,保留 MainCharacterCombat(战斗)+ SummonerClickMovement(移动)+ WorldHealthBar(血条)。SummonerClickMovement 已改为依赖 MainCharacterCombat 而非 UnitBase。**Why:** 召唤师是特殊单位,不应使用通用 UnitBase 逻辑;直线移动不需要 NavMesh。**How to apply:** 召唤师专属逻辑用 MainCharacterCombat,普通单位用 UnitBase。
+- [2026-09-17 09:34:13] 《召唤之战》场地规格:60×20(Z×X),三区划分各 20 单位 - 己方区 z∈[-30,-10](召唤师 z=-20),交战区 z∈[-10,10],敌方区 z∈[10,30](敌方召唤师 z=+20)。召唤师移动限制在己方区内(SummonerClickMovement clamp Z[-29,-11])。相机 orthoSize=22,位置(0,40,-40),角度 43°。**Why:** 竖屏竞技场设计,双方对称 40 单位间距,防风筝规则(召唤师不能进入交战区)。**How to apply:** 所有位置/spawn 相关代码使用新的 Z 坐标范围。
+- [2026-09-17 10:06:48] Projectile 伤害判定逻辑:HitTarget() 检查 UnitBase(普通单位)、BuildingHealth(建筑)、WorldHealthBar(召唤师等特殊单位)三种受击组件。召唤师使用 MainCharacterCombat + WorldHealthBar,不挂 UnitBase。**Why:** 召唤师是玩家控制的特殊单位,行为逻辑与普通 AI 单位不同。**How to apply:** 新增特殊单位必须有 WorldHealthBar 或 UnitBase 之一才能被攻击;法术球击中后调用对应组件的受伤方法(UnitBase.ReceiveDamage / WorldHealthBar.TakeDamage)。
+- [2026-09-17 10:38:49] 召唤师(MainCharacterCombat)与普通单位(UnitBase)的架构差异:召唤师使用 MainCharacterCombat + WorldHealthBar + StatusEffectController,不挂 UnitBase。**Why:** 召唤师是玩家控制的特殊单位,行为逻辑与 AI 单位不同。**How to apply:** 涉及单位的通用系统(伤害、眩晕、范围效果)必须同时支持 UnitBase 和非 UnitBase 路径。已修复:Projectile.HitTarget()、UnitBase.ApplyDirectDamage()、UnitBase.ApplyAreaStun() 均添加了 WorldHealthBar / StatusEffectController 的检测分支。
### Reference
- [2026-09-09 12:23:17] 飞行物脚本 Projectile.cs 负责锁敌追踪、碰撞检测和伤害结算。箭矢预制体 ArrowProjectile_White.prefab 已配置 Projectile 组件和 CapsuleCollider(Is Trigger)。远程单位需在攻击动画松弦帧添加 OnAttackEffect Event,并在 UnitBase 中设置 attackType=RangedProjectile、projectilePrefab、projectileSpawnPoint。
diff --git a/召唤之战 - 核心玩法拆家版.md b/召唤之战 - 核心玩法拆家版.md
new file mode 100644
index 0000000..fd53653
--- /dev/null
+++ b/召唤之战 - 核心玩法拆家版.md
@@ -0,0 +1,171 @@
+# 召唤之战-核心玩法拆家版
+> 版本:V1.1
+> 定位:竖版手机休闲即时战略小游戏
+> 单局时长:1~3分钟
+> 核心目标:摧毁敌方主基地获胜
+> 核心特点:轻量操作、自由寻路、资源与出兵据点争夺、兵种克制,无复杂框选与编队
+
+## 一、核心定位
+本玩法为《召唤之战》的「拆家流」经典RTS版本,玩家通过消耗法力召唤单位,争夺战场上的水晶矿与随机出兵点,建立经济或兵力优势,最终突破防御摧毁敌方基地取胜。
+- 操作极简:仅需「选择出兵目标 + 点击卡牌召唤单位」,无需框选、编队、驻守指令;
+- 单位全自动作战:复用自动寻路与寻敌逻辑,玩家不干预单位微操;
+- 策略核心:出兵目标选择、兵种搭配、资源分配节奏、资源点与出兵点取舍,避免纯数值对撞。
+
+## 二、战场地图设计
+### 2.1 整体布局
+采用**竖版9:16比例**,核心可通行区域为十字形,外围为不可通行障碍物(岩石/树林)。既限制战场范围避免混乱,又保留多路径进攻空间,杜绝单路排队式战斗。
+┌──────────────────────────────┐
+│ 障碍物(不可通行) │
+│ ┌────────────────────────┐ │
+│ │ 【敌方城堡】 │ │
+│ │ 【敌方防御塔】 │ │
+│ │ │ │
+│ │ ●─────────● │ │
+│ │ 水晶矿 出兵点 │ │
+│ │ (横向通道两端) │ │
+│ │ │ │
+│ │ 【我方防御塔】 │ │
+│ │ 【我方城堡】 │ │
+│ └────────────────────────┘ │
+│ 障碍物(不可通行) │
+└──────────────────────────────┘
+
+
+### 2.2 区域说明
+| 区域 | 位置 | 规则 |
+|------|------|------|
+| 己方阵地 | 屏幕下方 | 包含我方城堡、防御塔,为单位出生点 |
+| 敌方阵地 | 屏幕上方 | 包含敌方城堡、防御塔,为最终攻击目标 |
+| 十字主通道 | 战场中部 | 纵向连通双方阵地,横向连通水晶矿与出兵点;单位可在区域内自由移动 |
+| 水晶矿 | 横向通道一端 | 可攻击争夺目标,占领后持续提升法力回复速度 |
+| 随机出兵点 | 横向通道另一端 | 可攻击争夺目标,占领后定时随机产出单位 |
+
+### 2.3 寻路规则
+- 基于NavMesh实现自由寻路,仅十字区域为可通行烘焙区,障碍物完全阻挡;
+- 单位自动计算最短路径前往目标,途中自动避让友军与地形;
+- 无强制固定路径,单位可在十字区域内任意移动,避免排队式战斗。
+
+## 三、资源系统
+### 3.1 法力水晶
+- 战斗内唯一资源,用于召唤单位、释放技能;
+- 基础自动回复,开局默认速度,上限10点;
+- 占领水晶矿可提升回复速度,具体回复加成数值以测试为准;
+
+### 3.2 资源据点争夺规则
+- 水晶矿与随机出兵点均为可攻击目标,不设置生命值,使用**红蓝争夺进度条**(0~100);
+- 进度条中立状态为50:50。以当前观察方为例,100表示该方完全占领,0表示对方完全占领;
+- 己方单位攻击据点时,进度条向己方方向偏移;敌方攻击时,进度条向敌方方向偏移;
+- 进度条达到己方100%时,据点归属己方并开始产出对应资源;
+- 据点归属后,即使敌方开始攻击并推动进度条反向移动,原占有方仍持续获得产出;
+- 只有当进度条被对方完全推到100%(即原占有方进度为0)时,才中止原占有方产出并将据点翻转给对方;
+- 无任何单位攻击时,进度条保持当前数值不变;
+- 据点翻转后,按相同规则由新的占有方获得对应产出。
+
+### 3.3 水晶矿产出
+- 水晶矿归属己方后,持续提升己方法力回复速度;
+- 水晶矿被对方完全翻转后,法力回复加成立即转移给对方;
+- 单个水晶矿的回复加成与多个据点叠加规则,以战斗测试结果为准。
+
+### 3.4 随机出兵点产出
+- 随机出兵点归属己方后,按固定间隔在出兵点附近自动生成1个单位;
+- 产出单位从**当前玩家出战卡组**中随机抽取,不从所有已解锁或全体角色中抽取;
+- 随机概率按召唤费用分层:3费及以下概率较高,费用越高概率越低;具体概率以测试为准;
+- 产出单位不消耗玩家法力,价值定位低于玩家主动召唤,避免完全替代正常出兵;
+- 产出单位默认执行随机出兵点归属方当前的出兵目标;如无特殊设置,则默认目标为敌方主基地;
+- 随机出兵点被对方完全翻转后,自动出兵权转移给对方。
+
+## 四、单位系统
+### 4.1 通用属性
+所有单位统一包含:召唤费用、生命值、攻击力、攻击距离、移动速度、攻击优先级。
+
+### 4.2 单位分类与定位
+| 单位 | 费用档位 | 定位 | 核心特点 |
+|------|----------|------|----------|
+| 近战步兵 | 低费 | 前排肉盾/量产 | 血量适中,近战输出,优先攻击敌方单位 |
+| 远程射手 | 中费 | 后排输出 | 身脆伤害高,需前排保护,克制无甲单位 |
+| 重甲单位 | 高费 | 推线核心 | 高血量,伤害偏低,优先攻击建筑 |
+| 范围法师 | 高额 | 群体输出 | 范围伤害,克制扎堆小兵,自身极脆 |
+
+### 4.3 基础克制关系
+步兵克制法师、射手克制步兵、重甲克制建筑、法师克制扎堆步兵,形成循环博弈。
+
+## 五、核心战斗逻辑
+### 5.1 单位AI优先级(固定不变)
+单位自动寻敌,优先级从高到低:
+1. 视野内的敌方单位(最高优先级,发现即停止移动并攻击)
+2. 自身绑定的指定目标(水晶矿 / 随机出兵点 / 敌方基地)
+3. 路径上的敌方防御塔
+4. 敌方主基地
+
+> 说明:敌方单位优先级永远最高,保证单位不会无视敌人强行拆矿/拆塔;敌人击杀后,自动继续前往原定目标。
+
+### 5.2 出兵目标机制(方案B)
+核心操作规则,**无驻守指令、无全局切换、无单位选中**,完全通过「召唤前预设目标」实现分兵。
+1. 玩家在召唤单位前,先选择目标(进攻基地 / 进攻水晶矿 / 进攻随机出兵点);
+2. 点击卡牌召唤后,**新出生的单位永久绑定该目标**,直到死亡;
+3. 场上已存在的单位,目标不受后续按钮切换影响,始终执行出生时的任务;
+4. 默认目标为「进攻敌方基地」,符合玩家基础直觉。
+
+### 5.3 战斗行为示例
+1. 玩家选择目标「水晶矿」,召唤2个步兵,步兵出生后自动向水晶矿移动;
+2. 途中遭遇敌方单位,步兵立刻停止前进,优先攻击敌人;
+3. 敌人被击杀后,步兵继续前往水晶矿,攻击水晶推进己方进度;
+4. 玩家切换目标为「进攻随机出兵点」,再次召唤1个重甲,重甲出生后自动前往出兵点;
+5. 此前的2个步兵仍继续攻击水晶矿,不受目标切换影响,实现分兵作战;
+6. 随机出兵点归属己方期间,按固定间隔从玩家出战卡组中随机生成单位,生成单位默认前往敌方主基地。
+
+## 六、UI布局与操作
+### 6.1 整体布局
+采用「上战场、下操作」的竖版经典结构,战斗视野完整,拇指可覆盖全部操作区。
+┌──────────────────────────────┐
+│ │
+│ 战场区域(2.5D) │
+│ │
+│ │
+│ │
+├──────────────────────────────┤
+│ 攻城堡 打水晶 打出兵点 │ ← 目标选择按钮组
+├──────────────────────────────┤
+│ 步兵 射手 重甲 法师 │ ← 单位召唤卡牌面板
+└──────────────────────────────┘
+
+### 6.2 目标选择按钮组
+- 位置:召唤卡牌面板正上方,横向并排3个按钮;
+- 交互:同一时间仅一个选中,选中态高亮显示,未选中置灰;
+- 逻辑:仅决定「下一次召唤单位的目标」,不影响场上已存活单位;
+- 可选目标为「攻城堡」「打水晶」「打出兵点」;
+- 默认选中「攻城堡」,开局无需额外操作即可出兵。
+
+### 6.3 单位召唤卡牌面板
+- 位置:屏幕最底部,横向排列单位卡牌;
+- 交互:点击卡牌消耗对应法力,单位在己方城堡附近生成;
+- 信息:卡牌显示单位图标、召唤费用,费用不足时置灰不可点击。
+
+### 6.4 标准操作流程
+1. 观察战场局势,决定当前波次的目标(抢水晶矿、抢随机出兵点或推基地);
+2. 点击对应目标按钮,确认选中状态;
+3. 点击单位卡牌,消耗法力召唤对应兵种;
+4. 重复上述步骤,多批次分配不同目标的部队,形成多线作战。
+
+## 七、单局对局流程
+1. **开局阶段(0~30秒)**
+ 双方基础法力回复,默认出兵冲基地试探;可选择抢先派兵争夺水晶矿或随机出兵点,建立经济或兵力优势。
+
+2. **博弈阶段(30秒~1分30秒)**
+ 围绕水晶矿与随机出兵点展开争夺,搭配兵种组合,用前排+后排阵容推进;通过分兵同时牵制资源据点与主线。
+
+3. **决胜阶段(1分30秒~3分钟)**
+ 积累足够兵力后集中推进,突破防御塔,集火敌方城堡;城堡血量归零则对局结束。
+ 若3分钟未分胜负,按城堡剩余血量百分比判定胜负,防止无限拉扯。
+
+## 八、核心策略博弈点
+1. **资源节奏**:攒法力出高费重甲,还是持续低费小兵压制;
+2. **据点取舍**:优先抢水晶矿提升长期法力回复,还是抢随机出兵点获得额外兵力,或全力一波速攻基地;
+3. **分兵策略**:同时争夺水晶矿和随机出兵点,还是集中兵力主攻一路;
+4. **兵种搭配**:前排抗线+后排输出的组合,避免被范围伤害一波清场。
+
+## 九、开发落地建议
+1. **寻路复用**:直接复用现有NavMesh寻路逻辑,仅需烘焙十字形可行走区域;
+2. **AI简化**:保留原有单位战斗状态机,仅新增「出生目标绑定」字段,无需新增驻守状态;
+3. **据点实现**:水晶矿与随机出兵点作为特殊可攻击目标,共用红蓝争夺进度数值;据点达到己方100%后开始产出,只有被对方完全推到0才翻转并转移产出;水晶矿提供法力回复加成,随机出兵点按费用权重从玩家出战卡组中自动生成单位;
+4. **UI优先**:先实现底部目标按钮+召唤面板的核心交互,再扩展技能、特效等内容。
diff --git a/小游戏可行性方案.md b/小游戏可行性方案.md
deleted file mode 100644
index 641af63..0000000
--- a/小游戏可行性方案.md
+++ /dev/null
@@ -1,386 +0,0 @@
-# 微信/抖音小游戏可行性方案:法师捕获召唤闯关
-
-## 1. 项目定位
-
-目标做一款适合微信小游戏、抖音小游戏的轻量 3D 闯关游戏。
-
-核心卖点:
-
-- 玩家扮演法师。
-- 打倒或削弱敌人后,可以捕获敌人单位。
-- 捕获后的单位可在战斗中召唤出来协助作战。
-- 召唤单位消耗法力,法力会随时间自动恢复。
-- 以短关卡推进,适合碎片化游玩和广告变现。
-
-一句话概括:
-
-> 轻量版“捕获 + 召唤 + 闯关”的 3D 卡通动作策略小游戏。
-
-## 2. 核心玩法判断
-
-这个方向可行,但不建议做成开放世界或复杂解谜大作。小游戏平台更适合:
-
-- 单局时间短。
-- 操作简单。
-- 目标明确。
-- 关卡推进快。
-- 内容可以持续扩展。
-
-因此推荐形态是:
-
-> 俯视角 / 斜俯视角 3D 关卡制动作策略游戏。
-
-玩家主要体验不是复杂操作,而是:
-
-- 走位躲避。
-- 普攻削血。
-- 捕获敌人。
-- 选择合适单位召唤。
-- 用有限法力解决当前战斗。
-
-## 3. MVP 核心循环
-
-第一版只验证核心是否好玩,不做过多外围系统。
-
-基础循环:
-
-```text
-进入关卡
-→ 遇到敌人
-→ 主角自动普攻削血
-→ 敌人进入可捕获状态
-→ 玩家捕获单位
-→ 消耗法力召唤已捕获单位
-→ 单位协助战斗
-→ 通关获得奖励
-→ 解锁/强化单位
-→ 进入下一关
-```
-
-MVP 必须具备:
-
-1. 主角移动。
-2. 主角简单自动普攻。
-3. 敌人血量与死亡。
-4. 敌人残血捕获。
-5. 单位收集列表。
-6. 法力自动恢复。
-7. 消耗法力召唤单位。
-8. 至少 3 种敌人/可召唤单位。
-9. 至少 5 个测试关卡。
-10. 简单胜负条件。
-
-## 4. 主角设计
-
-### 4.1 主角是否需要攻击能力
-
-需要,但第一版只做简单普攻。
-
-理由:
-
-- 没有普攻,玩家开局或单位死亡后会失去行动能力。
-- 捕获系统需要玩家先削弱敌人。
-- 普攻能保证玩家持续参与,而不是只等法力恢复。
-- 主角不应成为主要输出,否则召唤系统价值会下降。
-
-### 4.2 推荐定位
-
-主角是“指挥型法师”:
-
-- 能自保。
-- 能削血。
-- 能补刀。
-- 真正的战斗主力来自召唤单位。
-
-输出占比建议:
-
-| 来源 | 占比 |
-|---|---:|
-| 主角普攻 | 30% |
-| 召唤单位 | 60% |
-| 法术/道具 | 10% |
-
-### 4.3 第一版主角能力
-
-- 移动:虚拟摇杆或拖拽移动。
-- 普攻:自动锁定最近敌人,发射魔法弹。
-- 捕获:敌人低血量时点击捕获按钮。
-- 召唤:选择已捕获单位,在目标位置放置。
-
-暂不做复杂连招、装备技能树和多主动技能。
-
-## 5. 法力系统
-
-法力是召唤单位的核心资源。
-
-### 5.1 基础规则
-
-- 法力随时间自动恢复。
-- 不同单位消耗不同法力。
-- 法力有上限。
-- 召唤后立即扣除法力。
-- 强单位消耗高,弱单位消耗低。
-
-### 5.2 推荐初始数值
-
-| 参数 | 建议值 |
-|---|---:|
-| 初始法力上限 | 10 |
-| 初始法力恢复 | 1 点 / 秒 |
-| 小怪召唤消耗 | 2-3 |
-| 远程单位消耗 | 3-4 |
-| 坦克单位消耗 | 4-5 |
-| 精英单位消耗 | 6-8 |
-
-### 5.3 设计目的
-
-法力系统要制造选择:
-
-- 是马上召唤两个小怪?
-- 还是攒法力召唤一个强力单位?
-- 是用坦克挡伤害?
-- 还是用远程单位补输出?
-
-## 6. 捕获系统
-
-捕获是项目最有辨识度的玩法。
-
-### 6.1 第一版捕获规则
-
-推荐简单规则:
-
-- 敌人血量低于 30% 时进入可捕获状态。
-- 可捕获状态显示明显特效或图标。
-- 玩家点击捕获按钮后尝试捕获。
-- 普通敌人捕获成功率高。
-- 精英敌人捕获成功率低或需要通关后解锁。
-
-### 6.2 MVP 简化方案
-
-第一版可以先做确定性捕获:
-
-```text
-敌人血量低于 30% → 点击捕获 → 100% 成功
-```
-
-这样方便验证玩法闭环。后续再加入概率、品质、道具等复杂机制。
-
-### 6.3 捕获后的单位
-
-捕获后进入单位池:
-
-- 可以在当前关卡继续召唤。
-- 也可以进入关卡外图鉴。
-- 同类单位可以转化为升级材料。
-
-## 7. 召唤单位设计
-
-第一版建议只做 3-5 种单位,保证差异明显。
-
-| 单位 | 定位 | 特点 | 法力消耗 |
-|---|---|---|---:|
-| 小史莱姆 | 低费近战 | 便宜、数量多、伤害低 | 2 |
-| 蓝色怪物 | 近战战士 | 均衡输出与血量 | 3 |
-| 石头怪 | 坦克 | 血厚、移速慢 | 5 |
-| 弓箭手 | 远程输出 | 脆皮、站后排 | 4 |
-| 冰霜怪 | 控制 | 攻击带减速 | 5 |
-
-MVP 可以先做:
-
-1. 近战小怪。
-2. 坦克小怪。
-3. 远程小怪。
-
-只要三种单位的战斗感受不同,就能验证策略性。
-
-## 8. 关卡结构
-
-### 8.1 单关时长
-
-小游戏适合短关卡:
-
-| 类型 | 时长 |
-|---|---:|
-| 普通关 | 1-2 分钟 |
-| 精英关 | 2-3 分钟 |
-| Boss 关 | 3-5 分钟 |
-
-### 8.2 第一版关卡类型
-
-推荐先做闯关型:
-
-- 每关一个小地图。
-- 玩家从入口进入。
-- 击败全部敌人或击败 Boss 后通关。
-- 通关后获得金币、经验、单位碎片。
-
-### 8.3 关卡变化方式
-
-早期不需要大地图,靠以下内容制造变化:
-
-- 敌人组合变化。
-- 障碍物位置变化。
-- 地形大小变化。
-- 陷阱和机关。
-- Boss 技能变化。
-
-## 9. 成长系统
-
-第一版成长不要太重,避免拖慢开发。
-
-### 9.1 法师成长
-
-- 最大生命值。
-- 普攻伤害。
-- 法力上限。
-- 法力恢复速度。
-- 捕获范围。
-
-### 9.2 单位成长
-
-- 单位等级。
-- 生命值提升。
-- 攻击力提升。
-- 特殊效果增强。
-
-### 9.3 图鉴系统
-
-图鉴是很适合这个玩法的长期目标:
-
-- 收集过的单位会点亮图鉴。
-- 图鉴给少量属性加成。
-- 稀有单位作为关卡目标。
-
-## 10. 广告变现设计
-
-这个玩法可以做广告变现,尤其适合激励视频广告。
-
-### 10.1 推荐广告点
-
-| 广告位 | 说明 | 推荐程度 |
-|---|---|---|
-| 失败复活 | 死亡后看广告复活一次 | 高 |
-| 通关奖励翻倍 | 通关后金币/材料翻倍 | 高 |
-| 额外捕获机会 | 捕获失败后看广告再试一次 | 中高 |
-| 每日奖励翻倍 | 每日登录奖励翻倍 | 中 |
-| 体力/门票补充 | 体力不足时看广告补充 | 中 |
-| 临时法力加速 | 本关法力恢复短时间提升 | 中 |
-
-### 10.2 不建议的广告
-
-不建议做:
-
-- 关卡中强制插屏。
-- 每一两分钟强制广告。
-- 战斗中频繁打断操作。
-
-小游戏前期更适合激励广告,让玩家主动选择看广告。
-
-## 11. 技术可行性
-
-### 11.1 适合小游戏的原因
-
-- 玩法可以做成小地图短关卡。
-- 角色数量可控。
-- 3D 卡通低模风格适合性能控制。
-- 系统可以逐步扩展,不必一开始做大世界。
-
-### 11.2 风险点
-
-| 风险 | 说明 | 规避方式 |
-|---|---|---|
-| 操作复杂 | 移动、攻击、捕获、召唤同时存在 | 普攻自动化,玩家只管移动、捕获、召唤 |
-| 单位太多导致混乱 | 召唤物数量过多会遮挡和卡顿 | 限制同时在场数量或总法力 |
-| 关卡重复 | 只打怪容易腻 | 增加敌人组合、地形、Boss、词缀 |
-| 平衡困难 | 捕获和召唤容易过强 | 用法力消耗、召唤上限、冷却控制 |
-| 性能压力 | 小游戏平台性能有限 | 低模、少骨骼、少特效、限制同屏单位 |
-
-### 11.3 性能建议
-
-- 采用低模 3D 卡通风格。
-- 同屏敌人 + 召唤物建议控制在 20 个以内。
-- 单位动画简单化。
-- 特效使用少量粒子或贴片动画。
-- 地图使用简单 Mesh / Cube / Plane 组合。
-- UI 和战斗逻辑尽量轻量。
-
-## 12. 推荐开发阶段
-
-### 阶段 1:核心原型
-
-目标:验证“捕获 + 召唤”是否好玩。
-
-内容:
-
-- 一个测试地图。
-- 一个主角。
-- 三种敌人。
-- 自动普攻。
-- 残血捕获。
-- 法力恢复。
-- 召唤单位。
-- 一个胜负流程。
-
-### 阶段 2:关卡 MVP
-
-目标:做出可连续玩的体验。
-
-内容:
-
-- 5-10 个关卡。
-- 1 个 Boss。
-- 简单成长系统。
-- 单位图鉴雏形。
-- 通关奖励。
-
-### 阶段 3:小游戏商业化测试
-
-目标:验证留存和广告点。
-
-内容:
-
-- 失败复活广告。
-- 通关奖励翻倍广告。
-- 每日奖励。
-- 基础数据埋点。
-
-### 阶段 4:内容扩展
-
-目标:增强长期游玩。
-
-内容:
-
-- 更多单位。
-- 更多 Boss。
-- 关卡词缀。
-- 每日挑战。
-- 单位升级和稀有度。
-
-## 13. 当前项目建议
-
-当前项目还处于早期,建议不要急着做完整游戏系统。
-
-最优先事项:
-
-1. 确定角色控制方式。
-2. 做一个 40x40 的测试地图。
-3. 让蓝色怪物作为第一个敌人/召唤单位。
-4. 做主角自动普攻。
-5. 做敌人残血捕获。
-6. 做法力条和召唤按钮。
-
-第一阶段目标不是好看,而是验证:
-
-> 玩家是否愿意为了捕获单位而继续打下一关。
-
-## 14. 最终建议
-
-这个项目方向可行,适合做小游戏。
-
-建议坚持三个原则:
-
-1. **主角简单**:移动 + 自动普攻 + 捕获 + 召唤。
-2. **单位有差异**:近战、坦克、远程、控制逐步扩展。
-3. **关卡短平快**:1-3 分钟一局,适合广告和碎片化游玩。
-
-第一版不要追求复杂剧情、大地图和大量角色。先把“捕获敌人并召唤出来战斗”的核心爽点做出来,再决定是否扩展为更大的项目。