Files
project-reset/Assets/Scripts/Units/Enemy/EnemySpawn.cs

223 lines
7.8 KiB
C#

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<GameObject> 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<ProceduralGraphMover>().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<Enemy>().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<EnemyCombat>().DropTarget();
}
break;
case SpawnerState.InCombatUnlocked:
foreach (GameObject thisEnemy in enemies) {
thisEnemy.GetComponent<Enemy>().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<EnemyCombat>().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);
}
}
}