new spawning manager, spawner improvements - now with multiple enemies to spawn, KillTrigger
This commit is contained in:
@@ -3177,6 +3177,18 @@ Transform:
|
|||||||
type: 3}
|
type: 3}
|
||||||
m_PrefabInstance: {fileID: 789096691}
|
m_PrefabInstance: {fileID: 789096691}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
--- !u!114 &579328271 stripped
|
||||||
|
MonoBehaviour:
|
||||||
|
m_CorrespondingSourceObject: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
|
type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 789096691}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 01a49bbd2406d4a5f8ae5ad40326117b, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
--- !u!21 &581839214
|
--- !u!21 &581839214
|
||||||
Material:
|
Material:
|
||||||
serializedVersion: 8
|
serializedVersion: 8
|
||||||
@@ -11216,7 +11228,7 @@ PrefabInstance:
|
|||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_distance
|
propertyPath: m_distance
|
||||||
value: 20
|
value: 200
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
@@ -12619,7 +12631,7 @@ PrefabInstance:
|
|||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_distance
|
propertyPath: m_distance
|
||||||
value: 20
|
value: 200
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
@@ -12640,6 +12652,7 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 796152178}
|
- component: {fileID: 796152178}
|
||||||
|
- component: {fileID: 796152179}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: 1= ENEMIES Spawners
|
m_Name: 1= ENEMIES Spawners
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -12667,6 +12680,40 @@ Transform:
|
|||||||
- {fileID: 579328270}
|
- {fileID: 579328270}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &796152179
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 796152177}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 6ce0b24e87d404b299a748ccc1671160, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
spawnerConfigurations:
|
||||||
|
- spawner: {fileID: 1804595575}
|
||||||
|
overridePrefab: {fileID: 145182807891766407, guid: e115436bfe06bd447a266ca75621bbdd,
|
||||||
|
type: 3}
|
||||||
|
overrideEnemiesPerWave: 3
|
||||||
|
- spawner: {fileID: 1238364584}
|
||||||
|
overridePrefab: {fileID: 8406365487746723040, guid: fcfa2b03d43105e43a8c6b1931305099,
|
||||||
|
type: 3}
|
||||||
|
overrideEnemiesPerWave: 5
|
||||||
|
- spawner: {fileID: 1491841298}
|
||||||
|
overridePrefab: {fileID: 3165479696603231053, guid: 5e1c7bd492ab28047bf1e68ef6ae8b9f,
|
||||||
|
type: 3}
|
||||||
|
overrideEnemiesPerWave: 4
|
||||||
|
- spawner: {fileID: 579328271}
|
||||||
|
overridePrefab: {fileID: 974380812503253259, guid: fe3f8cbdaa5d03147a0de1c74712d32b,
|
||||||
|
type: 3}
|
||||||
|
overrideEnemiesPerWave: 4
|
||||||
|
globalOverridePrefab: {fileID: 0}
|
||||||
|
globalOverrideEnemiesPerWave: -1
|
||||||
|
overrideOnStart: 1
|
||||||
|
autoEnableSpawners: 1
|
||||||
|
disableSpawnersOnStart: 1
|
||||||
--- !u!1 &799294618
|
--- !u!1 &799294618
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -13389,7 +13436,7 @@ Material:
|
|||||||
m_Offset: {x: 0, y: 0}
|
m_Offset: {x: 0, y: 0}
|
||||||
m_Ints: []
|
m_Ints: []
|
||||||
m_Floats:
|
m_Floats:
|
||||||
- _AlphaPow: -1.89
|
- _AlphaPow: 2
|
||||||
- _BlendMode: 1
|
- _BlendMode: 1
|
||||||
- _BumpScale: 1
|
- _BumpScale: 1
|
||||||
- _CullMode: 2
|
- _CullMode: 2
|
||||||
@@ -15472,6 +15519,11 @@ PrefabInstance:
|
|||||||
propertyPath: m_Layer
|
propertyPath: m_Layer
|
||||||
value: 8
|
value: 8
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 4460069056851369441, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
|
type: 3}
|
||||||
|
propertyPath: m_IsActive
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 4460112240072912095, guid: 851e8e61247888340bdec90fc8aa37f5,
|
- target: {fileID: 4460112240072912095, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_Layer
|
propertyPath: m_Layer
|
||||||
@@ -16660,12 +16712,12 @@ PrefabInstance:
|
|||||||
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
|
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_AnchorMax.x
|
propertyPath: m_AnchorMax.x
|
||||||
value: 0
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
|
- target: {fileID: 5657452459766331955, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 1
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5671630659928648966, guid: 851e8e61247888340bdec90fc8aa37f5,
|
- target: {fileID: 5671630659928648966, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
type: 3}
|
type: 3}
|
||||||
@@ -16707,6 +16759,11 @@ PrefabInstance:
|
|||||||
propertyPath: selectedToolbar
|
propertyPath: selectedToolbar
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 5935883485110830931, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
|
type: 3}
|
||||||
|
propertyPath: m_IsActive
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6116517633565365760, guid: 851e8e61247888340bdec90fc8aa37f5,
|
- target: {fileID: 6116517633565365760, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_Layer
|
propertyPath: m_Layer
|
||||||
@@ -16792,6 +16849,11 @@ PrefabInstance:
|
|||||||
propertyPath: m_Layer
|
propertyPath: m_Layer
|
||||||
value: 8
|
value: 8
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 6783963330927412624, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
|
type: 3}
|
||||||
|
propertyPath: m_IsActive
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6786312951323477463, guid: 851e8e61247888340bdec90fc8aa37f5,
|
- target: {fileID: 6786312951323477463, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_IsActive
|
propertyPath: m_IsActive
|
||||||
@@ -17704,6 +17766,18 @@ Material:
|
|||||||
- _SpecColor: {r: 1, g: 1, b: 1, a: 1}
|
- _SpecColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
m_BuildTextureStacks: []
|
m_BuildTextureStacks: []
|
||||||
m_AllowLocking: 1
|
m_AllowLocking: 1
|
||||||
|
--- !u!114 &1238364584 stripped
|
||||||
|
MonoBehaviour:
|
||||||
|
m_CorrespondingSourceObject: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
|
type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 636119092}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 01a49bbd2406d4a5f8ae5ad40326117b, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
--- !u!21 &1242034730
|
--- !u!21 &1242034730
|
||||||
Material:
|
Material:
|
||||||
serializedVersion: 8
|
serializedVersion: 8
|
||||||
@@ -18394,7 +18468,7 @@ PrefabInstance:
|
|||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_distance
|
propertyPath: m_distance
|
||||||
value: 20
|
value: 200
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
@@ -19395,6 +19469,18 @@ Material:
|
|||||||
- _SpecColor: {r: 1, g: 1, b: 1, a: 1}
|
- _SpecColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
m_BuildTextureStacks: []
|
m_BuildTextureStacks: []
|
||||||
m_AllowLocking: 1
|
m_AllowLocking: 1
|
||||||
|
--- !u!114 &1491841298 stripped
|
||||||
|
MonoBehaviour:
|
||||||
|
m_CorrespondingSourceObject: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
|
type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 1316222306}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 01a49bbd2406d4a5f8ae5ad40326117b, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
--- !u!4 &1516777619 stripped
|
--- !u!4 &1516777619 stripped
|
||||||
Transform:
|
Transform:
|
||||||
m_CorrespondingSourceObject: {fileID: 6425420852750441961, guid: 851e8e61247888340bdec90fc8aa37f5,
|
m_CorrespondingSourceObject: {fileID: 6425420852750441961, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||||
@@ -20885,7 +20971,7 @@ PrefabInstance:
|
|||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
propertyPath: m_distance
|
propertyPath: m_distance
|
||||||
value: 20
|
value: 200
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
- target: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
type: 3}
|
type: 3}
|
||||||
@@ -20903,6 +20989,18 @@ Transform:
|
|||||||
type: 3}
|
type: 3}
|
||||||
m_PrefabInstance: {fileID: 1804595573}
|
m_PrefabInstance: {fileID: 1804595573}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
--- !u!114 &1804595575 stripped
|
||||||
|
MonoBehaviour:
|
||||||
|
m_CorrespondingSourceObject: {fileID: 5278190478906961763, guid: dbf14178717e1164086bd444126f4446,
|
||||||
|
type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 1804595573}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 01a49bbd2406d4a5f8ae5ad40326117b, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
--- !u!1001 &1819555269
|
--- !u!1001 &1819555269
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -1,171 +1,411 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Invector;
|
using Invector; // Assuming this is for vDamage, if not used, can be removed
|
||||||
using Invector.vCharacterController.AI;
|
using Invector.vCharacterController.AI;
|
||||||
using PixelCrushers;
|
using PixelCrushers; // For Saver and SaveSystem
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
|
using UnityEngine.AI; // Required for NavMesh
|
||||||
|
|
||||||
namespace Beyond
|
namespace Beyond
|
||||||
{
|
{
|
||||||
public class EnemySpawner : Saver
|
public class EnemySpawner : Saver
|
||||||
{
|
{
|
||||||
public GameObject m_prefab;
|
[Header("Spawning Configuration")]
|
||||||
public Transform m_distanceFrom;
|
public GameObject m_prefab;
|
||||||
//public bool m_distanceFromMainCam;
|
public int m_enemiesPerSpawnWave = 3;
|
||||||
public float m_spawnCheckInterval = .5f;
|
public float m_spawnRadius = 5f;
|
||||||
public float m_distance = 20f;
|
public int m_maxSpawnAttemptsPerEnemy = 10;
|
||||||
public int m_numToSpawn = -1;
|
public float m_navMeshSampleDistance = 1.0f;
|
||||||
public bool m_respawnOnPlayersDeath = true;
|
|
||||||
public LayerMask raycastMask = 1; //default
|
|
||||||
private SaveData m_spawnData;
|
|
||||||
//private bool m_spawned;
|
|
||||||
private vControlAI m_spawnedAI;
|
|
||||||
public UnityEvent<EnemySpawner> m_OnDead;
|
|
||||||
public UnityEvent<EnemySpawner> m_onReceivedDamage;
|
|
||||||
class SaveData
|
|
||||||
{
|
|
||||||
public int numSpawned;
|
|
||||||
public bool spawned;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Awake()
|
[Header("Activation & Respawn")]
|
||||||
{
|
public Transform m_distanceFrom;
|
||||||
#if ENEMIES_DISABLED && UNITY_EDITOR
|
public float m_spawnCheckInterval = .5f;
|
||||||
enabled = false;
|
public float m_distance = 20f;
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
base.Awake();
|
|
||||||
//m_spawned = false;
|
|
||||||
m_spawnData = new SaveData();
|
|
||||||
m_spawnData.numSpawned = 0;
|
|
||||||
m_spawnData.spawned = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnDestroy()
|
[Tooltip("Maximum number of times a full wave can be (re)spawned. Set to -1 for infinite respawns. This count increments each time a wave is successfully initiated.")]
|
||||||
|
public int m_maxWaveRespawns = -1;
|
||||||
|
|
||||||
|
public bool m_respawnOnPlayersDeath = true;
|
||||||
|
public LayerMask raycastMask = 1; // Default layer
|
||||||
|
|
||||||
|
[Header("Events")]
|
||||||
|
public UnityEvent<EnemySpawner> m_OnDead; // Invoked when an enemy spawned by THIS spawner dies
|
||||||
|
public UnityEvent<EnemySpawner> m_onReceivedDamage; // Invoked when an enemy from THIS spawner receives damage
|
||||||
|
|
||||||
|
private SaveData m_spawnData;
|
||||||
|
private List<vControlAI> m_activeEnemies = new List<vControlAI>();
|
||||||
|
private bool _initialWaveCheckPassedThisActivation = false; // Flag to control CheckSpawn behavior per activation cycle
|
||||||
|
|
||||||
|
const float RAY_Y_OFFSET = 30f; // How high above candidate point to start raycast
|
||||||
|
const float GROUND_Y_OFFSET = .1f; // How high above ground to place enemy pivot
|
||||||
|
|
||||||
|
// Internal class for saving spawner state
|
||||||
|
class SaveData
|
||||||
|
{
|
||||||
|
public int wavesSpawnedCount;
|
||||||
|
public bool waveActive;
|
||||||
|
// Note: _initialWaveCheckPassedThisActivation is transient and reset based on game state, not directly saved.
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Awake()
|
||||||
|
{
|
||||||
|
#if ENEMIES_DISABLED && UNITY_EDITOR
|
||||||
|
enabled = false;
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
base.Awake(); // Important: Call Saver's Awake
|
||||||
|
m_spawnData = new SaveData
|
||||||
|
{
|
||||||
|
wavesSpawnedCount = 0,
|
||||||
|
waveActive = false
|
||||||
|
};
|
||||||
|
m_activeEnemies = new List<vControlAI>();
|
||||||
|
_initialWaveCheckPassedThisActivation = false; // Initialize flag
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Start()
|
||||||
|
{
|
||||||
|
base.Start(); // Important: Call Saver's Start
|
||||||
|
|
||||||
|
// Set up m_distanceFrom
|
||||||
|
if (Player.Instance != null)
|
||||||
|
m_distanceFrom = Player.Instance.transform;
|
||||||
|
else if (Camera.main != null)
|
||||||
|
m_distanceFrom = Camera.main.transform;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError($"EnemySpawner ({name}): m_distanceFrom could not be set (Player.Instance and Camera.main are null). Disabling spawner.", this);
|
||||||
|
enabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to player respawn events
|
||||||
|
if (m_respawnOnPlayersDeath && Player.Instance != null)
|
||||||
|
{
|
||||||
|
var respawner = Player.Instance.GetComponent<Respawner>(); // Assuming PixelCrushers Respawner
|
||||||
|
if (respawner != null)
|
||||||
|
{
|
||||||
|
respawner.m_onRespawned.AddListener(OnPlayerRespawned);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"EnemySpawner ({name}): Player.Instance does not have a Respawner component. m_respawnOnPlayersDeath will not function fully.", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start checking for spawn conditions
|
||||||
|
InvokeRepeating("CheckSpawn", m_spawnCheckInterval + (Random.value * m_spawnCheckInterval), m_spawnCheckInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
// Unsubscribe from player respawn events
|
||||||
|
if (m_respawnOnPlayersDeath && Player.Instance != null)
|
||||||
|
{
|
||||||
|
var respawner = Player.Instance.GetComponent<Respawner>();
|
||||||
|
if (respawner != null)
|
||||||
|
{
|
||||||
|
respawner.m_onRespawned.RemoveListener(OnPlayerRespawned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up listeners from active enemies
|
||||||
|
foreach (var ai in m_activeEnemies)
|
||||||
|
{
|
||||||
|
if (ai != null)
|
||||||
|
{
|
||||||
|
ai.onDead.RemoveListener(OnEnemyDead);
|
||||||
|
ai.onReceiveDamage.RemoveListener(OnEnemyReceiveDamage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_activeEnemies.Clear();
|
||||||
|
|
||||||
|
CancelInvoke("CheckSpawn"); // Ensure InvokeRepeating is stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnPlayerRespawned()
|
||||||
|
{
|
||||||
|
if (!m_respawnOnPlayersDeath) return;
|
||||||
|
|
||||||
|
// If a wave was previously active and its enemies are now gone, mark the wave as inactive.
|
||||||
|
if (m_spawnData.waveActive && m_activeEnemies.Count == 0)
|
||||||
|
{
|
||||||
|
m_spawnData.waveActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the flag to allow CheckSpawn (or direct spawn here) to work for this new "life"
|
||||||
|
_initialWaveCheckPassedThisActivation = false;
|
||||||
|
|
||||||
|
// Attempt to spawn a new wave if conditions are met
|
||||||
|
if (!m_spawnData.waveActive && CanSpawnMoreWaves())
|
||||||
|
{
|
||||||
|
if (IsPlayerInRange())
|
||||||
|
{
|
||||||
|
SpawnWave();
|
||||||
|
if (m_spawnData.waveActive) // If wave was successfully spawned by this call
|
||||||
|
{
|
||||||
|
_initialWaveCheckPassedThisActivation = true; // Mark as done for this cycle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else Debug.Log($"Player respawned, but spawner {name} is out of range. No wave spawned immediately by OnPlayerRespawned.");
|
||||||
|
}
|
||||||
|
// else if (m_spawnData.waveActive) Debug.Log($"Player respawned, but spawner {name} still has an active wave.");
|
||||||
|
// else if (!CanSpawnMoreWaves()) Debug.Log($"Player respawned, but spawner {name} has reached its wave limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string RecordData()
|
||||||
|
{
|
||||||
|
return SaveSystem.Serialize(m_spawnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ApplyData(string s)
|
||||||
|
{
|
||||||
|
var data = SaveSystem.Deserialize<SaveData>(s);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
m_spawnData = data;
|
||||||
|
// If loaded save indicates a wave was active but no enemies are present (they weren't saved/restored),
|
||||||
|
// mark wave as inactive.
|
||||||
|
if (m_spawnData.waveActive && m_activeEnemies.Count == 0) // m_activeEnemies is runtime only
|
||||||
|
{
|
||||||
|
m_spawnData.waveActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the state of _initialWaveCheckPassedThisActivation based on loaded data
|
||||||
|
// If a wave was active, or any waves had been spawned, assume the "initial check" for the
|
||||||
|
// current game session (before this load) effectively passed.
|
||||||
|
// A Reset or PlayerRespawn will clear this flag if needed for a new cycle.
|
||||||
|
if (m_spawnData.waveActive || m_spawnData.wavesSpawnedCount > 0) {
|
||||||
|
_initialWaveCheckPassedThisActivation = true;
|
||||||
|
} else {
|
||||||
|
_initialWaveCheckPassedThisActivation = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAnyEnemyAlive()
|
||||||
|
{
|
||||||
|
return m_activeEnemies.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanSpawnMoreWaves()
|
||||||
|
{
|
||||||
|
if (m_spawnData == null) return false;
|
||||||
|
return m_maxWaveRespawns < 0 || m_spawnData.wavesSpawnedCount < m_maxWaveRespawns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExhausted()
|
||||||
|
{
|
||||||
|
return !CanSpawnMoreWaves() && !IsAnyEnemyAlive();
|
||||||
|
}
|
||||||
|
public int GetWavesSpawnedCount()
|
||||||
{
|
{
|
||||||
if (m_respawnOnPlayersDeath && Player.Instance != null)
|
if (m_spawnData != null)
|
||||||
{
|
{
|
||||||
var respawner = Player.Instance.GetComponent<Respawner>();
|
return m_spawnData.wavesSpawnedCount;
|
||||||
respawner.m_onRespawned.RemoveListener(OnPlayerRespawned);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_spawnedAI)
|
|
||||||
{
|
|
||||||
m_spawnedAI.onDead.RemoveListener(OnDead);
|
|
||||||
}
|
}
|
||||||
|
return 0; // Should ideally not happen if Awake ran
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Start()
|
public void SpawnWave()
|
||||||
{
|
{
|
||||||
|
if (m_prefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"EnemySpawner ({name}): m_prefab is not set!", this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
base.Start();
|
if (!CanSpawnMoreWaves())
|
||||||
if (Player.Instance != null)
|
{
|
||||||
m_distanceFrom = Player.Instance.transform;
|
// This check is also in CheckSpawn/OnPlayerRespawned, but good for direct calls too
|
||||||
else
|
// Debug.Log($"EnemySpawner ({name}): Max wave respawn limit reached or cannot spawn more waves.", this);
|
||||||
{
|
return;
|
||||||
m_distanceFrom = Camera.main.transform;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (m_respawnOnPlayersDeath)
|
if (m_spawnData.waveActive)
|
||||||
{
|
{
|
||||||
var respawner = Player.Instance.GetComponent<Respawner>();
|
// Debug.Log($"EnemySpawner ({name}): Wave already active, not spawning another.", this);
|
||||||
respawner.m_onRespawned.AddListener(OnPlayerRespawned);
|
return;
|
||||||
}
|
}
|
||||||
InvokeRepeating("CheckSpawn", m_spawnCheckInterval + (Random.value * m_spawnCheckInterval), m_spawnCheckInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CheckRespawnCondition()
|
int enemiesSuccessfullySpawned = 0;
|
||||||
{
|
for (int i = 0; i < m_enemiesPerSpawnWave; i++)
|
||||||
if ((m_numToSpawn < 0 || m_numToSpawn > m_spawnData.numSpawned) && m_spawnedAI == null )
|
{
|
||||||
m_spawnData.spawned = false;
|
Vector3 spawnPosition = Vector3.zero;
|
||||||
|
bool foundValidPosition = false;
|
||||||
|
|
||||||
}
|
for (int attempt = 0; attempt < m_maxSpawnAttemptsPerEnemy; attempt++)
|
||||||
private void OnPlayerRespawned()
|
{
|
||||||
{
|
Vector2 randomCircleOffset = Random.insideUnitCircle * m_spawnRadius;
|
||||||
CheckRespawnCondition();
|
Vector3 candidateBasePosition = transform.position + new Vector3(randomCircleOffset.x, 0, randomCircleOffset.y);
|
||||||
}
|
Vector3 raycastStartPos = candidateBasePosition + Vector3.up * RAY_Y_OFFSET;
|
||||||
|
|
||||||
public override string RecordData()
|
RaycastHit hit;
|
||||||
{
|
if (Physics.Raycast(raycastStartPos, Vector3.down, out hit, RAY_Y_OFFSET * 2, raycastMask))
|
||||||
return SaveSystem.Serialize(m_spawnData);
|
{
|
||||||
}
|
Vector3 potentialSpawnPoint = hit.point + Vector3.up * GROUND_Y_OFFSET;
|
||||||
|
NavMeshHit navHit;
|
||||||
|
if (NavMesh.SamplePosition(potentialSpawnPoint, out navHit, m_navMeshSampleDistance, NavMesh.AllAreas))
|
||||||
|
{
|
||||||
|
spawnPosition = navHit.position;
|
||||||
|
foundValidPosition = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void ApplyData(string s)
|
if (foundValidPosition)
|
||||||
{
|
{
|
||||||
var data = SaveSystem.Deserialize<SaveData>(s);
|
GameObject enemyGO = Instantiate(m_prefab, spawnPosition, transform.rotation, transform);
|
||||||
if (data != null)
|
vControlAI spawnedAI = enemyGO.GetComponent<vControlAI>();
|
||||||
{
|
if (spawnedAI != null)
|
||||||
m_spawnData = data;
|
{
|
||||||
//m_spawned = true;
|
m_activeEnemies.Add(spawnedAI);
|
||||||
CheckRespawnCondition();
|
spawnedAI.onDead.AddListener(OnEnemyDead);
|
||||||
}
|
spawnedAI.onReceiveDamage.AddListener(OnEnemyReceiveDamage);
|
||||||
}
|
enemiesSuccessfullySpawned++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError($"EnemySpawner ({name}): Spawned prefab '{m_prefab.name}' does not have a vControlAI component!", enemyGO);
|
||||||
|
Destroy(enemyGO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"EnemySpawner ({name}): Failed to find a valid spawn position for an enemy after {m_maxSpawnAttemptsPerEnemy} attempts.", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool SpawnedAndLive()
|
if (enemiesSuccessfullySpawned > 0)
|
||||||
{
|
{
|
||||||
return m_spawnedAI != null;
|
m_spawnData.waveActive = true;
|
||||||
}
|
m_spawnData.wavesSpawnedCount++;
|
||||||
|
Debug.Log($"EnemySpawner ({name}): Spawned wave {m_spawnData.wavesSpawnedCount}/{(m_maxWaveRespawns < 0 ? "infinite" : m_maxWaveRespawns.ToString())} with {enemiesSuccessfullySpawned} enemies.", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No enemies were spawned in this attempt, so the wave isn't truly "active".
|
||||||
|
// And we shouldn't count it towards wavesSpawnedCount if nothing came out.
|
||||||
|
m_spawnData.waveActive = false;
|
||||||
|
Debug.LogWarning($"EnemySpawner ({name}): Attempted to spawn a wave, but no enemies were successfully placed.", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool SpawnedAndDead()
|
private void OnEnemyReceiveDamage(vDamage damageData) // Assuming vDamage from Invector
|
||||||
{
|
{
|
||||||
return m_spawnData.spawned && m_spawnedAI == null;
|
m_onReceivedDamage?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Spawn()
|
private void OnEnemyDead(GameObject deadEnemyObject)
|
||||||
{
|
{
|
||||||
if (m_prefab == null)
|
vControlAI deadAI = deadEnemyObject.GetComponent<vControlAI>();
|
||||||
return;
|
if (deadAI != null && m_activeEnemies.Contains(deadAI))
|
||||||
|
{
|
||||||
|
deadAI.onDead.RemoveListener(OnEnemyDead);
|
||||||
|
deadAI.onReceiveDamage.RemoveListener(OnEnemyReceiveDamage);
|
||||||
|
m_activeEnemies.Remove(deadAI);
|
||||||
|
m_OnDead?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
Vector3 pos;
|
if (m_activeEnemies.Count == 0 && m_spawnData.waveActive)
|
||||||
const float RAY_OFFSET = 30f;
|
{
|
||||||
const float Y_OFFSET = .1f;
|
m_spawnData.waveActive = false; // Mark wave as inactive
|
||||||
pos = transform.position;
|
// DO NOT call CheckSpawn() here to prevent immediate respawn.
|
||||||
pos.y += RAY_OFFSET;
|
// DO NOT reset _initialWaveCheckPassedThisActivation here. That's for new cycles (player respawn/reset).
|
||||||
RaycastHit hit = new RaycastHit();
|
Debug.Log($"EnemySpawner ({name}): All enemies in wave died. Wave inactive. Waiting for player respawn or manual reset.", this);
|
||||||
if (Physics.Raycast(pos, Vector3.down, out hit, 100f, raycastMask))
|
}
|
||||||
{
|
}
|
||||||
pos.y -= RAY_OFFSET;
|
|
||||||
if (pos.y < hit.point.y + Y_OFFSET)
|
|
||||||
{
|
|
||||||
pos.y = hit.point.y + Y_OFFSET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogError("EnemySpawner error: raycast failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//m_spawned = true;
|
private bool IsPlayerInRange()
|
||||||
m_spawnData.spawned = true;
|
{
|
||||||
m_spawnData.numSpawned++;
|
if (m_distanceFrom == null) // Should be set in Start, but as a fallback
|
||||||
var enemy = Instantiate(m_prefab, pos, transform.rotation, transform);
|
{
|
||||||
m_spawnedAI = enemy.GetComponent<vControlAI>();
|
if (Player.Instance != null) m_distanceFrom = Player.Instance.transform;
|
||||||
if (m_spawnedAI)
|
else if (Camera.main != null) m_distanceFrom = Camera.main.transform;
|
||||||
{
|
else
|
||||||
m_spawnedAI.onDead.AddListener(OnDead);
|
{
|
||||||
m_spawnedAI.onReceiveDamage.AddListener(OnReceiveDamage);
|
// Debug.LogWarning($"EnemySpawner ({name}): Cannot check range, m_distanceFrom is null.", this);
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (transform.position - m_distanceFrom.position).sqrMagnitude < m_distance * m_distance;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
public void CheckSpawn() // Called by InvokeRepeating
|
||||||
|
{
|
||||||
|
if (!enabled || !gameObject.activeInHierarchy || m_prefab == null) return;
|
||||||
|
|
||||||
private void OnReceiveDamage(vDamage arg0)
|
// If the "initial" spawn for this activation cycle has already happened (or attempted),
|
||||||
{
|
// CheckSpawn should not try again until the cycle is reset (by player death or ResetSpawner).
|
||||||
m_onReceivedDamage?.Invoke(this);
|
if (_initialWaveCheckPassedThisActivation)
|
||||||
}
|
{
|
||||||
|
// If a wave is active, great, CheckSpawn's job for this cycle is done.
|
||||||
|
// If no wave is active BUT wavesSpawnedCount > 0, it means the wave spawned by this cycle died.
|
||||||
|
// In this case, CheckSpawn also shouldn't immediately respawn.
|
||||||
|
// It waits for OnPlayerRespawned or ResetSpawner to clear the flag.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDead(GameObject arg0)
|
// Try to spawn if: no wave active, can spawn more, player in range, AND initial check hasn't passed.
|
||||||
{
|
if (!m_spawnData.waveActive && CanSpawnMoreWaves() && IsPlayerInRange())
|
||||||
m_spawnedAI = null;
|
{
|
||||||
m_OnDead?.Invoke(this);
|
SpawnWave();
|
||||||
}
|
if (m_spawnData.waveActive) // If wave was successfully spawned
|
||||||
|
{
|
||||||
|
_initialWaveCheckPassedThisActivation = true; // Mark as done for this cycle
|
||||||
|
}
|
||||||
|
// If SpawnWave failed to actually spawn anyone (e.g. no valid spots),
|
||||||
|
// waveActive will be false, and _initialWaveCheckPassedThisActivation will remain false,
|
||||||
|
// allowing CheckSpawn to try again on the next interval.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void CheckSpawn(){
|
public void ResetSpawner()
|
||||||
if (!m_spawnData.spawned && gameObject.activeInHierarchy && (transform.position - m_distanceFrom.position).sqrMagnitude < m_distance * m_distance){
|
{
|
||||||
Spawn();
|
CancelInvoke("CheckSpawn"); // Stop existing repeating call
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Destroy active enemies
|
||||||
|
for (int i = m_activeEnemies.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (m_activeEnemies[i] != null)
|
||||||
|
{
|
||||||
|
m_activeEnemies[i].onDead.RemoveListener(OnEnemyDead);
|
||||||
|
m_activeEnemies[i].onReceiveDamage.RemoveListener(OnEnemyReceiveDamage);
|
||||||
|
Destroy(m_activeEnemies[i].gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_activeEnemies.Clear();
|
||||||
|
|
||||||
|
// Reset spawn data
|
||||||
|
if (m_spawnData != null)
|
||||||
|
{
|
||||||
|
m_spawnData.wavesSpawnedCount = 0;
|
||||||
|
m_spawnData.waveActive = false;
|
||||||
|
}
|
||||||
|
else // Should not happen if Awake ran
|
||||||
|
{
|
||||||
|
m_spawnData = new SaveData { wavesSpawnedCount = 0, waveActive = false };
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialWaveCheckPassedThisActivation = false; // Reset the flag for a new cycle
|
||||||
|
|
||||||
|
// Restart InvokeRepeating if spawner is active
|
||||||
|
if (enabled && gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
// Re-initialize m_distanceFrom
|
||||||
|
if (Player.Instance != null) m_distanceFrom = Player.Instance.transform;
|
||||||
|
else if (Camera.main != null) m_distanceFrom = Camera.main.transform;
|
||||||
|
|
||||||
|
InvokeRepeating("CheckSpawn", m_spawnCheckInterval + (Random.value * m_spawnCheckInterval), m_spawnCheckInterval);
|
||||||
|
Debug.Log($"Spawner ({name}) has been reset. CheckSpawn restarted. Will attempt to spawn when conditions met.", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Log($"Spawner ({name}) has been reset but is currently disabled or inactive. CheckSpawn not restarted.", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
161
Assets/Scripts/Characters/EnemySpawnerManager.cs
Normal file
161
Assets/Scripts/Characters/EnemySpawnerManager.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Sirenix.OdinInspector;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Beyond
|
||||||
|
{
|
||||||
|
public class EnemySpawnerManager : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Spawner Configurations")]
|
||||||
|
[Tooltip("List of spawners to manage and their override settings.")]
|
||||||
|
public List<SpawnerOverrideConfig> spawnerConfigurations = new List<SpawnerOverrideConfig>();
|
||||||
|
|
||||||
|
[Header("Global Settings (Optional)")]
|
||||||
|
[Tooltip("If assigned, this prefab will be used for ALL spawners in the list that don't have their own overridePrefab set.")]
|
||||||
|
public GameObject globalOverridePrefab;
|
||||||
|
|
||||||
|
[Tooltip("If > 0, this will be used for ALL spawners that don't have overrideEnemiesPerWave set. Set to 0 or less to ignore.")]
|
||||||
|
public int globalOverrideEnemiesPerWave = -1;
|
||||||
|
public bool overrideOnStart = true;
|
||||||
|
|
||||||
|
public bool autoEnableSpawners = true;
|
||||||
|
public bool disableSpawnersOnStart = true;
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
if (disableSpawnersOnStart)
|
||||||
|
{
|
||||||
|
// Disable all spawners initially if needed
|
||||||
|
foreach (var config in spawnerConfigurations)
|
||||||
|
{
|
||||||
|
if (config.spawner != null)
|
||||||
|
{
|
||||||
|
config.spawner.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplySpawnerConfigurations();
|
||||||
|
}
|
||||||
|
|
||||||
|
// You could also call this from Start() if you want to ensure all other Awakes have run,
|
||||||
|
// though for setting properties on other components, Awake() is generally fine.
|
||||||
|
// void Start()
|
||||||
|
// {
|
||||||
|
// ApplySpawnerConfigurations();
|
||||||
|
// }
|
||||||
|
[Button]
|
||||||
|
public void ApplySpawnerConfigurations()
|
||||||
|
{
|
||||||
|
if (spawnerConfigurations == null || spawnerConfigurations.Count == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("EnemySpawnerManager: No spawner configurations assigned.", this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (SpawnerOverrideConfig config in spawnerConfigurations)
|
||||||
|
{
|
||||||
|
if (config == null || config.spawner == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("EnemySpawnerManager: A spawner configuration or its target spawner is null. Skipping.", this);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemySpawner targetSpawner = config.spawner;
|
||||||
|
|
||||||
|
// Apply specific override prefab if set
|
||||||
|
if (config.overridePrefab != null)
|
||||||
|
{
|
||||||
|
targetSpawner.m_prefab = config.overridePrefab;
|
||||||
|
// Debug.Log($"Manager overriding prefab for {targetSpawner.name} to {config.overridePrefab.name}", this);
|
||||||
|
}
|
||||||
|
// Else, if a global override prefab is set, apply that
|
||||||
|
else if (globalOverridePrefab != null)
|
||||||
|
{
|
||||||
|
targetSpawner.m_prefab = globalOverridePrefab;
|
||||||
|
// Debug.Log($"Manager applying global override prefab to {targetSpawner.name} ({globalOverridePrefab.name})", this);
|
||||||
|
}
|
||||||
|
// If neither specific nor global override is set, the spawner uses its own m_prefab.
|
||||||
|
|
||||||
|
// Apply specific override for enemies per wave if set (and > 0)
|
||||||
|
if (config.overrideEnemiesPerWave > -1)
|
||||||
|
{
|
||||||
|
targetSpawner.m_enemiesPerSpawnWave = config.overrideEnemiesPerWave;
|
||||||
|
// Debug.Log($"Manager overriding enemies per wave for {targetSpawner.name} to {config.overrideEnemiesPerWave}", this);
|
||||||
|
}
|
||||||
|
// Else, if a global override for enemies per wave is set (and > 0), apply that
|
||||||
|
else if (globalOverrideEnemiesPerWave > -1)
|
||||||
|
{
|
||||||
|
targetSpawner.m_enemiesPerSpawnWave = globalOverrideEnemiesPerWave;
|
||||||
|
// Debug.Log($"Manager applying global override enemies per wave to {targetSpawner.name} ({globalOverrideEnemiesPerWave})", this);
|
||||||
|
}
|
||||||
|
if (autoEnableSpawners)
|
||||||
|
{
|
||||||
|
targetSpawner.enabled = true; // Enable the spawner if it was disabled
|
||||||
|
}
|
||||||
|
// If neither specific nor global override is set, the spawner uses its own m_enemiesPerSpawnWave.
|
||||||
|
|
||||||
|
|
||||||
|
// --- IMPORTANT ---
|
||||||
|
// Ensure the spawner hasn't already started its InvokeRepeating for CheckSpawn
|
||||||
|
// if its settings are being changed.
|
||||||
|
// One way is to disable the spawner initially and let the manager enable it.
|
||||||
|
// Or, if spawners might already be active, you might need to CancelInvoke and Restart it.
|
||||||
|
// For simplicity, let's assume spawners are either configured to not auto-start
|
||||||
|
// or this manager runs early enough (e.g., via Script Execution Order).
|
||||||
|
|
||||||
|
// If you want to ensure spawners don't start before manager configures them:
|
||||||
|
// Option A: Set spawners to be disabled by default in the editor, then enable them here.
|
||||||
|
// targetSpawner.enabled = true; // If they were disabled by default
|
||||||
|
|
||||||
|
// Option B: Make sure EnemySpawnerManager's Awake runs before EnemySpawner's Start.
|
||||||
|
// You can do this via Edit > Project Settings > Script Execution Order.
|
||||||
|
// Add EnemySpawnerManager and set it to an earlier value (e.g., -100)
|
||||||
|
// than the default time or EnemySpawner.
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("EnemySpawnerManager: Applied configurations to spawners.", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Helper to add spawners programmatically if needed
|
||||||
|
public void AddSpawnerToManage(EnemySpawner spawner, GameObject prefabOverride = null, int enemiesPerWaveOverride = 0)
|
||||||
|
{
|
||||||
|
if (spawner == null) return;
|
||||||
|
|
||||||
|
SpawnerOverrideConfig newConfig = new SpawnerOverrideConfig
|
||||||
|
{
|
||||||
|
spawner = spawner,
|
||||||
|
overridePrefab = prefabOverride,
|
||||||
|
overrideEnemiesPerWave = enemiesPerWaveOverride
|
||||||
|
};
|
||||||
|
spawnerConfigurations.Add(newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets all managed spawners to their initial state.
|
||||||
|
/// This will destroy their current enemies and restart their spawning cycles.
|
||||||
|
/// </summary>
|
||||||
|
[ContextMenu("Reset All Managed Spawners")] // Adds a right-click option in Inspector for easy testing
|
||||||
|
[Button]
|
||||||
|
public void ResetAllManagedSpawners()
|
||||||
|
{
|
||||||
|
if (spawnerConfigurations == null || spawnerConfigurations.Count == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("EnemySpawnerManager: No spawners configured to reset.", this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("EnemySpawnerManager: Initiating reset for all managed spawners...", this);
|
||||||
|
int resetCount = 0;
|
||||||
|
foreach (SpawnerOverrideConfig config in spawnerConfigurations)
|
||||||
|
{
|
||||||
|
if (config != null && config.spawner != null)
|
||||||
|
{
|
||||||
|
config.spawner.ResetSpawner(); // Call the spawner's own reset method
|
||||||
|
resetCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug.Log($"EnemySpawnerManager: Reset command sent to {resetCount} spawners.", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/Characters/EnemySpawnerManager.cs.meta
Normal file
2
Assets/Scripts/Characters/EnemySpawnerManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6ce0b24e87d404b299a748ccc1671160
|
||||||
@@ -22,9 +22,13 @@ namespace Beyond
|
|||||||
private SaveData m_data = new SaveData();
|
private SaveData m_data = new SaveData();
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
|
if (m_spawners == null) return; // Guard clause
|
||||||
foreach (var s in m_spawners)
|
foreach (var s in m_spawners)
|
||||||
{
|
{
|
||||||
s.m_OnDead.AddListener(OnEnemyKilled);
|
if (s != null) // Good practice to check for null spawners in array
|
||||||
|
{
|
||||||
|
s.m_OnDead.AddListener(OnEnemyKilled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,24 +36,44 @@ namespace Beyond
|
|||||||
{
|
{
|
||||||
if (m_data.wasTriggered)
|
if (m_data.wasTriggered)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (m_spawners == null) return; // Guard clause
|
||||||
|
|
||||||
foreach (var spawner in m_spawners)
|
foreach (var spawner in m_spawners)
|
||||||
{
|
{
|
||||||
if (!spawner.SpawnedAndDead())
|
if (spawner == null) continue; // Skip if a spawner in the array is null
|
||||||
return;
|
|
||||||
|
bool hasSpawnedAWave = spawner.GetWavesSpawnedCount() > 0; // We'll need to add this getter to EnemySpawner
|
||||||
|
bool noEnemiesCurrentlyAlive = !spawner.IsAnyEnemyAlive();
|
||||||
|
|
||||||
|
if (hasSpawnedAWave && noEnemiesCurrentlyAlive)
|
||||||
|
{
|
||||||
|
// This spawner has done its part for *this current engagement*
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return; // If ANY spawner doesn't meet this, the trigger condition isn't met
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OnTrigger?.Invoke(this);
|
OnTrigger?.Invoke(this);
|
||||||
m_data.wasTriggered = true;
|
m_data.wasTriggered = true;
|
||||||
}
|
}
|
||||||
private void OnEnemyKilled(EnemySpawner arg0)
|
private void OnEnemyKilled(EnemySpawner arg0)
|
||||||
{
|
{
|
||||||
|
// arg0 is the spawner from which an enemy died.
|
||||||
|
// We need to check all spawners associated with this trigger.
|
||||||
CheckSpawners();
|
CheckSpawners();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (m_spawners == null) return; // Guard clause
|
||||||
foreach (var s in m_spawners)
|
foreach (var s in m_spawners)
|
||||||
{
|
{
|
||||||
s.m_OnDead.RemoveListener(OnEnemyKilled);
|
if (s != null) // Good practice
|
||||||
|
{
|
||||||
|
s.m_OnDead.RemoveListener(OnEnemyKilled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +87,8 @@ namespace Beyond
|
|||||||
var data = SaveSystem.Deserialize<SaveData>(s);
|
var data = SaveSystem.Deserialize<SaveData>(s);
|
||||||
if (data != null)
|
if (data != null)
|
||||||
m_data = data;
|
m_data = data;
|
||||||
|
// Optional: if not triggered, re-check on load in case state was met while inactive
|
||||||
|
// if (!m_data.wasTriggered) { CheckSpawners(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
21
Assets/Scripts/Characters/SpawnerOverrideConfig.cs
Normal file
21
Assets/Scripts/Characters/SpawnerOverrideConfig.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// This can be in its own file (e.g., SpawnerConfig.cs) or inside the EnemySpawnerManager.cs file if you prefer.
|
||||||
|
// For better organization, a separate file is often good.
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Beyond
|
||||||
|
{
|
||||||
|
[System.Serializable] // Makes it show up in the Inspector when used in a list
|
||||||
|
public class SpawnerOverrideConfig
|
||||||
|
{
|
||||||
|
public EnemySpawner spawner; // Reference to the EnemySpawner instance in the scene
|
||||||
|
|
||||||
|
[Tooltip("If assigned, this prefab will override the spawner's own m_prefab.")]
|
||||||
|
public GameObject overridePrefab;
|
||||||
|
|
||||||
|
[Tooltip("If > -1, this will override the spawner's m_enemiesPerSpawnWave. Set to 0 or less to use spawner's default.")]
|
||||||
|
public int overrideEnemiesPerWave = -1;
|
||||||
|
|
||||||
|
// You could add more overrides here if needed, e.g., overrideSpawnRadius, overrideMaxWaveRespawns, etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/Characters/SpawnerOverrideConfig.cs.meta
Normal file
2
Assets/Scripts/Characters/SpawnerOverrideConfig.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 40b3ccfac6d0f46f0bb77671c0057209
|
||||||
Reference in New Issue
Block a user