using System; using System.Collections.Generic; using Drawing; using Pathfinding; using Sirenix.OdinInspector; using UnityEngine; using Random = UnityEngine.Random; namespace Reset.Units{ public class EnemySpawn : MonoBehaviour{ enum SpawnerState{ Idle = 0, TargetAcquired, TargetDropped, InCombatUnlocked, InCombatLocked, Cooldown, } public float radius = 30f; public int minimumEnemies = 1; public int maximumEnemies = 5; public Vector2 enemyCount; // TODO: Replace this with an Enemy selector based on difficulty, random chance, etc? public GameObject enemy; public List enemies; public GridGraph relatedGraph; private SpawnerState spawnerState; private float timeInState; void Start(){ CreateAstarGraph(); SpawnEnemies(); } void CreateAstarGraph(){ relatedGraph = AstarPath.active.data.AddGraph(typeof(GridGraph)) as GridGraph; relatedGraph.SetDimensions(Mathf.FloorToInt(radius) * 2, Mathf.FloorToInt(radius) * 2, 1f); relatedGraph.collision.diameter = 3f; AstarPath.active.Scan(relatedGraph); GetComponent().graph = relatedGraph; } void SpawnEnemies(){ int count = Random.Range(minimumEnemies, maximumEnemies + 1); for (int i = 0; i < count; i++) { Vector3 newPosition = transform.position; float randomX = Random.Range(-(radius / 2f), radius / 2f); float randomZ = Random.Range(-(radius / 2f), radius / 2f); newPosition += new Vector3(randomX, transform.position.y, randomZ); float randomRot = Random.Range(0f, 360f); GameObject newEnemy = Instantiate(enemy, newPosition, Quaternion.AngleAxis(randomRot, Vector3.up)); newEnemy.GetComponent().relatedSpawner = this; enemies.Add(newEnemy); } } void Update(){ ProcessStateUpdate(); DrawSpawnStateGizmos(); } // Set the new state of the spawner and also reset the timer for state management void ChangeState(SpawnerState newState){ spawnerState = newState; ProcessStateStart(); timeInState = 0f; } // Does initial actions on state switch void ProcessStateStart(){ switch (spawnerState) { case SpawnerState.TargetDropped: foreach (GameObject thisEnemy in enemies) { thisEnemy.GetComponent().DropTarget(); } break; case SpawnerState.InCombatUnlocked: foreach (GameObject thisEnemy in enemies) { thisEnemy.GetComponent().Graph.SendEvent("Enter Combat"); } break; } } // Actively processes the state of the spawner and its assocaited enemies void ProcessStateUpdate(){ switch (spawnerState) { // Check if player is in range of this spawner. If so, set them as the new target case SpawnerState.Idle: if (PlayerIsInRange(radius, radius / 2f)) { foreach (GameObject thisEnemy in enemies) { thisEnemy.GetComponent().SetNewTarget(PlayerManager.Player); } ChangeState(SpawnerState.TargetAcquired); } break; // After grabbing a player in range, make sure they stay in range. If they aren't in range after 5 seconds, disengage. If they are or get too close, engage. case SpawnerState.TargetAcquired: if (PlayerIsInRange(8f, 8f) && timeInState > 1f) { ChangeState(SpawnerState.InCombatUnlocked); return; } if (timeInState > 5f) { if (PlayerIsInRange(radius + 2f, radius * .6f)) { ChangeState(SpawnerState.InCombatUnlocked); return; } ChangeState(SpawnerState.TargetDropped); return; } break; // Wait a few seconds after dropping the target before doing anything else case SpawnerState.TargetDropped: if (timeInState > 3f) { ChangeState(SpawnerState.Idle); return; } break; // Set all units to combat. Drop combat if the target gets too far case SpawnerState.InCombatUnlocked: if (!PlayerIsInRange(radius * 1.5f, radius * 1.5f) && timeInState > 5f) { ChangeState(SpawnerState.TargetDropped); return; } break; // Lock the player to the spawn. Currently unused. case SpawnerState.InCombatLocked: break; // Long cooldown for after all units have been killed. Currently unused. case SpawnerState.Cooldown: break; } // Increment state timer timeInState += 1f * Time.deltaTime; } GameObject PlayerIsInRange(float spawnerRange, float enemyRange){ // TODO: Make compatible with all players if (!PlayerManager.Player) { return null; } Vector3 playerPos = PlayerManager.Player.transform.position; // Skip checking and return null/false if the player is nowhere near the input range if (Vector3.Distance(playerPos, transform.position) > spawnerRange * 2f) { return null; } // If they are in range, check if the player is close enough to either an enemy or the input range if (Vector3.Distance(playerPos, transform.position) < spawnerRange) { return PlayerManager.Player; } foreach (GameObject thisEnemy in enemies) { if (Vector3.Distance(playerPos, thisEnemy.transform.position) < enemyRange) { return PlayerManager.Player; } } return null; } private void DrawSpawnStateGizmos(){ Color cylinderColor = Color.blue; switch (spawnerState) { case SpawnerState.Idle: break; case SpawnerState.TargetAcquired: cylinderColor = Color.coral; break; case SpawnerState.TargetDropped: cylinderColor = Color.khaki; break; case SpawnerState.InCombatUnlocked: cylinderColor = Color.red; break; case SpawnerState.InCombatLocked: cylinderColor = Color.violetRed; break; case SpawnerState.Cooldown: cylinderColor = Color.gray; break; } Draw.WireCylinder(transform.position, transform.position + Vector3.up * 7f, radius, cylinderColor); } } }