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 分钟一局,适合广告和碎片化游玩。 - -第一版不要追求复杂剧情、大地图和大量角色。先把“捕获敌人并召唤出来战斗”的核心爽点做出来,再决定是否扩展为更大的项目。