You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1506 lines
61 KiB
C#
1506 lines
61 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
using MalbersAnimations.Events;
|
|
|
|
using UnityEngine.AI;
|
|
using MalbersAnimations.Scriptables;
|
|
using System.Collections.Generic;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.Serialization;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
|
|
namespace MalbersAnimations.Controller.AI
|
|
{
|
|
[AddComponentMenu("Malbers/Animal Controller/AI/AI Control")]
|
|
public class MAnimalAIControl : MonoBehaviour, IAIControl, IAITarget, IAnimatorListener
|
|
{
|
|
#region Components and References
|
|
/// <summary> Reference for the Agent</summary>
|
|
[SerializeField] private NavMeshAgent agent;
|
|
|
|
/// <summary> Reference for the Animal</summary>
|
|
[RequiredField] public MAnimal animal;
|
|
|
|
/// <summary>Cache if the Animal has an Input Source</summary>
|
|
public IInputSource InputSource { get; internal set; }
|
|
|
|
/// <summary>Cache if the Animal has an Interactor</summary>
|
|
public IInteractor Interactor { get; internal set; }
|
|
|
|
public bool ArriveLookAt => false; //do this later
|
|
|
|
public virtual bool Active => enabled && gameObject.activeInHierarchy;
|
|
|
|
#endregion
|
|
|
|
#region Internal Variables
|
|
/// <summary>Target Last Position (Useful to know if the Target is moving)</summary>
|
|
protected Vector3 TargetLastPosition;
|
|
|
|
/// <summary>Remaining Distance to the Destination Point</summary>
|
|
public virtual float RemainingDistance { get; set; }
|
|
//{
|
|
// get => m_RemainingDistance;
|
|
// set
|
|
// {
|
|
// m_RemainingDistance = value;
|
|
// if (debug) Debug.Log($"Remaining Distance = {m_RemainingDistance:F3}");
|
|
// }
|
|
//}
|
|
//float m_RemainingDistance;
|
|
|
|
|
|
/// <summary> Returns the Current Agent Remaining Distance </summary>
|
|
public virtual float AgentRemainingDistance => Agent.remainingDistance;
|
|
|
|
/// <summary>Store the Current Remaining Distance. This is used to slowdown the Animal when is circling around and it cannot arrive to the destination</summary>
|
|
public virtual float MinRemainingDistance { get; set; }
|
|
|
|
|
|
// public float CircleAroundMultiplier { get; private set; }
|
|
|
|
/// <summary>Used to Slow Down the Animal when its close the Destination</summary>
|
|
public float SlowMultiplier
|
|
{
|
|
get
|
|
{
|
|
var result = 1f;
|
|
if (CurrentSlowingDistance > CurrentStoppingDistance && RemainingDistance < CurrentSlowingDistance)
|
|
result = Mathf.Max(RemainingDistance / CurrentSlowingDistance, slowingLimit);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Transform Transform => transform;
|
|
|
|
[Tooltip("When the animal is on any of these States, The AI agent will be disable to improve performance.")]
|
|
[ContextMenuItem("Set Default", "SetDefaulStopAgent")]
|
|
public List<StateID> StopAgentOn;
|
|
|
|
/// <summary>Stores the Agent Direction used to move the Animal</summary>
|
|
public Vector3 AIDirection { get; set; }
|
|
//{
|
|
// get => m_AIDirection;
|
|
// set
|
|
// {
|
|
// m_AIDirection = value;
|
|
// Debug.Log($"<B>AI DIR: {m_AIDirection}</b>");
|
|
// }
|
|
//}
|
|
//Vector3 m_AIDirection;
|
|
|
|
/// <summary>Is the Agent in a OffMesh Link</summary>
|
|
public bool InOffMeshLink { get; set; }
|
|
//{
|
|
// get => m_InOffMeshLink;
|
|
// set
|
|
// {
|
|
// m_InOffMeshLink = value;
|
|
// Debug.Log($"<B>AI OFFML: {m_InOffMeshLink}</b>");
|
|
// }
|
|
//}
|
|
//bool m_InOffMeshLink;
|
|
|
|
public virtual bool AgentInOffMeshLink => Agent.isOnOffMeshLink;
|
|
|
|
/// <summary>Store if the Animal is on a Blocking Agent State</summary>
|
|
public bool StateIsBlockingAgent { get; set; }
|
|
|
|
/// <summary>Is the Agent Enabled/Active ?</summary>
|
|
public virtual bool ActiveAgent
|
|
{
|
|
get => agent.enabled && agent.isOnNavMesh;
|
|
set
|
|
{
|
|
agent.enabled = value;
|
|
if (agent.isOnNavMesh) agent.isStopped = !value;
|
|
// Debug.Log($"<B>{(agent.enabled? "[•]": "[ ]" )}</B> Agent Enable");
|
|
}
|
|
}
|
|
|
|
/// <summary>Checks if the Animal Can Fly</summary>
|
|
public virtual bool CanFly { get; private set; }
|
|
|
|
/// <summary>Has the animal arrived to the destination</summary>
|
|
public bool HasArrived { get; set; }
|
|
//{
|
|
// get => m_hasarrived;
|
|
// set
|
|
// {
|
|
// m_hasarrived = value;
|
|
// Debug.Log($"<B>{(m_hasarrived ? "[•]" : "[ ]")}</B> Has Arrived");
|
|
// }
|
|
//}
|
|
//private bool m_hasarrived;
|
|
|
|
/// <summary>Updates the Destination Position if the Target Moves</summary>
|
|
public virtual bool UpdateDestinationPosition { get; set; }
|
|
//{
|
|
// get => updateTargetPosition;
|
|
// set
|
|
// {
|
|
// updateTargetPosition = value;
|
|
// Debug.Log($"<B>{(updateTargetPosition ? "[•]" : "[ ]")}</B> UpdateTargetPosition");
|
|
// }
|
|
//}
|
|
//private bool updateTargetPosition;
|
|
|
|
/// <summary>Destination Position to use on Agent.SetDestination()</summary>
|
|
public virtual Vector3 DestinationPosition { get; set; }
|
|
//{
|
|
// get => m_DestinationPosition;
|
|
// set
|
|
// {
|
|
// m_DestinationPosition = value;
|
|
// if (debug) Debug.Log($"Dest Pos: [{m_DestinationPosition:F3}] Is AI [{IsAITarget != null}] Targ:[{Target}]");
|
|
// }
|
|
//}
|
|
//Vector3 m_DestinationPosition;
|
|
|
|
|
|
private IEnumerator I_WaitToNextTarget;
|
|
private IEnumerator IFreeMoveOffMesh;
|
|
private IEnumerator IClimbOffMesh;
|
|
#endregion
|
|
|
|
#region Public Variables
|
|
[Min(0)] public float UpdateAI = 0.2f;
|
|
private float CurrentTime;
|
|
|
|
[Min(0)] [SerializeField] protected float stoppingDistance = 0.6f;
|
|
[Min(0)] [SerializeField] protected float PointStoppingDistance = 0.6f;
|
|
|
|
/// <summary>The animal will change automatically to Walk if the distance to the target is this value</summary>
|
|
[SerializeField]
|
|
[UnityEngine.Serialization.FormerlySerializedAs("walkDistance")]
|
|
[Min(0)] protected float slowingDistance = 1f;
|
|
|
|
[Min(0)] public float OffMeshAlignment = 0.15f;
|
|
|
|
|
|
[Tooltip("If the difference between the current direction and the desired direction is greater than this value; the animal will stop to turn around.")]
|
|
[Range(0, 180)]
|
|
public float TurnAngle = 90f;
|
|
|
|
[Tooltip("Distance from the Animals Root to apply LookAt Target Logic when the Animal arrives to a target.")]
|
|
[Min(0)] public float LookAtOffset = 1;
|
|
|
|
[Tooltip("Limit for the Slowing Multiplier to be applied to the Speed Modifier")]
|
|
[Range(0, 1)]
|
|
[SerializeField] private float slowingLimit = 0.3f;
|
|
|
|
[SerializeField] private Transform target;
|
|
[SerializeField] private Transform nextTarget;
|
|
|
|
/// <summary>When the AI Arrives to a Waypoint Target, it will set the Next Target from the AIWaypoint</summary>
|
|
public bool AutoNextTarget { get; set; }
|
|
|
|
/// <summary>The Animal will Rotate/Look at the Target when he arrives to it</summary>
|
|
public bool LookAtTargetOnArrival { get; set; }
|
|
|
|
public bool debug = false;
|
|
public bool debugGizmos = true;
|
|
public bool debugStatus = true;
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>is the Animal, Flying, swimming, On Free Mode?</summary>
|
|
public bool FreeMove { get; private set; }
|
|
|
|
|
|
/// <summary>Default Stopping Distance</summary>
|
|
public virtual float StoppingDistance { get => stoppingDistance; set => stoppingDistance = value; }
|
|
|
|
protected float currentStoppingDistance;
|
|
|
|
/// <summary>Current Stoping distance of the Current Target/Destination</summary>
|
|
public virtual float CurrentStoppingDistance
|
|
{
|
|
get => currentStoppingDistance;
|
|
set => Agent.stoppingDistance = currentStoppingDistance = value;
|
|
}
|
|
|
|
/// <summary>Default Slowing Distance</summary>
|
|
public virtual float SlowingDistance => slowingDistance;
|
|
|
|
public virtual float Height => Agent.height * animal.ScaleFactor;
|
|
|
|
/// <summary>Current Slowing Distance from the Current AI Target</summary>
|
|
public virtual float CurrentSlowingDistance { get; set; }
|
|
|
|
/// <summary>Is the Animal Playing a mode</summary>
|
|
public bool IsOnMode => animal.IsPlayingMode;
|
|
|
|
/// <summary> Stop all Modes that does not allow Movement </summary>
|
|
private bool IsOnNonMovingMode => (IsOnMode && !animal.ActiveMode.AllowMovement);
|
|
|
|
|
|
|
|
/// <summary>Is the Target a WayPoint?</summary>
|
|
public IWayPoint IsWayPoint { get; set; }
|
|
|
|
/// <summary>Is the Target an AITarget</summary>
|
|
public IAITarget IsAITarget { get; set; }
|
|
|
|
/// <summary>AITarget Position</summary>
|
|
public Vector3 AITargetPos => IsAITarget.GetPosition(); //Update the AI Target Pos if the Target moved
|
|
|
|
/// <summary>Is the Target an AITarget</summary>
|
|
public IInteractable IsTargetInteractable { get; protected set; }
|
|
#endregion
|
|
|
|
#region Events
|
|
[Space]
|
|
public Vector3Event OnTargetPositionArrived = new Vector3Event();
|
|
public TransformEvent OnTargetArrived = new TransformEvent();
|
|
public TransformEvent OnTargetSet = new TransformEvent();
|
|
|
|
public TransformEvent TargetSet => OnTargetSet;
|
|
public TransformEvent OnArrived => OnTargetArrived;
|
|
|
|
#endregion
|
|
|
|
/// <summary>The Target is an Air Target</summary>
|
|
internal bool IsAirDestination => IsAITarget != null && IsAITarget.TargetType == WayPointType.Air;
|
|
internal bool IsGroundDestination => IsAITarget != null && IsAITarget.TargetType == WayPointType.Ground;
|
|
|
|
public UnityEvent OnEnabled = new UnityEvent();
|
|
public UnityEvent OnDisabled = new UnityEvent();
|
|
|
|
|
|
#region Properties
|
|
/// <summary>Reference of the Nav Mesh Agent</summary>
|
|
public virtual NavMeshAgent Agent => agent;
|
|
|
|
public Transform AgentTransform;
|
|
|
|
public virtual Vector3 GetPosition() => AgentTransform.position;
|
|
|
|
public Vector3 GetCenter() => animal.Center;
|
|
|
|
/// <summary> Self Target Type </summary>
|
|
public virtual WayPointType TargetType => animal.FreeMovement ? WayPointType.Air : WayPointType.Ground;
|
|
|
|
|
|
/// <summary>is the Target transform moving??</summary>
|
|
public virtual bool TargetIsMoving { get; internal set; }
|
|
|
|
|
|
/// <summary> Is the Animal waiting x time to go to the Next waypoint</summary>
|
|
public virtual bool IsWaiting { get; internal set; }
|
|
|
|
public virtual Vector3 LastOffMeshDestination{ get; set; }
|
|
|
|
|
|
|
|
public Vector3 NullVector { get; set; }
|
|
|
|
public virtual Transform NextTarget { get => nextTarget; set => nextTarget = value; }
|
|
|
|
public virtual Transform Target { get => target; set => target = value; }
|
|
|
|
/// <summary>Stores the Local Agent Position relative to the Animal</summary>
|
|
protected Vector3 AgentPosition;
|
|
|
|
#endregion
|
|
public virtual void SetActive(bool value)
|
|
{
|
|
if (gameObject.activeInHierarchy)
|
|
enabled = value;
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Unity Functions
|
|
public virtual bool OnAnimatorBehaviourMessage(string message, object value) => this.InvokeWithParams(message, value);
|
|
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
if (animal == null) animal = gameObject.FindComponent<MAnimal>();
|
|
ValidateAgent();
|
|
|
|
Interactor = animal.FindInterface<IInteractor>(); //Check if there's any Interactor
|
|
InputSource = animal.FindInterface<IInputSource>(); //Check if there's any Input Source
|
|
animal.UseSmoothVertical = true; //This needs to be disable so the slow distance works!!!!!!
|
|
|
|
LookAtTargetOnArrival = true; //By Default Look Target on Arrival set it to True
|
|
AutoNextTarget = true; //By Default Auto Next Target is set to True
|
|
UpdateDestinationPosition = true;
|
|
|
|
NullVector = new Vector3(-998.9999f, -998.9999f, -998.9999f);
|
|
|
|
DestinationPosition = NullVector;
|
|
|
|
CanFly = animal.HasState(StateEnum.Fly); //Check if the Animal can Fly
|
|
|
|
SetAgent();
|
|
}
|
|
|
|
/// <summary> Set the Default properties for the Nav mesh Agent </summary>
|
|
protected virtual void SetAgent()
|
|
{
|
|
if (agent == null) AgentTransform.GetComponent<NavMeshAgent>(); //Re-Check if the Agent is not properly assigned
|
|
|
|
if (agent)
|
|
{
|
|
AgentPosition = Agent.transform.localPosition;
|
|
Agent.angularSpeed = 0;
|
|
Agent.speed = 1; //The Agent needs a speed different from 0 to calculate the velocity
|
|
Agent.acceleration = 0;
|
|
Agent.autoBraking = false;
|
|
Agent.updateRotation = false; //The Animal will control the rotation . NOT THE AGENT
|
|
Agent.updatePosition = false; //The Animal will control the postion . NOT THE AGENT
|
|
Agent.autoTraverseOffMeshLink = false; //Offmesh links are handled by animation
|
|
Agent.stoppingDistance = StoppingDistance;
|
|
}
|
|
}
|
|
|
|
protected virtual void OnEnable()
|
|
{
|
|
animal.OnStateActivate.AddListener(OnState);
|
|
animal.OnModeStart.AddListener(OnModeStart);
|
|
animal.OnModeEnd.AddListener(OnModeEnd);
|
|
|
|
IsWaiting = true; //The AI Has not Started yet
|
|
|
|
this.Delay_Action(1,() => StartAI());//Start AI a Frame later;
|
|
//Invoke(nameof(StartAI), 0.01f);
|
|
|
|
//Disable any Input Source in case it was active
|
|
if (InputSource != null)
|
|
{
|
|
InputSource.MoveCharacter = false;
|
|
Debuging("Input Move Disabled");
|
|
}
|
|
|
|
OnEnabled.Invoke();
|
|
}
|
|
|
|
protected virtual void OnDisable()
|
|
{
|
|
animal.OnStateActivate.RemoveListener(OnState); //Listen when the Animations changes..
|
|
animal.OnModeStart.RemoveListener(OnModeStart); //Listen when the Animations changes..
|
|
animal.OnModeEnd.RemoveListener(OnModeEnd); //Listen when the Animations changes..
|
|
Stop();
|
|
StopAllCoroutines();
|
|
OnDisabled.Invoke();
|
|
|
|
animal.Rotate_at_Direction = false;
|
|
|
|
//Disable any Input Source in case it was active
|
|
if (InputSource != null)
|
|
{
|
|
InputSource.MoveCharacter = true;
|
|
Debuging("Input Move Enabled");
|
|
}
|
|
}
|
|
|
|
protected virtual void Update() { Updating(); }
|
|
#endregion
|
|
|
|
#region Animal Events Listen
|
|
/// <summary>Called when the Animal Enter an Action, Attack, Damage or something similar</summary>
|
|
public virtual void OnModeStart(int ModeID, int ability)
|
|
{
|
|
Debuging($"has started a Mode: <B>[{animal.ActiveMode.ID.name}]</B>. Ability: <B>[{animal.ActiveMode.ActiveAbility.Name}]</B>");
|
|
if (animal.ActiveMode.AllowMovement) return; //Don't stop the Animal Movevemt if the Mode can make movements
|
|
|
|
var Dest = DestinationPosition; //Store the Destination with modes
|
|
Stop(); //If the Agent was moving Stop it
|
|
DestinationPosition = Dest; //Restore the Destination with modes
|
|
}
|
|
|
|
/// <summary> Listen if the Animal Has finished a mode </summary>
|
|
public virtual void OnModeEnd(int ModeID, int ability)
|
|
{
|
|
if (StateIsBlockingAgent) return; //Do nothing if the current State is blocking the agent.
|
|
|
|
Debuging($"has ended a Mode: <B>[{ModeID}]</B>. Ability: <B>[{ability}]</B>");
|
|
|
|
|
|
|
|
if (!HasArrived) //Don't move if there's no destination
|
|
{
|
|
CalculatePath();
|
|
Move();
|
|
}
|
|
|
|
CompleteOffMeshLink();
|
|
CheckAirTarget(); //Everytime a State Changes Check again in case it failed by mistake
|
|
}
|
|
|
|
|
|
/// <summary>Listen to the Animal when it changes States</summary>
|
|
public virtual void OnState(int stateID)
|
|
{
|
|
if (IsWaiting) return; //Do nothing if the Agent is waiting
|
|
|
|
FreeMove = (animal.ActiveState.General.FreeMovement); //Recheck if the current State is a FreeState
|
|
CheckAirTarget(); //Everytime a State Changes Check again in case it failed by mistake
|
|
|
|
StateIsBlockingAgent = animal.ActiveStateID != 0 && StopAgentOn != null && StopAgentOn.Contains(animal.ActiveStateID); //Store the Active State Blocking
|
|
|
|
|
|
if (StateIsBlockingAgent) //Check if we are on a State that does not require the Agent
|
|
{
|
|
ActiveAgent = false; //Disable the Agent
|
|
}
|
|
else
|
|
{
|
|
if (!IsOnNonMovingMode)
|
|
{
|
|
CalculatePath();
|
|
Move();
|
|
}
|
|
|
|
CompleteOffMeshLink();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
public virtual void StartAI()
|
|
{
|
|
FreeMove = (animal.ActiveState.General.FreeMovement);
|
|
if (FreeMove) ActiveAgent = false;
|
|
if (Agent && !Agent.isOnNavMesh) ActiveAgent = false;
|
|
|
|
|
|
HasArrived = false;
|
|
TargetIsMoving = false;
|
|
var targ = target; target = null;
|
|
SetTarget(targ); //Set the first Target (IMPORTANT) it also set the next future targets
|
|
|
|
if (AgentTransform == animal.transform)
|
|
Debug.LogError("The Nav Mesh Agent needs to be attached to a child Gameobject, not in the same gameObject as the Animal Component");
|
|
}
|
|
|
|
public virtual void Updating()
|
|
{
|
|
ResetAgentPosition();
|
|
|
|
if (InOffMeshLink || IsWaiting) return; //Do nothing while is in an offmeshLink or its Waiting
|
|
|
|
CheckMovingTarget();
|
|
|
|
if (FreeMove)
|
|
{
|
|
FreeMovement();
|
|
}
|
|
else
|
|
{
|
|
UpdateAgent();
|
|
}
|
|
}
|
|
|
|
/// <summary>Reset the Agent Transform position to its Local Offset</summary>
|
|
protected virtual void ResetAgentPosition()
|
|
{
|
|
AgentTransform.localPosition = AgentPosition; //Important! Reset the Agent Position to the default Position
|
|
Agent.nextPosition = Agent.transform.position; //IMPORTANT!!!!Update the Agent Position to the Transform position
|
|
}
|
|
|
|
/// <summary>Check if there's a path to go to</summary>
|
|
public virtual bool PathPending() => ActiveAgent && Agent.isOnNavMesh && Agent.pathPending;
|
|
|
|
/// <summary> Updates the Agents using he animation root motion </summary>
|
|
public virtual void UpdateAgent()
|
|
{
|
|
if (HasArrived)
|
|
{
|
|
if (LookAtTargetOnArrival && LookAtOffset > 0)
|
|
{
|
|
if (DestinationPosition == NullVector)
|
|
{
|
|
DestinationPosition = (target != null ? target.position : transform.position + transform.forward);
|
|
}
|
|
|
|
var Origin = (animal.transform.position - animal.transform.forward * LookAtOffset * animal.ScaleFactor);
|
|
|
|
var LookAtDir = (target != null ? target.position : DestinationPosition) - Origin;
|
|
|
|
|
|
|
|
if (debugGizmos)
|
|
{
|
|
MTools.Draw_Arrow(Origin, LookAtDir, Color.magenta);
|
|
MTools.DrawWireSphere(Origin , Color.magenta, 0.1f);
|
|
}
|
|
|
|
animal.RotateAtDirection(LookAtDir);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (ActiveAgent)
|
|
{
|
|
if (PathPending()) return; //Means is still calculating the path to the Destination
|
|
|
|
SetRemainingDistance(AgentRemainingDistance);
|
|
|
|
if (!Arrive_Destination()) //if we havent't arrived to the destination ... Find the way
|
|
{
|
|
if (!CheckOffMeshLinks())
|
|
{
|
|
CalculatePath();
|
|
Move(); //Calculate the AI DIRECTION
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Check if we have Arrived to the Destination</summary>
|
|
public virtual bool Arrive_Destination()
|
|
{
|
|
if (CurrentStoppingDistance >= RemainingDistance)
|
|
{
|
|
if (IsPathIncomplete()) //Check when the Agent is trapped on an NavMesh that cannot exit
|
|
{
|
|
Debuging($"[Agent Path Status: {Agent.pathStatus}]. Force Stop");
|
|
Stop();
|
|
StopWait();
|
|
HasArrived = true;
|
|
RemainingDistance = 0; //Reset the Remaining Distance
|
|
AIDirection = Vector3.zero; //Reset AI Direction
|
|
return true;
|
|
}
|
|
|
|
if (!CheckDestinationHeight()) return false;
|
|
|
|
HasArrived = true;
|
|
RemainingDistance = 0; //Reset the Remaining Distance
|
|
AIDirection = Vector3.zero; //Reset AI Direction
|
|
Move();
|
|
|
|
OnTargetPositionArrived.Invoke(DestinationPosition); //Invoke the Event On Target Position Arrived
|
|
|
|
if (target)
|
|
{
|
|
Debuging($"<color=green>has arrived to: <B>{target.name}</B> → {DestinationPosition} </color>");
|
|
|
|
OnTargetArrived.Invoke(target); //Invoke the Event On Target Arrived
|
|
|
|
CheckInteractions();
|
|
|
|
if (IsAITarget != null/* && IsAITarget.GetPosition() == DestinationPosition*/) //If we have arrived to an AI Target and the Destination is the same one
|
|
{
|
|
IsAITarget.TargetArrived(animal.gameObject); //Call the method that the Target has arrived to the destination
|
|
LookAtTargetOnArrival = IsAITarget.ArriveLookAt;
|
|
if (IsAITarget.TargetType == WayPointType.Ground) FreeMove = false; //if the next waypoing is on the Ground then set the free Movement to false
|
|
if (AutoNextTarget) MovetoNextTarget(); //Set and Move to the Next Target
|
|
else Stop();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debuging($"<color=green>has arrived to: <B>{DestinationPosition}</B>. Stop</color>");
|
|
Stop(); //The target was removed
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected virtual bool IsPathIncomplete()
|
|
{
|
|
return ActiveAgent && !FreeMove && Agent.pathStatus != NavMeshPathStatus.PathComplete;
|
|
}
|
|
|
|
/// <summary>Check if the Height of the Destination is near the Animal</summary>
|
|
protected virtual bool CheckDestinationHeight()
|
|
{
|
|
if (FreeMove) return true; //When Flying do not check the Height of the Point
|
|
|
|
MTools.DrawWireSphere(DestinationPosition, Color.white, 0.1f);
|
|
//if (IsWayPoint!= null) DestinationPosition = Agent.destination;
|
|
|
|
var Result = NavMesh.SamplePosition(DestinationPosition, out _, Height, NavMesh.AllAreas);
|
|
return Result;
|
|
}
|
|
|
|
/// <summary> Check if the Target is moving </summary>
|
|
public virtual void CheckMovingTarget()
|
|
{
|
|
if (MTools.ElapsedTime(CurrentTime, UpdateAI))
|
|
{
|
|
if (Target)
|
|
{
|
|
TargetIsMoving = (Target.position - TargetLastPosition).sqrMagnitude > (0.01f / animal.ScaleFactor);
|
|
TargetLastPosition = Target.position;
|
|
|
|
if (TargetIsMoving) Update_DestinationPosition();
|
|
}
|
|
CurrentTime = Time.time;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>Calculates the Direction to move the Animal using the Agent Desired Velocity</summary>
|
|
public virtual void CalculatePath()
|
|
{
|
|
if (FreeMove) return; //Do nothing when its on Free Move
|
|
//if (IsWaiting) return; //Do nothing when its waiting
|
|
|
|
if (!ActiveAgent) //Enable the Agent in case is disabled
|
|
{
|
|
ActiveAgent = true;
|
|
ResetFreeMoveOffMesh();
|
|
}
|
|
|
|
if (Agent.isOnNavMesh)
|
|
{
|
|
if (Agent.destination != DestinationPosition) //Calculate the New Path **ONLY** when the Destination is Different
|
|
{
|
|
Agent.SetDestination(DestinationPosition); //Set the Current Destination;
|
|
|
|
if (IsWayPoint != null) DestinationPosition = Agent.destination; //Important use the Cast value on the terrain.
|
|
}
|
|
|
|
if (Agent.desiredVelocity != Vector3.zero) AIDirection = Agent.desiredVelocity.normalized;
|
|
}
|
|
}
|
|
|
|
|
|
public virtual void Move()
|
|
{
|
|
animal.ForwardMultiplier = Mathf.Abs(animal.DeltaAngle) > TurnAngle ? 0 : 1; //Slow Down if the Animal can arrive to the target.
|
|
animal.Move(AIDirection * SlowMultiplier); //Move the Animal using the Agent Direction and the Slow Multiplier
|
|
}
|
|
|
|
/// <summary> Disable the AI Agent and it Stops the Animal</summary>
|
|
public virtual void Stop()
|
|
{
|
|
ActiveAgent = false; //Disable the Agent
|
|
AIDirection = Vector3.zero;
|
|
DestinationPosition = NullVector;
|
|
animal.StopMoving(); //Stop the Animal
|
|
Debuging($"[Stopped]. Agent Disabled");
|
|
}
|
|
|
|
|
|
/// <summary>Update The Target Position </summary>
|
|
protected virtual void Update_DestinationPosition()
|
|
{
|
|
if (UpdateDestinationPosition)
|
|
{
|
|
DestinationPosition = GetTargetPosition(); //Update the Target Position
|
|
|
|
var DistanceOnMovingTarget = Vector3.Distance(DestinationPosition, AgentTransform.position); //Double check if the Animal is far from the target
|
|
|
|
if (DistanceOnMovingTarget >= CurrentStoppingDistance)
|
|
{
|
|
HasArrived = false;
|
|
CalculatePath();
|
|
Move();
|
|
}
|
|
else
|
|
{
|
|
HasArrived = true; //Check if the animal hasn't arrived to a moving target
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Store the remaining distance -- but if navMeshAgent is still looking for a path Keep Moving
|
|
/// </summary>
|
|
/// <param name="current"></param>
|
|
protected virtual void SetRemainingDistance(float current) => RemainingDistance = current;
|
|
|
|
|
|
|
|
#region Set Assing Target and Next Targets
|
|
|
|
/// <summary> Resets al the Internal Values of the AI Control </summary>
|
|
public virtual void ResetAIValues()
|
|
{
|
|
StopWait(); //If the Animal was waiting Reset the waiting IMPORTANT!!
|
|
RemainingDistance = float.PositiveInfinity; //Set the Remaining Distance as the Max Float Value
|
|
MinRemainingDistance = float.PositiveInfinity; //Set the Remaining Distance as the Max Float Value
|
|
HasArrived = false;
|
|
}
|
|
|
|
/// <summary>Set the next Target</summary>
|
|
public virtual void SetTarget(Transform newTarget, bool move)
|
|
{
|
|
// if (target == Target && !HasArrived) return; //Don't assign the same target if we are travelling to that target (Breaks Wander Areas)
|
|
|
|
target = newTarget;
|
|
OnTargetSet.Invoke(newTarget); //Invoked that the Target has changed.
|
|
|
|
if (target != null)
|
|
{
|
|
TargetLastPosition = newTarget.position; //Since is a new Target "Reset the Target last position"
|
|
DestinationPosition = newTarget.position; //Update the Target Position
|
|
|
|
IsAITarget = newTarget.gameObject.FindInterface<IAITarget>();
|
|
IsTargetInteractable = newTarget.FindInterface<IInteractable>();
|
|
IsWayPoint = newTarget.FindInterface<IWayPoint>();
|
|
|
|
NextTarget = null;
|
|
|
|
if (IsWayPoint != null)
|
|
{
|
|
NextTarget = IsWayPoint.NextTarget(); //Find the Next Target on the Waypoint
|
|
}
|
|
// Debuging($"<color=yellow>New Target <B>[{newTarget.name}]</B> → [{DestinationPosition}]. Move = [{move}]</color>");
|
|
|
|
CheckAirTarget();
|
|
|
|
//Resume the Agent is MoveAgent is true
|
|
if (move)
|
|
{
|
|
|
|
ResetAIValues();
|
|
if (animal.IsPlayingMode) animal.Mode_Interrupt(); //In Case it was making any Mode Interrupt it because there's a new target to go to.
|
|
CurrentStoppingDistance = GetTargetStoppingDistance();
|
|
CurrentSlowingDistance = GetTargetSlowingDistance();
|
|
|
|
DestinationPosition = GetTargetPosition();
|
|
|
|
CalculatePath();
|
|
|
|
Move();
|
|
Debuging($"<color=yellow>is travelling to <B>Target: [{newTarget.name}]</B> → [{DestinationPosition}] </color>");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IsAITarget = null; //Reset the AI Target
|
|
IsTargetInteractable = null; //Reset the AI Target Interactable
|
|
IsWayPoint = null; //Reset the Waypoint
|
|
|
|
if (move) Stop(); //Means the Target is null so Stop the Animal
|
|
}
|
|
}
|
|
|
|
public virtual void SetTarget(GameObject target) => SetTarget(target, true);
|
|
public virtual void SetTarget(GameObject target, bool move) => SetTarget(target != null ? target.transform : null, move);
|
|
|
|
|
|
|
|
/// <summary>Remove the current Target and stop the Agent </summary>
|
|
public virtual void ClearTarget() => SetTarget((Transform)null, false);
|
|
|
|
/// <summary>Remove the current Target </summary>
|
|
public virtual void NullTarget() => target = null;
|
|
|
|
/// <summary>Assign a new Target but it does not move it to it</summary>
|
|
public virtual void SetTargetOnly(Transform target) => SetTarget(target, false);
|
|
public virtual void SetTargetOnly(GameObject target) => SetTarget(target, false);
|
|
public virtual void SetTarget(Transform target) => SetTarget(target, true);
|
|
|
|
/// <summary> Returns the Current Target Destination</summary>
|
|
public virtual Vector3 GetTargetPosition()
|
|
{
|
|
var TargetPos = (IsAITarget != null) ? AITargetPos : target.position;
|
|
if (TargetPos == Vector3.zero) TargetPos = target.position; //HACK FOR WHEN THE TARGET REMOVED THEIR AI TARGET COMPONENT
|
|
return TargetPos;
|
|
}
|
|
|
|
public void TargetArrived(GameObject target) {/*Do nothing*/ }
|
|
|
|
public virtual float GetTargetStoppingDistance() => IsAITarget != null ? IsAITarget.StopDistance() : StoppingDistance * animal.ScaleFactor;
|
|
public virtual float GetTargetSlowingDistance() => IsAITarget != null ? IsAITarget.SlowDistance() : SlowingDistance * animal.ScaleFactor;
|
|
|
|
/// <summary>Set the Next Target from on the NextTargets Stored on the Waypoints or Zones</summary>
|
|
|
|
public virtual void SetNextTarget(GameObject next)
|
|
{
|
|
NextTarget = next.transform;
|
|
IsWayPoint = next.GetComponent<IWayPoint>(); //Check if the next gameobject is a Waypoint.
|
|
}
|
|
|
|
public virtual void MovetoNextTarget()
|
|
{
|
|
if (NextTarget == null)
|
|
{
|
|
Debuging("There's no Next Target");
|
|
Stop();
|
|
return;
|
|
}
|
|
|
|
if (IsWayPoint != null)
|
|
{
|
|
StopWait();
|
|
I_WaitToNextTarget = C_WaitToNextTarget(IsWayPoint.WaitTime, NextTarget); //IMPORTANT YOU NEED TO WAIT 1 FRAME ALWAYS TO GO TO THE NEXT WAYPOINT
|
|
StartCoroutine(I_WaitToNextTarget);
|
|
}
|
|
else
|
|
{
|
|
SetTarget(NextTarget);
|
|
}
|
|
}
|
|
|
|
public void StopWait()
|
|
{
|
|
IsWaiting = false;
|
|
if (I_WaitToNextTarget != null) StopCoroutine(I_WaitToNextTarget); //Stop the coroutine in case it was playing
|
|
}
|
|
|
|
/// <summary> Check if the Next Target is a Air Target, if true then go to it</summary>
|
|
internal virtual bool CheckAirTarget()
|
|
{
|
|
if (!CanFly) return false;
|
|
|
|
if (IsAirDestination && !FreeMove) //If the animal can fly, there's a new wayPoint & is on the Air
|
|
{
|
|
if (Target) Debuging($"Target {Target} is in the Air. Activating Fly State", Target.gameObject);
|
|
animal.State_Activate(StateEnum.Fly);
|
|
FreeMove = true;
|
|
}
|
|
|
|
return IsAirDestination;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public virtual void SetDestination(Vector3 PositionTarget) => SetDestination(PositionTarget, true);
|
|
|
|
/// <summary>Set the next Destination Position without having a target</summary>
|
|
public virtual void SetDestination(Vector3 newDestination, bool move)
|
|
{
|
|
LookAtTargetOnArrival = false; //Do not Look at the Target when its setting a destination
|
|
|
|
if (newDestination == DestinationPosition) return; //Means that you're already going to the same point so Skip the code
|
|
|
|
CurrentStoppingDistance = PointStoppingDistance; //Reset the stopping distance when Set Destination is used.
|
|
|
|
ResetAIValues();
|
|
|
|
if (IsOnNonMovingMode) animal.Mode_Interrupt();
|
|
|
|
IsWayPoint = null;
|
|
|
|
if (I_WaitToNextTarget != null)
|
|
StopCoroutine(I_WaitToNextTarget); //if there's a coroutine active then stop it
|
|
|
|
DestinationPosition = newDestination; //Update the Target Position
|
|
|
|
if (move)
|
|
{
|
|
CalculatePath();
|
|
Move();
|
|
Debuging($"<color=yellow>is travelling to: {DestinationPosition} </color>");
|
|
}
|
|
}
|
|
|
|
/// <summary>Set the next Destination Position without having a target</summary>
|
|
public virtual void SetDestination(Vector3Var newDestination) => SetDestination(newDestination.Value);
|
|
|
|
|
|
public virtual void SetDestinationClearTarget(Vector3 PositionTarget)
|
|
{
|
|
target = null;
|
|
SetDestination(PositionTarget, true);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>Check Interactions when Arriving to the Destination</summary>
|
|
protected virtual void CheckInteractions()
|
|
{
|
|
if (IsTargetInteractable != null && IsTargetInteractable.Auto) //If the interactable is set to Auto!!!!!!!
|
|
{
|
|
if (Interactor != null)
|
|
{
|
|
Interactor.Interact(IsTargetInteractable); //Do an Interaction if the Animal has an Interactor
|
|
Debuging($"Interact with : <b><{IsTargetInteractable.Owner.name}></b>. Interactor [{Interactor.Owner.name}]");
|
|
}
|
|
else
|
|
{
|
|
IsTargetInteractable.Interact(0, animal.gameObject); //Do an Empty Interaction does not have an interactor
|
|
Debuging($"Interact with : <b><{IsTargetInteractable.Owner.name}></b>. Interactor:Null");
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary> Move Freely towards the Destination.. No Obstacle is calculated</summary>
|
|
protected virtual void FreeMovement()
|
|
{
|
|
AIDirection = (DestinationPosition - animal.transform.position); //Important to be normalized!!
|
|
SetRemainingDistance(AIDirection.magnitude);
|
|
|
|
AIDirection = AIDirection.normalized * SlowMultiplier; //Important to be normalized!!
|
|
|
|
animal.Move(AIDirection);
|
|
Arrive_Destination();
|
|
}
|
|
|
|
|
|
protected virtual bool CheckOffMeshLinks()
|
|
{
|
|
if (AgentInOffMeshLink && !InOffMeshLink) //Check if the Agent is on a OFF MESH LINK (Do this once! per offmesh link)
|
|
{
|
|
InOffMeshLink = true; //Just to avoid entering here again while we are on a OFF MESH LINK
|
|
LastOffMeshDestination = DestinationPosition;
|
|
|
|
OffMeshLinkData OMLData = Agent.currentOffMeshLinkData;
|
|
|
|
if (OMLData.linkType == OffMeshLinkType.LinkTypeManual) //Means that it has a OffMesh Link component
|
|
{
|
|
var OffMesh_Link = OMLData.offMeshLink; //Check if the OffMeshLink is a Manually placed Link
|
|
|
|
if (OffMesh_Link)
|
|
{
|
|
var AnimalLink = OffMesh_Link.GetComponent<MAIAnimalLink>();
|
|
|
|
//CUSTOM OFFMESHLINK
|
|
if (AnimalLink)
|
|
{
|
|
AnimalLink.Execute(this, animal);
|
|
return true;
|
|
}
|
|
|
|
Zone IsOffMeshZone =
|
|
OffMesh_Link.FindComponent<Zone>(); //Search if the OFFMESH IS An ACTION ZONE (EXAMPLE CRAWL)
|
|
|
|
if (IsOffMeshZone) //if the OffmeshLink is a zone and is not making an action
|
|
{
|
|
if (debug) Debuging($"<color=white>is on a <b>[OffmeshLink Zone]</b> -> [{IsOffMeshZone.name}]</color>");
|
|
|
|
IsOffMeshZone.ActivateZone(animal); //Activate the Zone
|
|
return true;
|
|
}
|
|
|
|
|
|
var NearTransform = transform.NearestTransform(OffMesh_Link.endTransform, OffMesh_Link.startTransform);
|
|
var FarTransform = transform.FarestTransform(OffMesh_Link.endTransform, OffMesh_Link.startTransform);
|
|
|
|
AIDirection = NearTransform.forward;
|
|
animal.Move(AIDirection);//Move where the AI DIRECTION FROM THE OFFMESH IS POINTINg
|
|
|
|
|
|
if (OffMesh_Link.CompareTag("Fly"))
|
|
{
|
|
Debuging($"<color=white>is On a <b>[OffmeshLink]</b> -> [Fly]</color>");
|
|
FlyOffMesh(FarTransform);
|
|
}
|
|
else if (OffMesh_Link.CompareTag("Climb"))
|
|
{
|
|
Debuging($"<color=white>is On a <b>[OffmeshLink]</b> -> [Climb] -> { OffMesh_Link.transform.name}</color>");
|
|
ClimbOffMesh();
|
|
}
|
|
else if (OffMesh_Link.area == 2) //2 is Off mesh Jump
|
|
{
|
|
animal.State_Activate(StateEnum.Jump); //if the OffMesh Link is a Jump type activate the jump
|
|
Debuging($"<color=white>is On a <b>[OffmeshLink]</b> -> [Jump]</color>");
|
|
}
|
|
}
|
|
}
|
|
else if (OMLData.linkType == OffMeshLinkType.LinkTypeJumpAcross) //Means that it has a OffMesh Link component
|
|
{
|
|
animal.State_Activate(StateEnum.Jump); //2 is Jump State
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> Completes the OffmeshLink in case the animal was in one </summary>
|
|
public virtual void CompleteOffMeshLink()
|
|
{
|
|
if (InOffMeshLink)
|
|
{
|
|
CompleteAgentOffMesh();
|
|
|
|
InOffMeshLink = false;
|
|
DestinationPosition = LastOffMeshDestination; //restore the OffMesh Link
|
|
CalculatePath();
|
|
Move();
|
|
}
|
|
}
|
|
|
|
protected virtual void CompleteAgentOffMesh()
|
|
{
|
|
if (Agent && Agent.isOnOffMeshLink) Agent.CompleteOffMeshLink(); //Complete an offmesh link in case the Agent was in one
|
|
}
|
|
|
|
protected virtual void FlyOffMesh(Transform target)
|
|
{
|
|
ResetFreeMoveOffMesh();
|
|
IFreeMoveOffMesh = C_FlyMoveOffMesh(target);
|
|
StartCoroutine(IFreeMoveOffMesh);
|
|
}
|
|
|
|
protected virtual void ClimbOffMesh()
|
|
{
|
|
if (IClimbOffMesh != null) StopCoroutine(IClimbOffMesh);
|
|
IClimbOffMesh = C_Climb_OffMesh();
|
|
StartCoroutine(IClimbOffMesh);
|
|
}
|
|
|
|
|
|
/// <summary>Check if the The animal was moving on a Free OffMesh Link </summary>
|
|
protected virtual void ResetFreeMoveOffMesh()
|
|
{
|
|
if (IFreeMoveOffMesh != null)
|
|
{
|
|
InOffMeshLink = false;
|
|
StopCoroutine(IFreeMoveOffMesh);
|
|
IFreeMoveOffMesh = null;
|
|
}
|
|
}
|
|
|
|
protected virtual IEnumerator C_WaitToNextTarget(float time, Transform NextTarget)
|
|
{
|
|
IsWaiting = true;
|
|
|
|
if (time > 0)
|
|
{
|
|
yield return null; //SUUUUUUUUUPER IMPORTANT!!!!!!!!!
|
|
Debuging($"<color=white> is waiting <B>{time:F2}</B> seconds to go to <B>[{NextTarget.name}]</B> → {DestinationPosition} </color>");
|
|
|
|
animal.Move(AIDirection = Vector3.zero); //Stop the Animal
|
|
yield return new WaitForSeconds(time);
|
|
}
|
|
SetTarget(NextTarget);
|
|
}
|
|
|
|
protected virtual IEnumerator C_FlyMoveOffMesh(Transform target)
|
|
{
|
|
animal.State_Activate(StateEnum.Fly); //Set the State to Fly
|
|
InOffMeshLink = true;
|
|
float distance = float.MaxValue;
|
|
|
|
while (distance > StoppingDistance)
|
|
{
|
|
if (target == null) break;
|
|
animal.Move((target.position - animal.transform.position).normalized * SlowMultiplier);
|
|
distance = Vector3.Distance(animal.transform.position, target.position);
|
|
yield return null;
|
|
}
|
|
animal.ActiveState.AllowExit();
|
|
|
|
Debuging("Exit Fly State Off Mesh");
|
|
|
|
InOffMeshLink = false;
|
|
}
|
|
|
|
protected virtual IEnumerator C_Climb_OffMesh()
|
|
{
|
|
animal.State_Activate(StateEnum.Climb); //Set the State to Climb
|
|
InOffMeshLink = true;
|
|
yield return null;
|
|
ActiveAgent = false;
|
|
|
|
while (animal.ActiveState.ID == StateEnum.Climb)
|
|
{
|
|
animal.SetInputAxis(Vector3.forward); //Move Upwards on the Climb
|
|
yield return null;
|
|
}
|
|
|
|
Debuging("Exit Climb State Off Mesh");
|
|
|
|
InOffMeshLink = false;
|
|
|
|
IClimbOffMesh = null;
|
|
}
|
|
|
|
public void ResetStoppingDistance() => CurrentStoppingDistance = StoppingDistance;
|
|
public void ResetSlowingDistance() => CurrentSlowingDistance = SlowingDistance;
|
|
public float StopDistance() => StoppingDistance;
|
|
public float SlowDistance() => SlowingDistance;
|
|
|
|
public virtual void ValidateAgent()
|
|
{
|
|
if (agent == null) agent = gameObject.FindComponent<NavMeshAgent>();
|
|
|
|
AgentTransform = (agent != null) ? agent.transform : transform;
|
|
}
|
|
|
|
|
|
protected virtual void Debuging(string Log) { if (debug) Debug.Log($"<B>{animal.name} AI:</B> " + Log,this); }
|
|
protected virtual void Debuging(string Log, GameObject obj) { if (debug) Debug.Log($"<B>{animal.name}:</B> " + Log, obj); }
|
|
|
|
#if UNITY_EDITOR
|
|
[HideInInspector] public int Editor_Tabs1;
|
|
|
|
protected virtual void OnValidate()
|
|
{
|
|
if (animal == null) animal = gameObject.FindComponent<MAnimal>();
|
|
ValidateAgent();
|
|
}
|
|
|
|
|
|
void Reset()
|
|
{
|
|
SetDefaulStopAgent();
|
|
}
|
|
|
|
void SetDefaulStopAgent()
|
|
{
|
|
StopAgentOn = new List<StateID>(3)
|
|
{
|
|
MTools.GetInstance<StateID>("Fall"),
|
|
MTools.GetInstance<StateID>("Jump"),
|
|
MTools.GetInstance<StateID>("Fly")
|
|
};
|
|
}
|
|
|
|
private string CheckBool(bool val) => val ? "[X]" : "[ ]";
|
|
|
|
protected virtual void OnDrawGizmos()
|
|
{
|
|
var isPlaying = Application.isPlaying;
|
|
|
|
if (isPlaying && debugStatus)
|
|
{
|
|
string log = "\nTarget: [" + (Target != null ? Target.name : "-none-") + "]";
|
|
log += "- NextTarget: [" + (NextTarget != null ? NextTarget.name : "-none-") + "]";
|
|
log += "\nRemainingDistance: " + RemainingDistance.ToString("F2");
|
|
log += "\nStopDistance: " + CurrentStoppingDistance.ToString("F2");
|
|
log += "\n" + CheckBool(HasArrived) + " HasArrived";
|
|
log += "\n" + CheckBool(ActiveAgent) + " Agent";
|
|
log += "\n" + CheckBool(TargetIsMoving) + " Target is Moving";
|
|
log += "\n" + CheckBool(IsAITarget != null) + "Target is AITarget";
|
|
log += "\n" + CheckBool(IsWayPoint != null) + "Target is WayPoint";
|
|
log += "\n" + CheckBool(IsWaiting) + " Waiting";
|
|
log += "\n" + CheckBool(IsOnMode) + " On Mode";
|
|
log += "\n" + CheckBool(FreeMove) + " Free Move";
|
|
log += "\n" + CheckBool(InOffMeshLink) + " InOffMeshLink";
|
|
|
|
var Styl = new GUIStyle(GUI.skin.box);
|
|
Styl.normal.textColor = Color.white;
|
|
Styl.fontStyle = FontStyle.Bold;
|
|
Styl.alignment = TextAnchor.UpperLeft;
|
|
|
|
|
|
UnityEditor.Handles.Label(transform.position, "AI Log:" + log, Styl);
|
|
}
|
|
if (!debugGizmos) return;
|
|
|
|
|
|
//Paths
|
|
if (Agent && ActiveAgent && Agent.path != null)
|
|
{
|
|
Gizmos.color = Color.yellow;
|
|
for (int i = 1; i < Agent.path.corners.Length; i++)
|
|
{
|
|
Gizmos.DrawLine(Agent.path.corners[i - 1], Agent.path.corners[i]);
|
|
}
|
|
}
|
|
|
|
|
|
if (isPlaying)
|
|
{
|
|
MTools.Draw_Arrow(AgentTransform.position, AIDirection * 2, Color.white);
|
|
|
|
Gizmos.color = Color.white;
|
|
Gizmos.DrawWireSphere(DestinationPosition, stoppingDistance);
|
|
}
|
|
if (AgentTransform)
|
|
{
|
|
var scale = animal ? animal.ScaleFactor : transform.lossyScale.y;
|
|
var Pos = (isPlaying) ? DestinationPosition : AgentTransform.position;
|
|
var Stop = (isPlaying) ? CurrentStoppingDistance : StoppingDistance * scale;
|
|
var Slow = (isPlaying) ? CurrentSlowingDistance : SlowingDistance * scale;
|
|
|
|
|
|
|
|
Gizmos.color = Color.red;
|
|
Gizmos.DrawSphere(AgentTransform.position, 0.1f);
|
|
if (Slow > Stop)
|
|
{
|
|
UnityEditor.Handles.color = Color.cyan;
|
|
UnityEditor.Handles.DrawWireDisc(Pos, Vector3.up, Slow);
|
|
}
|
|
|
|
UnityEditor.Handles.color = HasArrived ? Color.green : Color.red;
|
|
UnityEditor.Handles.DrawWireDisc(Pos, Vector3.up, Stop);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#region Inspector
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
[CustomEditor(typeof(MAnimalAIControl), true)]
|
|
public class AnimalAIControlEd : Editor
|
|
{
|
|
private MAnimalAIControl M;
|
|
|
|
protected SerializedProperty
|
|
stoppingDistance, SlowingDistance, LookAtOffset,targett, UpdateAI, slowingLimit,
|
|
agent, animal, PointStoppingDistance, OnEnabled,OnTargetPositionArrived, OnTargetArrived,
|
|
OnTargetSet, debugGizmos, debugStatus, debug, Editor_Tabs1, nextTarget, OnDisabled, AgentTransform, OffMeshAlignment,
|
|
StopAgentOn, TurnAngle;
|
|
|
|
protected virtual void OnEnable()
|
|
{
|
|
M = (MAnimalAIControl)target;
|
|
|
|
animal = serializedObject.FindProperty("animal");
|
|
AgentTransform = serializedObject.FindProperty("AgentTransform");
|
|
GetAgentProperty();
|
|
|
|
slowingLimit = serializedObject.FindProperty("slowingLimit");
|
|
TurnAngle = serializedObject.FindProperty("TurnAngle");
|
|
|
|
OnEnabled = serializedObject.FindProperty("OnEnabled");
|
|
OnDisabled = serializedObject.FindProperty("OnDisabled");
|
|
|
|
OnTargetSet = serializedObject.FindProperty("OnTargetSet");
|
|
OnTargetArrived = serializedObject.FindProperty("OnTargetArrived");
|
|
OnTargetPositionArrived = serializedObject.FindProperty("OnTargetPositionArrived");
|
|
stoppingDistance = serializedObject.FindProperty("stoppingDistance");
|
|
PointStoppingDistance = serializedObject.FindProperty("PointStoppingDistance");
|
|
SlowingDistance = serializedObject.FindProperty("slowingDistance");
|
|
LookAtOffset = serializedObject.FindProperty("LookAtOffset");
|
|
targett = serializedObject.FindProperty("target");
|
|
nextTarget = serializedObject.FindProperty("nextTarget");
|
|
OffMeshAlignment = serializedObject.FindProperty("OffMeshAlignment");
|
|
|
|
debugGizmos = serializedObject.FindProperty("debugGizmos");
|
|
debugStatus = serializedObject.FindProperty("debugStatus");
|
|
debug = serializedObject.FindProperty("debug");
|
|
|
|
Editor_Tabs1 = serializedObject.FindProperty("Editor_Tabs1");
|
|
StopAgentOn = serializedObject.FindProperty("StopAgentOn");
|
|
|
|
UpdateAI = serializedObject.FindProperty("UpdateAI");
|
|
|
|
|
|
if (M.StopAgentOn == null || M.StopAgentOn.Count == 0)
|
|
{
|
|
M.StopAgentOn = new System.Collections.Generic.List<StateID>(2) { MTools.GetInstance<StateID>("Fall"), MTools.GetInstance<StateID>("Fly") };
|
|
StopAgentOn.isExpanded = true;
|
|
MTools.SetDirty(M);
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
public virtual void GetAgentProperty()
|
|
{
|
|
agent = serializedObject.FindProperty("agent");
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
serializedObject.Update();
|
|
MalbersEditor.DrawDescription("AI Source. Moves the animal using an AI Agent");
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
{
|
|
EditorGUILayout.BeginVertical(MalbersEditor.StyleGray);
|
|
|
|
Editor_Tabs1.intValue = GUILayout.Toolbar(Editor_Tabs1.intValue, new string[] { "General", "Events", "Debug" });
|
|
|
|
int Selection = Editor_Tabs1.intValue;
|
|
|
|
if (Selection == 0) ShowGeneral();
|
|
else if (Selection == 1) ShowEvents();
|
|
else if (Selection == 2) ShowDebug();
|
|
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(target, "Animal AI Control Changed");
|
|
}
|
|
}
|
|
|
|
if (M.Agent != null && M.animal != null && M.Agent.transform == M.animal.transform)
|
|
{
|
|
EditorGUILayout.HelpBox("The NavMesh Agent needs to be attached to a child gameObject. " +
|
|
"It cannot be in the same gameObject as the Animal Component", MessageType.Error);
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
private void ShowGeneral()
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
{
|
|
targett.isExpanded = MalbersEditor.Foldout(targett.isExpanded, "Targets");
|
|
if (targett.isExpanded)
|
|
{
|
|
EditorGUILayout.PropertyField(targett, new GUIContent("Target", "Target to follow"));
|
|
EditorGUILayout.PropertyField(nextTarget, new GUIContent("Next Target", "Next Target the animal will go"));
|
|
}
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
{
|
|
UpdateAI.isExpanded = MalbersEditor.Foldout(UpdateAI.isExpanded, "AI Parameters");
|
|
|
|
if (UpdateAI.isExpanded)
|
|
{
|
|
// EditorGUILayout.LabelField("AI Parameters", EditorStyles.boldLabel);
|
|
EditorGUILayout.PropertyField(UpdateAI, new GUIContent("Update Agent", " Recalculate the Path for the Agent every x seconds "));
|
|
EditorGUILayout.PropertyField(stoppingDistance, new GUIContent("Stopping Distance", "Agent Stopping Distance"));
|
|
EditorGUILayout.PropertyField(SlowingDistance, new GUIContent("Slowing Distance", "Distance to Start slowing the animal before arriving to the destination"));
|
|
EditorGUILayout.PropertyField(LookAtOffset);
|
|
EditorGUILayout.PropertyField(PointStoppingDistance, new GUIContent("Point Stop Distance", "Stop Distance used on the SetDestination method. No Target Assigned"));
|
|
EditorGUILayout.PropertyField(TurnAngle);
|
|
EditorGUILayout.PropertyField(slowingLimit);
|
|
EditorGUILayout.PropertyField(OffMeshAlignment);
|
|
}
|
|
}
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
if (M.Agent)
|
|
{
|
|
M.Agent.stoppingDistance = stoppingDistance.floatValue;
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
{
|
|
animal.isExpanded = MalbersEditor.Foldout(animal.isExpanded, "References");
|
|
|
|
if (animal.isExpanded)
|
|
{
|
|
EditorGUILayout.PropertyField(animal, new GUIContent("Animal", "Reference for the Animal Controller"));
|
|
EditorGUILayout.PropertyField(AgentTransform, new GUIContent("Agent", "Reference for the AI Agent Transform"));
|
|
//EditorGUILayout.PropertyField(agent, new GUIContent("Agent", "Reference for the Nav Mesh Agent"));
|
|
EditorGUI.indentLevel++;
|
|
|
|
EditorGUILayout.PropertyField(StopAgentOn, new GUIContent($"{StopAgentOn.displayName} ({StopAgentOn.arraySize })"), true);
|
|
|
|
if (StopAgentOn.isExpanded && GUILayout.Button(new GUIContent ("Set Default Off States","By Default the AI should not be Active on Fly, Jump or Fall states"), GUILayout.MinWidth(150)))
|
|
{
|
|
M.StopAgentOn = new List<StateID>(3)
|
|
{
|
|
MTools.GetInstance<StateID>("Fall"),
|
|
MTools.GetInstance<StateID>("Jump"),
|
|
MTools.GetInstance<StateID>("Fly")
|
|
};
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
Debug.Log("Stop Agent set to default: [Fall,Jump,Fly]");
|
|
MTools.SetDirty(target);
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
|
|
|
|
M.ValidateAgent();
|
|
|
|
if (!M.AgentTransform)
|
|
{
|
|
EditorGUILayout.HelpBox("There's no Agent found on the hierarchy on this gameobject\nPlease add a NavMesh Agent Component", MessageType.Error);
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void ShowEvents()
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
{
|
|
EditorGUILayout.PropertyField(OnEnabled);
|
|
EditorGUILayout.PropertyField(OnDisabled);
|
|
EditorGUILayout.PropertyField(OnTargetPositionArrived, new GUIContent("On Position Arrived"));
|
|
EditorGUILayout.PropertyField(OnTargetArrived, new GUIContent("On Target Arrived"));
|
|
EditorGUILayout.PropertyField(OnTargetSet, new GUIContent("On New Target Set"));
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
protected GUIStyle Bold(bool tru) => tru ? EditorStyles.boldLabel : EditorStyles.miniBoldLabel;
|
|
|
|
private void ShowDebug()
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUIUtility.labelWidth = 50f;
|
|
EditorGUILayout.PropertyField(debug, new GUIContent("Console"));
|
|
EditorGUILayout.PropertyField(debugGizmos, new GUIContent("Gizmos"));
|
|
EditorGUIUtility.labelWidth = 80f;
|
|
EditorGUILayout.PropertyField(debugStatus, new GUIContent("In-Game Log"));
|
|
EditorGUIUtility.labelWidth = 0f;
|
|
EditorGUILayout.EndHorizontal();
|
|
if (Application.isPlaying)
|
|
{
|
|
|
|
Repaint();
|
|
EditorGUI.BeginDisabledGroup(true);
|
|
EditorGUILayout.PropertyField(targett);
|
|
EditorGUILayout.ObjectField("Next Target", M.NextTarget, typeof(Transform), false);
|
|
EditorGUILayout.Vector3Field("Destination", M.DestinationPosition);
|
|
EditorGUILayout.Vector3Field("AI Direction", M.AIDirection);
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.FloatField("Current Stop Distance", M.StoppingDistance);
|
|
EditorGUILayout.FloatField("Remaining Distance", M.RemainingDistance);
|
|
EditorGUILayout.FloatField("Slow Multiplier", M.SlowMultiplier);
|
|
// EditorGUILayout.FloatField("Circling Around", M.CircleAroundMultiplier);
|
|
EditorGUILayout.Space();
|
|
|
|
|
|
|
|
|
|
EditorGUIUtility.labelWidth = 70;
|
|
EditorGUILayout.BeginHorizontal();
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
EditorGUILayout.ToggleLeft("Target is Moving", M.TargetIsMoving, Bold(M.TargetIsMoving));
|
|
EditorGUILayout.ToggleLeft("Target is AITarget", M.IsAITarget != null, Bold(M.IsAITarget != null));
|
|
EditorGUILayout.ToggleLeft("Target is WayPoint", M.IsWayPoint != null, Bold(M.IsWayPoint != null));
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.ToggleLeft("LookAt Target", M.LookAtTargetOnArrival, Bold(M.LookAtTargetOnArrival));
|
|
EditorGUILayout.ToggleLeft("Auto Next Target", M.AutoNextTarget, Bold(M.AutoNextTarget));
|
|
EditorGUILayout.ToggleLeft("UpdateDestinationPos", M.UpdateDestinationPosition, Bold(M.UpdateDestinationPosition));
|
|
EditorGUILayout.EndVertical();
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
EditorGUILayout.ToggleLeft("Is On Mode", M.IsOnMode, Bold(M.IsOnMode));
|
|
EditorGUILayout.ToggleLeft("Free Move", M.FreeMove, Bold(M.FreeMove));
|
|
EditorGUILayout.ToggleLeft("In OffMesh Link", M.InOffMeshLink, Bold(M.InOffMeshLink));
|
|
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.ToggleLeft("Waiting", M.IsWaiting, Bold(M.IsWaiting));
|
|
EditorGUILayout.ToggleLeft("Has Arrived to Destination", M.HasArrived, Bold(M.HasArrived));
|
|
|
|
EditorGUILayout.ToggleLeft("Active Agent", M.ActiveAgent, Bold(M.ActiveAgent));
|
|
if (M.Agent && M.ActiveAgent)
|
|
{
|
|
EditorGUILayout.ToggleLeft("Agent in NavMesh", M.Agent.isOnNavMesh, Bold(M.Agent.isOnNavMesh));
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
EditorGUIUtility.labelWidth = 0;
|
|
|
|
DrawChildDebug();
|
|
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
protected virtual void DrawChildDebug()
|
|
{}
|
|
|
|
}
|
|
#endif
|
|
#endregion
|
|
} |