diff --git a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverAction.cs b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverAction.cs deleted file mode 100644 index 9498123..0000000 --- a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverAction.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public class GoToCoverAction : Action -{ - -} diff --git a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverAction.cs.meta b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverAction.cs.meta deleted file mode 100644 index 4e71fe6..0000000 --- a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverAction.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8f017c8d0c9712f448b04539911d0497 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverLocation.asset b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverLocation.asset new file mode 100644 index 0000000..df2531e --- /dev/null +++ b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverLocation.asset @@ -0,0 +1,14 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8f017c8d0c9712f448b04539911d0497, type: 3} + m_Name: GoToCoverLocation + m_EditorClassIdentifier: diff --git a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverLocation.asset.meta b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverLocation.asset.meta new file mode 100644 index 0000000..399d9b2 --- /dev/null +++ b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToCoverLocation.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ec862de62fb0bb340aa50877b8093ccc +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToLocationCoverAction.cs b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToLocationCoverAction.cs new file mode 100644 index 0000000..0374d96 --- /dev/null +++ b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToLocationCoverAction.cs @@ -0,0 +1,60 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +[CreateAssetMenu(menuName = "Finite State Machine/Action/Combat/Ranged/GoToCoverLocation")] +public class GoToLocationCoverAction : Action +{ + public override void Execute(FSM fsm) + { + GameObject target = fsm.Controller.AgentFOV.GetTarget(); + + Debug.Log($"Execute action: {nameof(GoToLocationCoverAction)}"); + + if (fsm.CurrentState() is RangedCombatState) + { + RangedCombatState state = (RangedCombatState)fsm.CurrentState(); + + if (state.coverObject != null) + { + Debug.Log($"Go to cover location: {state.coverObject}"); + + //Calculate the closest point of the cover spot closer to the NPC + Vector3 closestPointOnCover = state.coverObject.GetComponent().ClosestPoint(fsm.transform.position); + + //Check the direction from the cover to the target + Vector3 coverToPlayerDir = (target.transform.position - closestPointOnCover).normalized; + + //Check the direction from the cover to the NPC + Vector3 coverToNPCDir = (fsm.transform.position - closestPointOnCover).normalized; + + //Calculate final target position behind cover + float dotProduct = Vector3.Dot(coverToPlayerDir, coverToNPCDir); + + //Calculate the final cover position + Vector3 finalCoverPosition = closestPointOnCover; + + if (dotProduct > 0) + { + //Calculate the normal of the cover(using the normal of the closest point on the collider) + RaycastHit hit; + + if (Physics.Raycast(closestPointOnCover + Vector3.up, -coverToPlayerDir, out hit)) + { + finalCoverPosition = hit.point; + } + } + + //Set Agent cover destionation + fsm.Controller.Agent.SetDestination(finalCoverPosition); + } + + else + { + Debug.LogError($"There is no Cover Location!"); + } + } + } + + +} diff --git a/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToLocationCoverAction.cs.meta b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToLocationCoverAction.cs.meta new file mode 100644 index 0000000..4e71fe6 --- /dev/null +++ b/Assets/Scripts/NPC/FSM/Actions/Combat/Ranged/GoToLocationCoverAction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f017c8d0c9712f448b04539911d0497 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NPC/FSM/Conditions/Combat/TryToFindCoverLocation.cs b/Assets/Scripts/NPC/FSM/Conditions/Combat/TryToFindCoverLocation.cs index d9ab51c..14279b9 100644 --- a/Assets/Scripts/NPC/FSM/Conditions/Combat/TryToFindCoverLocation.cs +++ b/Assets/Scripts/NPC/FSM/Conditions/Combat/TryToFindCoverLocation.cs @@ -7,30 +7,87 @@ public class TryToFindCoverLocation : Condition { [SerializeField] private CheckTargetState targetState; - [SerializeField] private string tag; + [SerializeField] private string tag; //Target object tab + [SerializeField] private ushort numberOftries = 3; //Number of try checks to find a cover location public override bool CheckCondition(FSM fsm) { + Debug.Log("CheckCondition"); + //Checks if the target is on specific state if (targetState.CheckCondition(fsm)) { - return FindCover(fsm); + return FindSpot(fsm) ? true : false; } else return false; } - private bool FindCover(FSM fsm) + //tris to find a cover location for the NPC + private GameObject FindSpot(FSM fsm) { Awareness agentAware = fsm.Controller.AgentAwareness; + GameObject target = fsm.Controller.AgentFOV.GetTarget(); - if (agentAware != null) + if (agentAware != null && target != null) { + //Use the sensor agentAware.SetTargetTag(tag); agentAware.enabled = true; + + if (agentAware.TargetsList.Count > 0) + { + //Closest target + float closestTargetDistance = float.MaxValue; + GameObject coverObj = null; + + foreach (GameObject g in agentAware.TargetsList) + { + float distance = Vector3.Distance(fsm.transform.position, g.transform.position); + + if (distance < closestTargetDistance || distance <= 0f) + { + if (IsSpotProtected(target.transform, coverObj.transform)) + { + coverObj = g; + closestTargetDistance = distance; + } + } + } + + return coverObj; + } + + return null; - return agentAware.TargetsList.Count > 0; } - else return false; + return null; } -} + + //If there are any available spots check if the spot protects from the player + private bool IsSpotProtected(Transform target, Transform coverObject) + { + RaycastHit hit; + + //Checks if either target or cover object is not null + if (target != null && coverObject != null) + { + //Calculates the direction + Vector3 directionToPlayer = (target.position - coverObject.position).normalized; + + //Performs the raycast to check if the cover object will provide actual cover to the NPC + if (Physics.Raycast(coverObject.position, directionToPlayer, out hit)) + { + return hit.transform != target; + } + Debug.LogWarning($"Raycast failed for cover check."); + return false; + } + + else + { + Debug.LogWarning($"Missing: {nameof(target)} or {nameof(coverObject)}"); + return false; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/NPC/FSM/FSM.cs b/Assets/Scripts/NPC/FSM/FSM.cs index f2d30e9..ae5fab7 100644 --- a/Assets/Scripts/NPC/FSM/FSM.cs +++ b/Assets/Scripts/NPC/FSM/FSM.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using UnityEngine; public class FSM : MonoBehaviour @@ -63,6 +64,9 @@ currentState?.Enter(this); //Executes entry action } + //Returns the current state + public State CurrentState() => currentState; + //DEBUGING void OnGUI() @@ -71,15 +75,22 @@ float elementHeight = 20; // Height for each line float padding = 5; List actions = new List(); + List conditions = new List(); foreach (Action action in currentState.GetActions()) { actions.Add($"Action: {action.name}"); } + foreach (Transition transition in currentState.GetTransitions().ToList()) + { + conditions.Add($"Transition: {transition}\nCondition: {transition.GetCondition().name}: {transition.GetCondition()}"); + } + // Create a list to hold all the UI elements List uiElements = new List { $"State: {currentState.name}" }; uiElements.AddRange(actions); + uiElements.AddRange(conditions); // Define the GUIStyle GUIStyle style = new GUIStyle(); diff --git a/Assets/Scripts/NPC/FSM/State.cs b/Assets/Scripts/NPC/FSM/State.cs index 362a1d5..26a4f2c 100644 --- a/Assets/Scripts/NPC/FSM/State.cs +++ b/Assets/Scripts/NPC/FSM/State.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using UnityEngine; -[CreateAssetMenu(menuName = "Finite State Machine/State")] +[CreateAssetMenu(menuName = "Finite State Machine/State/Default")] public class State : ScriptableObject { //Variables @@ -17,7 +17,7 @@ public Action GetEntryAction() => entryAction; public Action[] GetActions() => actions; public Action ExitAction() => exitAction; - public Transition[] Transitions() => transitions; + public Transition[] GetTransitions() => transitions; public List SubStates() => subStates; //Runs the entry action of the currentState diff --git a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangeCombatState.asset b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangeCombatState.asset new file mode 100644 index 0000000..ee75ded --- /dev/null +++ b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangeCombatState.asset @@ -0,0 +1,21 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 816078cfc7889294f97d62f3c62fe9e4, type: 3} + m_Name: RangeCombatState + m_EditorClassIdentifier: + entryAction: {fileID: 0} + actions: [] + exitAction: {fileID: 0} + transitions: [] + initialSubState: {fileID: 0} + subStates: [] + coverObject: {fileID: 0} diff --git a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangeCombatState.asset.meta b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangeCombatState.asset.meta new file mode 100644 index 0000000..45f8251 --- /dev/null +++ b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangeCombatState.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 50ceb3e8ee758154eba7597e047a8fc5 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.asset b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.asset deleted file mode 100644 index c82c9fc..0000000 --- a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.asset +++ /dev/null @@ -1,22 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d0e7b5ba3c7cfc843bcdd88fcd76de11, type: 3} - m_Name: RangedCombatState - m_EditorClassIdentifier: - entryAction: {fileID: 11400000, guid: 0a0b9e6ef6cf3704c91720d616195100, type: 2} - actions: [] - exitAction: {fileID: 0} - transitions: [] - initialSubState: {fileID: 11400000, guid: 63273e20759d3cc4da713ccd0a9bafdc, type: 2} - subStates: - - {fileID: 11400000, guid: 1b3bae95f7aec1a478f358b25487cd61, type: 2} - - {fileID: 11400000, guid: 63273e20759d3cc4da713ccd0a9bafdc, type: 2} diff --git a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.asset.meta b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.asset.meta deleted file mode 100644 index 463a5fc..0000000 --- a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 329961279b6f8274291834391b47b0f4 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.cs b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.cs new file mode 100644 index 0000000..3c2a731 --- /dev/null +++ b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.cs @@ -0,0 +1,9 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +[CreateAssetMenu(menuName = "Finite State Machine/State/Combat")] +public class RangedCombatState : State +{ + public GameObject coverObject = null; +} diff --git a/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.cs.meta b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.cs.meta new file mode 100644 index 0000000..057cd68 --- /dev/null +++ b/Assets/Scripts/NPC/FSM/States/Alive/Combat/RangedCombat/RangedCombatState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 816078cfc7889294f97d62f3c62fe9e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/NPC/FSM/Transition.cs b/Assets/Scripts/NPC/FSM/Transition.cs index 75efe42..5c110d9 100644 --- a/Assets/Scripts/NPC/FSM/Transition.cs +++ b/Assets/Scripts/NPC/FSM/Transition.cs @@ -15,4 +15,6 @@ public State TargetState() => targetState; public Action GetAction() => action; + + public Condition GetCondition() => condition; } diff --git a/Assets/Scripts/NPC/Sensors/Awareness.cs b/Assets/Scripts/NPC/Sensors/Awareness.cs index 39a5336..bfcffd8 100644 --- a/Assets/Scripts/NPC/Sensors/Awareness.cs +++ b/Assets/Scripts/NPC/Sensors/Awareness.cs @@ -18,7 +18,15 @@ { get{ return enable; } - set { enable = value;} + set + { + if (enable == false) + { + targets.Clear(); + } + + enable = value; + } } public List TargetsList => targets; diff --git a/Assets/Scripts/Player/PlayerController.cs b/Assets/Scripts/Player/PlayerController.cs index 05a52f5..db0d0f5 100644 --- a/Assets/Scripts/Player/PlayerController.cs +++ b/Assets/Scripts/Player/PlayerController.cs @@ -21,7 +21,7 @@ private float turnVelocity; [Header("Player State")] - [SerializeField] private bool isArmed, isReloding, noAmmo; + [SerializeField] private bool isArmed, isAiming, isReloding, noAmmo; [SerializeField] private IState.TargetState playerState; private void Awake() @@ -39,6 +39,7 @@ // Update is called once per frame void Update() { + TestState(); IsCrouched(); ApplyRotation(); // Read input values once per frame @@ -150,4 +151,14 @@ public IState.TargetState GetState() => playerState; void IState.ChangeState(IState.TargetState state) => playerState = state; + + + //------------------------JUST FOR TESTING------------------------// + void TestState() + { + if (isAiming) + { + playerState = IState.TargetState.Aiming; + } + } } \ No newline at end of file