From ea048be111643ba496a0d8e1fcaa618836df4b1d Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 22 Nov 2025 14:15:58 -0500 Subject: [PATCH] change: enemy combat/detection state now handled by the spawner --- .../Enemies/Common/Graphs/BasicEnemyFSM.asset | 22 ++- Assets/Scripts/Units/Enemy/EnemyCombat.cs | 7 +- Assets/Scripts/Units/Enemy/EnemySpawn.cs | 155 ++++++++++++++++-- 3 files changed, 156 insertions(+), 28 deletions(-) diff --git a/Assets/Enemies/Common/Graphs/BasicEnemyFSM.asset b/Assets/Enemies/Common/Graphs/BasicEnemyFSM.asset index 4c6743e..bdebc00 100644 --- a/Assets/Enemies/Common/Graphs/BasicEnemyFSM.asset +++ b/Assets/Enemies/Common/Graphs/BasicEnemyFSM.asset @@ -14,18 +14,16 @@ MonoBehaviour: m_EditorClassIdentifier: NodeCanvas::NodeCanvas.StateMachines.FSM _serializedGraph: '{"type":"NodeCanvas.StateMachines.FSM","nodes":[{"_actionList":{"executionMode":1,"actions":[]},"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_position":{"x":522.1997,"y":608.5296},"$type":"NodeCanvas.StateMachines.ActionState","$id":"0"},{"_onEnterList":{"executionMode":1,"actions":[{"minValue":{"_value":2.0},"maxValue":{"_value":8.0},"floatVariable":{"_name":"_wanderTimer"},"$type":"NodeCanvas.Tasks.Actions.SetFloatRandom"}]},"_onUpdateList":{"executionMode":1,"actions":[]},"_onExitList":{"executionMode":1,"actions":[]},"foldEnter":true,"foldUpdate":true,"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_name":"Wander Stand-by","_position":{"x":83.0,"y":289.0},"$type":"NodeCanvas.StateMachines.SuperActionState","$id":"1"},{"_onEnterList":{"executionMode":1,"actions":[{"moveSpeed":{"label":"Move - Speed","changeValue":{"_value":1},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"acceleration":{"label":"Acceleration","changeValue":{},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"deacceleration":{"label":"Deacceleration","changeValue":{},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"$type":"Reset.Units.ChangeMoveSpeedSettings"},{"log":{"_value":"Detected - Player"},"$type":"NodeCanvas.Tasks.Actions.DebugLogText"},{"newSpeed":{},"absolute":{},"relativity":{},"$type":"Reset.Units.SetNewSpeed"},{"log":{"_value":"!?"},"secondsToRun":2.0,"verboseMode":2,"$type":"NodeCanvas.Tasks.Actions.DebugLogText"}]},"_onUpdateList":{"executionMode":1,"actions":[]},"_onExitList":{"executionMode":1,"actions":[]},"foldEnter":true,"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_name":"Detect","_position":{"x":486.012,"y":-14.01074},"$type":"NodeCanvas.StateMachines.SuperActionState","$id":"2"},{"_actionList":{"executionMode":1,"actions":[{"saveAs":{"_name":"_self"},"$type":"NodeCanvas.Tasks.Actions.GetSelf"},{"eventName":{"_value":"Start - Combat"},"eventValue":{"_name":"_self"},"delay":{},"$type":"NodeCanvas.Tasks.Actions.SendEvent`1[[UnityEngine.GameObject, - UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"}]},"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_name":"Signal - Spawnmates","_position":{"x":673.0,"y":-182.0},"$type":"NodeCanvas.StateMachines.ActionState","$id":"3"},{"_actionList":{"executionMode":1,"actions":[]},"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_name":"Enter - Combat","_position":{"x":1049.0,"y":32.0},"$type":"NodeCanvas.StateMachines.ActionState","$id":"4"},{"_nestedFSM":{"_value":1},"_position":{"x":1167.398,"y":424.1194},"$type":"NodeCanvas.StateMachines.NestedFSMState","$id":"5"},{"_name":"Check - for same spawner","_position":{"x":711.0,"y":193.0},"$type":"NodeCanvas.StateMachines.EmptyState","$id":"6"},{"_actionList":{"executionMode":1,"actions":[{"$type":"Reset.Units.SetWanderingPath"},{"minValue":{"_value":2.0},"maxValue":{"_value":7.0},"floatVariable":{"_name":"_newSpeed"},"$type":"NodeCanvas.Tasks.Actions.SetFloatRandom"},{"moveSpeed":{"label":"Move + Speed","changeValue":{"_value":1},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"acceleration":{"label":"Acceleration","changeValue":{},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"deacceleration":{"label":"Deacceleration","changeValue":{},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"$type":"Reset.Units.ChangeMoveSpeedSettings"},{"newSpeed":{},"absolute":{},"relativity":{},"$type":"Reset.Units.SetNewSpeed"},{"log":{"_value":"!?"},"secondsToRun":3.0,"verboseMode":2,"$type":"NodeCanvas.Tasks.Actions.DebugLogText"}]},"_onUpdateList":{"executionMode":1,"actions":[]},"_onExitList":{"executionMode":1,"actions":[]},"foldEnter":true,"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_name":"Detect","_position":{"x":486.012,"y":-16.01074},"$type":"NodeCanvas.StateMachines.SuperActionState","$id":"2"},{"_actionList":{"executionMode":1,"actions":[{"log":{"_value":"Entering + Combat!"},"$type":"NodeCanvas.Tasks.Actions.DebugLogText"}]},"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_name":"Enter + Combat","_position":{"x":756.0,"y":-110.0},"$type":"NodeCanvas.StateMachines.ActionState","$id":"3"},{"_nestedFSM":{"_value":1},"_name":"In + Combat","_position":{"x":966.3979,"y":378.1194},"$type":"NodeCanvas.StateMachines.NestedFSMState","$id":"4"},{"_actionList":{"executionMode":1,"actions":[{"$type":"Reset.Units.SetWanderingPath"},{"minValue":{"_value":2.0},"maxValue":{"_value":7.0},"floatVariable":{"_name":"_newSpeed"},"$type":"NodeCanvas.Tasks.Actions.SetFloatRandom"},{"moveSpeed":{"label":"Move Speed","changeValue":{"_value":1},"value":{"_name":"_newSpeed"},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"acceleration":{"label":"Acceleration","changeValue":{},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"deacceleration":{"label":"Deacceleration","changeValue":{},"value":{},"_changeSmoothing":{},"_smoothing":{},"_changeEasing":{},"_easing":{}},"$type":"Reset.Units.ChangeMoveSpeedSettings"},{"newSpeed":{"_name":"_newSpeed"},"absolute":{},"relativity":{},"$type":"Reset.Units.SetNewSpeed"}]},"_color":{"r":1.0,"g":0.42,"b":0.32,"a":1.0},"_name":"Get - New Wandering Path","_position":{"x":-134.0,"y":112.0},"$type":"NodeCanvas.StateMachines.ActionState","$id":"7"}],"connections":[{"_sourceNode":{"$ref":"0"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"eventName":{"_value":"New - Target Set"},"$type":"NodeCanvas.Tasks.Conditions.CheckEvent"},"_sourceNode":{"$ref":"1"},"_targetNode":{"$ref":"2"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"timeout":{"_name":"_wanderTimer"},"$type":"NodeCanvas.Tasks.Conditions.Timeout"},"_sourceNode":{"$ref":"1"},"_targetNode":{"$ref":"7"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"timeout":{"_value":6.0},"$type":"NodeCanvas.Tasks.Conditions.Timeout"},"_sourceNode":{"$ref":"2"},"_targetNode":{"$ref":"3"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"eventName":{"_value":"Start - Combat"},"saveEventValue":{"_name":"_calledBy"},"$type":"NodeCanvas.Tasks.Conditions.CheckEvent`1[[UnityEngine.GameObject, - UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},"_sourceNode":{"$ref":"2"},"_targetNode":{"$ref":"6"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_sourceNode":{"$ref":"3"},"_targetNode":{"$ref":"4"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_sourceNode":{"$ref":"4"},"_targetNode":{"$ref":"5"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"target":{"_name":"_calledBy"},"$type":"Reset.Units.CheckIfSpawnmate"},"_sourceNode":{"$ref":"6"},"_targetNode":{"$ref":"4"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_sourceNode":{"$ref":"6"},"_targetNode":{"$ref":"2"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_sourceNode":{"$ref":"7"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.StateMachines.FSMConnection"}],"canvasGroups":[],"localBlackboard":{"_variables":{}}}' + New Wandering Path","_position":{"x":-134.0,"y":112.0},"$type":"NodeCanvas.StateMachines.ActionState","$id":"5"}],"connections":[{"_sourceNode":{"$ref":"0"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"eventName":{"_value":"New + Target Detected"},"$type":"NodeCanvas.Tasks.Conditions.CheckEvent"},"_sourceNode":{"$ref":"1"},"_targetNode":{"$ref":"2"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"timeout":{"_name":"_wanderTimer"},"$type":"NodeCanvas.Tasks.Conditions.Timeout"},"_sourceNode":{"$ref":"1"},"_targetNode":{"$ref":"5"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"eventName":{"_value":"Drop + Target"},"$type":"NodeCanvas.Tasks.Conditions.CheckEvent"},"_sourceNode":{"$ref":"2"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"eventName":{"_value":"Enter + Combat"},"$type":"NodeCanvas.Tasks.Conditions.CheckEvent"},"_sourceNode":{"$ref":"2"},"_targetNode":{"$ref":"3"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_sourceNode":{"$ref":"3"},"_targetNode":{"$ref":"4"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_condition":{"eventName":{"_value":"Drop + Target"},"$type":"NodeCanvas.Tasks.Conditions.CheckEvent"},"_sourceNode":{"$ref":"4"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.StateMachines.FSMConnection"},{"_sourceNode":{"$ref":"5"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.StateMachines.FSMConnection"}],"canvasGroups":[],"localBlackboard":{"_variables":{}}}' _objectReferences: - {fileID: 0} - {fileID: 11400000, guid: 66d780be76d914c40afd1b3f00df719f, type: 2} @@ -33,7 +31,7 @@ MonoBehaviour: _version: 3.31 _category: _comments: - _translation: {x: -7, y: 478} + _translation: {x: 178, y: 300} _zoomFactor: 1 _haltSerialization: 0 _externalSerializationFile: {fileID: 0} diff --git a/Assets/Scripts/Units/Enemy/EnemyCombat.cs b/Assets/Scripts/Units/Enemy/EnemyCombat.cs index a450aaf..3f33676 100644 --- a/Assets/Scripts/Units/Enemy/EnemyCombat.cs +++ b/Assets/Scripts/Units/Enemy/EnemyCombat.cs @@ -8,8 +8,13 @@ namespace Reset.Units{ [Button] public void SetNewTarget(GameObject newTarget){ - Unit.Graph.SendEvent("New Target Set"); + Unit.Graph.SendEvent("New Target Detected"); target = newTarget; } + + public void DropTarget(){ + target = null; + Unit.Graph.SendEvent("Drop Target"); + } } } diff --git a/Assets/Scripts/Units/Enemy/EnemySpawn.cs b/Assets/Scripts/Units/Enemy/EnemySpawn.cs index 3c48ea2..ebb91ce 100644 --- a/Assets/Scripts/Units/Enemy/EnemySpawn.cs +++ b/Assets/Scripts/Units/Enemy/EnemySpawn.cs @@ -1,11 +1,22 @@ +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; @@ -19,6 +30,9 @@ namespace Reset.Units{ public List enemies; public GridGraph relatedGraph; + + private SpawnerState spawnerState; + private float timeInState; void Start(){ CreateAstarGraph(); @@ -56,43 +70,154 @@ namespace Reset.Units{ } } - // Update is called once per frame void Update(){ - Draw.WireCylinder(transform.position, transform.position + Vector3.up * 7f, radius); + 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; + } - if (PlayerIsInRange()) { - SetPlayerAsTarget(); + + // 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; } } - GameObject PlayerIsInRange(){ - // TODO: Make compatible with all players - Vector3 playerPos = PlayerManager.Player.transform.position; + // 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(radius, 8f) && timeInState > 1f) { + ChangeState(SpawnerState.InCombatUnlocked); + return; + } + + if (timeInState > 5f) { + if (PlayerIsInRange(radius + 2f, radius * .6f)) { + ChangeState(SpawnerState.InCombatUnlocked); + return; + } - // Skip checking and return null/false if the player is nowhere near the spawn - if (Vector3.Distance(playerPos, transform.position) < radius * 1.5f) { + 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; } - // If they are in range, check if the player is close enough to either an enemy or the spawn center - if (Vector3.Distance(playerPos, transform.position) < radius * .33f) { + 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) < radius / 2f) { + if (Vector3.Distance(playerPos, thisEnemy.transform.position) < enemyRange) { return PlayerManager.Player; } } return null; } + + private void DrawSpawnStateGizmos(){ + Color cylinderColor = Color.blue; - void SetPlayerAsTarget(){ - foreach (GameObject thisEnemy in enemies) { - thisEnemy.GetComponent().SetNewTarget(PlayerManager.Player); + 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); } + } } \ No newline at end of file