959e80cf72
assets upload description.
2370 lines
71 KiB
C#
2370 lines
71 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using RPGCreationKit;
|
|
using UnityEngine.AI;
|
|
using UnityEditor;
|
|
using RPGCreationKit.CellsSystem;
|
|
|
|
namespace RPGCreationKit.AI
|
|
{
|
|
/// <summary>
|
|
/// Defines the current MovementType this AI entity is currently using.
|
|
/// </summary>
|
|
public enum MovementType
|
|
{
|
|
Null = 0,
|
|
UnityNavmesh = 1,
|
|
TraversingLinks = 2,
|
|
CustomPathSystem = 3,
|
|
InActionPoint = 4,
|
|
Offline = 5
|
|
};
|
|
|
|
/// <summary>
|
|
/// Defines the precision of Obstacle Avoidance while the AI entity is traversing a Link.
|
|
/// The more the precision, the more the performances used.
|
|
/// </summary>
|
|
public enum TLinkObstacleAvoidancePrecision
|
|
{
|
|
Full = 0,
|
|
Good = 1,
|
|
Minimum = 2,
|
|
Null = 3
|
|
};
|
|
|
|
public enum SteeringBehaviours
|
|
{
|
|
Seek = 0,
|
|
Arrive = 1,
|
|
Flee = 2,
|
|
FleeAtDistance = 3,
|
|
RigtSideways = 4,
|
|
LeftSideways = 5
|
|
}
|
|
|
|
public class AIMovements : AITalkative
|
|
{
|
|
public BodyData bodyData;
|
|
|
|
//[Header("General Settings")]
|
|
public bool usesMovements = true; // AI makes use of movements
|
|
public bool usesOfflineMode = true; // Make the AI go in Offline mode instead of disabling it when the Persistent Reference is being disabled
|
|
public bool usesTlinkUnstuck = true; // Attempts to unstuck the AI if during TLink mode it detects itself to be stucked
|
|
|
|
[Space(5)]
|
|
|
|
//[Header("Components")]
|
|
public Collider physicalCollider; // The physical collider of the AI
|
|
public Collider interactableCollider; // The collider that defines the Interact Zone of the AI
|
|
|
|
public NavMeshAgent agent; // The agent component of this AI
|
|
public Rigidbody m_Rigidbody; // The Rigidbody of this AIa
|
|
public NavMeshPath navmeshPath; // The NavMeshPath the AI will generate and calculate at runtime while being in UnityNavmesh mode
|
|
public Inventory Inv;
|
|
|
|
[Space(5)]
|
|
|
|
//[Header("Status")]
|
|
public MovementType movementType; // Defines the movement type of the AI
|
|
public bool isWalking = true; // Defines wheter the AI is Walking (true) or running (false)
|
|
bool isTraversingLink = false; // True if the AI is traversing a link
|
|
bool isRotating = false; // True if the AI is rotating
|
|
float agentAngularSpeed;
|
|
|
|
bool isFollowingMainTarget = false; // True if the AI is simply following the MainTarget assigned
|
|
public bool isFollowingAIPath = false; // True if the AI is following an AIPath
|
|
|
|
[Space(5)]
|
|
|
|
public bool canSeeTarget = false;
|
|
public bool hasCompletlyLostTarget = false; // this is set to true if the agent reached the last known position of the target and doesn't know where to go from there
|
|
|
|
//[Header("Shoulds")]
|
|
public bool overrideShouldFollowMainTargetActive = false; // If this is enabled, overrideShouldFollowMainTarget will be used instead of "shouldFollowMainTarget"
|
|
public bool overrideShouldFollowMainTarget = false;
|
|
|
|
public bool shouldFollowMainTarget = false;
|
|
public bool shouldFollowOnlyIfCanSee = false;
|
|
public bool lookAtTarget = false;
|
|
|
|
public bool shouldFollowAIPath = false;
|
|
public bool shouldWander = false;
|
|
public bool shouldUseNPCActionPoint = false;
|
|
|
|
[Space(5)]
|
|
|
|
//[Header("Movements Components")]
|
|
|
|
public Transform mainTarget; // The main Target this AI is following
|
|
public bool hasMainTarget = false;
|
|
|
|
public Vector3 targetVector;
|
|
public bool shouldFollowTargetVector = false;
|
|
|
|
public AICustomPath aiPath; // The Path this AI is following
|
|
public NPCActionPoint actionPoint;
|
|
|
|
public bool lookATransformIfStopped = false; // If the AI stops, should he look at something? This can be useful if set at runtime, but can cause issues if forgotten on true
|
|
public Transform directionToLookIfStopped; // If the AI has to look at something if he's stopped, define that something
|
|
|
|
public bool lookAtVector3IfStopped = false;
|
|
public Vector3 vector3ToLookAtIfStopped;
|
|
|
|
[Space(5)]
|
|
|
|
//[Header("Movements Settings")]
|
|
public SteeringBehaviours selectedSteeringBehaviour;
|
|
public Deceleration arriveSteeringBehaviourDeceleration;
|
|
public float fleeAtDistanceValue = 20f;
|
|
|
|
|
|
public float currentSpeed = 0.0f; // Defines the current speed of the AI
|
|
public float maxSpeed = 0.0f; // Defines the maxSpeed the AI can travel in the current mode (movementType, isWalking...)
|
|
|
|
public bool invertAIPath = false; // Set at true if the AI has to follow its AIPath at the inverse
|
|
public int aiPathCurrentIndex = 0; // Defines the Index of the AIPath the AI is currently at
|
|
public bool aiPathResumeFromCurrentIndex = false;
|
|
|
|
public bool isStopped = false;
|
|
public float stoppingHalt = 0.1f; // The smoothness applied to stopping this Entity
|
|
public float stoppingDistance = 1.4f; // Defines the distance at which the current target will be set as reached
|
|
|
|
[SerializeField] public float generalRotationSpeed = 3f; // Defines the general speed of rotation of this AI
|
|
[SerializeField] float rotationThreshold = 5f; // The value of difference between the oldRotPos and the current RotPos to say if the AI is rotating or not
|
|
|
|
float walkModifier; // Caches .5f or 1f as 'max' values for the blendtrees of the Animator.
|
|
private Vector3 velocity = Vector3.zero; // Just a storage for the velocity
|
|
Vector3 oldEulerAngles; // Stores the old Euler Angles to determine if the AI has/had to rotate
|
|
|
|
public bool cachedMainTarget = false;
|
|
public Vector3 cachedMainTargetPos;
|
|
|
|
[Space(5)]
|
|
|
|
//[Header("Traversing Link Specific")]
|
|
public TLinkObstacleAvoidancePrecision tLinkObstacleAvoidancePrecision = TLinkObstacleAvoidancePrecision.Good;
|
|
|
|
public bool chaseTargetDuringTraversing = true; // If set to true, while in TLink mode the AI will keep following its target
|
|
public bool rotateToFaceTargetDuringTraversing = true; // If set to true, while in TLink mode the AI will lookAt its target
|
|
public bool hasToRandomlySearchTarget = false;
|
|
|
|
[Space(5)]
|
|
|
|
//[Header("Traversing Link Specific - Obstacle Avoidance")]
|
|
|
|
// Obstacle avoidance in TraversingLinks mode
|
|
public LayerMask obstacleAvoidanceMask; // Defines the Objects that are an Obstacles for the AI
|
|
float obstacleDistance = 4f; // Defines how far is too close for an obstacle
|
|
float leftRightOpeningMult = 1.5f; // Defines the opening of the raycasts checks that will be performed in order to attempt to detect the most cheap way to get around the obstacle
|
|
float rotationDivider = 35; // Defines how fast the AI will rotate if he's facing an obstacle (High value -> Less speed)
|
|
float lerpingToOriginal = .1f; // Defines how fast the AI will return to it's original trajectory after it was affected by ObstacleAvoidance
|
|
|
|
// Raycasthit for obstacle avoidance
|
|
RaycastHit feetLeftHit;
|
|
RaycastHit feetForwardHit;
|
|
RaycastHit feetRightHit;
|
|
|
|
RaycastHit bodyLeftHit;
|
|
RaycastHit bodyForwardHit;
|
|
RaycastHit bodyRightHit;
|
|
|
|
RaycastHit headLeftHit;
|
|
RaycastHit headForwardHit;
|
|
RaycastHit headRightHit;
|
|
|
|
// Information about obstacle avoidance
|
|
bool leftOccupied = false;
|
|
bool forwardOccupied = false;
|
|
bool rightOccupied = false;
|
|
GameObject leftHit;
|
|
GameObject rightHit;
|
|
|
|
[Space(5)]
|
|
|
|
[Header("Traversing Link Specific - Unstuck")]
|
|
|
|
// Variables that manages the unstuck system during TLinkMode
|
|
float tLinkStuckCounter = 0.0f; // Defines the time this AI was seen as Stuck
|
|
float tLinkStuckTime = 3f; // Defines the max time this AI can be seen as Stuck before attempting to unstuck it
|
|
float tLinkUnstuckForce = 25f; // Defines the forece to apply to the AI in order to attempt the unstuck
|
|
Vector3 tLinkStuckThreshold = new Vector3(0.25f, 0.25f, 0.25f); // Defines the values in which the AI will be seen as stuck
|
|
|
|
public string startingCellID;
|
|
public string runtimeStartingCell;
|
|
public string cellIDOfLastSaved;
|
|
bool getcellinfo = true;
|
|
/// Should the NPC check if he's changing cells? Usually yes if it's loaded in an exterior, no if he's unloaded or inside an interior.
|
|
public bool GetCellInfo
|
|
{
|
|
get
|
|
{
|
|
return getcellinfo;
|
|
}
|
|
set
|
|
{
|
|
getcellinfo = value;
|
|
|
|
if (getcellinfo)
|
|
StartCoroutine(GetMyCellInfo());
|
|
else
|
|
StopCoroutine(GetMyCellInfo());
|
|
}
|
|
}
|
|
|
|
float getCellInfoTimer = 5.0f;
|
|
|
|
|
|
/// <summary>
|
|
/// When the AI walks he could encounter a door, if he's in front of a door, this reference will be set.
|
|
/// </summary>
|
|
Door doorInTheWay;
|
|
|
|
[Space(5)]
|
|
|
|
[Header("Debug")]
|
|
public bool generalDebug = true;
|
|
public bool debugPath = true;
|
|
|
|
public virtual void Start()
|
|
{
|
|
if(string.IsNullOrEmpty(runtimeStartingCell))
|
|
if(myCellInfo != null)
|
|
runtimeStartingCell = myCellInfo.cell.ID;
|
|
|
|
navmeshPath = new NavMeshPath();
|
|
AdjustCurrentSpeed();
|
|
agent.autoTraverseOffMeshLink = false;
|
|
oldEulerAngles = transform.rotation.eulerAngles;
|
|
|
|
agentAngularSpeed = agent.angularSpeed;
|
|
|
|
if(agent.isOnNavMesh)
|
|
agent.CalculatePath(agent.transform.position + (transform.forward * 0.1f), navmeshPath);
|
|
|
|
GetCellInfo = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wait for scene to be loaded and get cellinfo
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerator GetMyCellInfo()
|
|
{
|
|
while (GetCellInfo)
|
|
{
|
|
yield return new WaitForSecondsRealtime(.5f);
|
|
yield return new WaitForEndOfFrame();
|
|
|
|
while (WorldManager.instance.isLoading || GameStatus.instance.AnyLoading())
|
|
yield return null;
|
|
|
|
if (WorldManager.instance.currentWorldspace.worldSpaceType == Worldspace.WorldSpaceType.Exterior)
|
|
{
|
|
Cell checkedCenterCell;
|
|
float shortestDistance = 99999.999f;
|
|
int shortestDistanceID = 0;
|
|
for (int i = 0; i < WorldManager.instance.currentWorldspace.cells.Length; i++)
|
|
{
|
|
float curDistance = Vector3.Distance(agent.transform.position, WorldManager.instance.currentWorldspace.cells[i].cellInWorldCoordinates);
|
|
if (curDistance < shortestDistance)
|
|
{
|
|
shortestDistance = curDistance;
|
|
shortestDistanceID = i;
|
|
}
|
|
}
|
|
|
|
checkedCenterCell = WorldManager.instance.currentWorldspace.cells[shortestDistanceID];
|
|
|
|
try
|
|
{
|
|
myCellInfo = UnityEngine.SceneManagement.SceneManager.GetSceneByPath(checkedCenterCell.sceneRef.ScenePath).GetCellInformation();
|
|
}
|
|
catch
|
|
{
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
myCellInfo = UnityEngine.SceneManagement.SceneManager.GetSceneByPath(WorldManager.instance.currentCenterCell.sceneRef.ScenePath).GetCellInformation();
|
|
}
|
|
|
|
while (WorldManager.instance.isLoading || GameStatus.instance.AnyLoading())
|
|
yield return null;
|
|
|
|
yield return new WaitForSecondsRealtime(getCellInfoTimer);
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
public void GetMyCellInfoImmediate()
|
|
{
|
|
if(WorldManager.instance.currentWorldspace.worldSpaceType == Worldspace.WorldSpaceType.Exterior)
|
|
{
|
|
Cell checkedCenterCell;
|
|
float shortestDistance = 99999.999f;
|
|
int shortestDistanceID = 0;
|
|
for (int i = 0; i < WorldManager.instance.currentWorldspace.cells.Length; i++)
|
|
{
|
|
float curDistance = Vector3.Distance(agent.transform.position, WorldManager.instance.currentWorldspace.cells[i].cellInWorldCoordinates);
|
|
if (curDistance < shortestDistance)
|
|
{
|
|
shortestDistance = curDistance;
|
|
shortestDistanceID = i;
|
|
}
|
|
}
|
|
|
|
checkedCenterCell = WorldManager.instance.currentWorldspace.cells[shortestDistanceID];
|
|
|
|
myCellInfo = UnityEngine.SceneManagement.SceneManager.GetSceneByPath(checkedCenterCell.sceneRef.ScenePath).GetCellInformation();
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
myCellInfo = UnityEngine.SceneManagement.SceneManager.GetSceneByPath(WorldManager.instance.currentCenterCell.sceneRef.ScenePath).GetCellInformation();
|
|
GetCellInfo = false;
|
|
}
|
|
catch
|
|
{
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void Update()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
if (!isAlive || isUnconscious)
|
|
return;
|
|
|
|
// Override states
|
|
if (overrideShouldFollowMainTargetActive)
|
|
shouldFollowMainTarget = overrideShouldFollowMainTarget;
|
|
|
|
CheckRotation();
|
|
HandleAnimations();
|
|
CheckForStuckStatus_TLink();
|
|
|
|
hasMainTarget = (mainTarget != null) ? true : false;
|
|
}
|
|
|
|
public bool navmeshPathFinished = false;
|
|
public bool dialogueLookAt = false;
|
|
public bool fixedFollowNavmeshpathRot;
|
|
[AIInvokable]
|
|
public void UpdateNavMeshPathStatus()
|
|
{
|
|
if (navmeshPath.corners.Length >= 2)
|
|
{
|
|
if(navmeshPath.corners.Length == 2)
|
|
navmeshPathFinished = (Vector3.Distance(agent.transform.position, navmeshPath.corners[1]) < stoppingDistance);
|
|
else
|
|
navmeshPathFinished = (Vector3.Distance(agent.transform.position, navmeshPath.corners[navmeshPath.corners.Length-1]) < stoppingDistance);
|
|
|
|
}
|
|
else
|
|
navmeshPathFinished = true;
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
if (!isAlive || isUnconscious)
|
|
return;
|
|
|
|
HandleMovements();
|
|
TLinkObstacleAvoidance();
|
|
|
|
if (isInConversation && dialogueLookAt && speakingTo != null && !isUsingActionPoint && !fixingInPlaceForActionPoint)
|
|
{
|
|
var lookPos = speakingTo.GetEntityGameObject().transform.position - transform.position;
|
|
lookPos.y = 0;
|
|
var lookAt = Quaternion.LookRotation(lookPos);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookAt, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
|
|
if(transformMovingToActionPoint)
|
|
{
|
|
transform.position = Vector3.MoveTowards(transform.position, actionPoint.pivotPoint.position, 2.5f * Time.deltaTime);
|
|
transform.rotation = Quaternion.RotateTowards(transform.rotation, actionPoint.pivotPoint.rotation, 50 * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
|
|
[ContextMenu("UpdatePathFromGUID")]
|
|
void PathCrossScene()
|
|
{
|
|
WaitAndUpdatePathFromGUID();
|
|
}
|
|
|
|
|
|
/// Manage everything reguarding Crosscene referencing and GUID Components
|
|
#region Crossscene and GUID
|
|
/// <summary>
|
|
/// Attempts to set the MainTarget to the GUID corresponding performing different checks over time.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public void WaitAndUpdateTargetFromGUID()
|
|
{
|
|
StopCoroutine(WaitAndUpdateTargetFromGUIDTask());
|
|
StartCoroutine(WaitAndUpdateTargetFromGUIDTask());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immediatly set the MainTarget to the GUID corresponding. It can fail if it is used while the scene where the GUIDReference is being loaded.
|
|
/// </summary>
|
|
[ContextMenu("UpdateTargetFromGUID"), AIInvokable]
|
|
public void UpdateTargetFromGUID()
|
|
{
|
|
GameObject rTest = iGUIDReferences.TryToGetReferenceGameObject(AIGUIDReferenceType.MainTarget);
|
|
if (rTest != null)
|
|
{
|
|
SetTarget(rTest);
|
|
}
|
|
}
|
|
|
|
private IEnumerator WaitAndUpdateTargetFromGUIDTask()
|
|
{
|
|
bool set = false;
|
|
GameObject rTest = null;
|
|
while (!set)
|
|
{
|
|
rTest = iGUIDReferences.TryToGetReferenceGameObject(AIGUIDReferenceType.MainTarget);
|
|
if (rTest != null)
|
|
{
|
|
SetTarget(rTest);
|
|
set = true;
|
|
}
|
|
else
|
|
yield return new WaitForEndOfFrame();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Attempts to set the MainTarget to the GUID corresponding performing different checks over time.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public void WaitAndUpdatePathFromGUID()
|
|
{
|
|
StopCoroutine(WaitAndUpdatePathFromGUIDTask());
|
|
StartCoroutine(WaitAndUpdatePathFromGUIDTask());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immediatly set the MainTarget to the GUID corresponding. It can fail if it is used while the scene where the GUIDReference is being loaded.
|
|
/// </summary>
|
|
public void UpdatePathFromGUID()
|
|
{
|
|
GameObject rTest = iGUIDReferences.TryToGetReferenceGameObject(AIGUIDReferenceType.CurrentPath);
|
|
if (rTest != null)
|
|
{
|
|
aiPath = rTest.GetComponent<AICustomPath>();
|
|
}
|
|
}
|
|
|
|
private IEnumerator WaitAndUpdatePathFromGUIDTask()
|
|
{
|
|
bool set = false;
|
|
GameObject rTest = null;
|
|
while (!set)
|
|
{
|
|
rTest = iGUIDReferences.TryToGetReferenceGameObject(AIGUIDReferenceType.CurrentPath);
|
|
|
|
if (rTest != null)
|
|
{
|
|
aiPath = rTest.GetComponent<AICustomPath>();
|
|
set = true;
|
|
}
|
|
else
|
|
yield return new WaitForSeconds(.25f);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// Methods that helps setting the current target, path and thing to do
|
|
#region Movements Setters
|
|
|
|
public void SetTarget(GameObject _target, bool updateGUID = true)
|
|
{
|
|
mainTarget = _target.transform;
|
|
|
|
if(updateGUID && iGUIDReferences != null)
|
|
iGUIDReferences.TryToSetReference(_target, AIGUIDReferenceType.MainTarget);
|
|
}
|
|
|
|
public void ClearTarget(bool updateGUID = true)
|
|
{
|
|
mainTarget = null;
|
|
|
|
if (updateGUID)
|
|
iGUIDReferences.ClearReference(AIGUIDReferenceType.MainTarget);
|
|
}
|
|
|
|
public void SetCustomPath(AICustomPath _path = null, bool updateGUID = true)
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
if (_path != null)
|
|
aiPath = _path;
|
|
|
|
if (aiPath == null)
|
|
{
|
|
Debug.LogWarning("Tried to call \"UseCustomPath()\" on AI: (ID:\"" + entityID + "\") but no AICustomPath has been given or was present.");
|
|
UseUnitysNavmesh(); // Fallback
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
iGUIDReferences.TryToSetReference(_path.gameObject, AIGUIDReferenceType.CurrentPath);
|
|
}
|
|
}
|
|
|
|
public void ClearCustomPath(bool updateGUID = true)
|
|
{
|
|
aiPath = null;
|
|
|
|
if (updateGUID)
|
|
iGUIDReferences.ClearReference(AIGUIDReferenceType.CurrentPath);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Movements
|
|
/// <summary>
|
|
/// Handles the different types of movement this AI entity can use.
|
|
/// </summary>
|
|
public void HandleMovements()
|
|
{
|
|
if(movementType == MovementType.UnityNavmesh)
|
|
{
|
|
if (!agent.enabled)
|
|
return;
|
|
|
|
isStopped = (agent.velocity.magnitude < RCKSettings.STOPPING_DETECTION_THRESHOLD);
|
|
|
|
if (canSeeTarget)
|
|
{
|
|
hasCompletlyLostTarget = false;
|
|
hasToRandomlySearchTarget = false;
|
|
}
|
|
|
|
if (!isTraversingLink) // If we're on a navmesh
|
|
{
|
|
// Determine the cases where the Path needs to be updated
|
|
if ((shouldFollowMainTarget && canSeeTarget && shouldFollowOnlyIfCanSee) || (shouldFollowMainTarget && !shouldFollowOnlyIfCanSee))
|
|
{
|
|
// Update for offline mode
|
|
if (usesOfflineMode && mainTarget != null)
|
|
{
|
|
cachedMainTargetPos = (mainTarget.position);
|
|
cachedMainTarget = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(movementType == MovementType.Offline && shouldFollowMainTarget && usesOfflineMode)
|
|
{
|
|
if(cachedMainTarget)
|
|
{
|
|
// If the target isn't reached yet
|
|
if (Vector3.Distance(transform.position, cachedMainTargetPos) >= stoppingDistance)
|
|
{
|
|
isStopped = false;
|
|
// Moves the Transform to the cached position
|
|
transform.position = Vector3.MoveTowards(transform.position, cachedMainTargetPos, maxSpeed * Time.deltaTime);
|
|
}
|
|
else
|
|
isStopped = true;
|
|
}
|
|
}
|
|
else if(movementType == MovementType.Offline && shouldFollowTargetVector && usesOfflineMode)
|
|
{
|
|
// If the target isn't reached yet
|
|
if (Vector3.Distance(transform.position, targetVector) >= stoppingDistance)
|
|
{
|
|
isStopped = false;
|
|
// Moves the Transform to the cached position
|
|
transform.position = Vector3.MoveTowards(transform.position, targetVector, maxSpeed * Time.deltaTime);
|
|
}
|
|
else
|
|
isStopped = true;
|
|
}
|
|
else if(movementType == MovementType.TraversingLinks)
|
|
{
|
|
isStopped = m_Rigidbody.velocity.magnitude < RCKSettings.STOPPING_DETECTION_THRESHOLD;
|
|
}
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void FollowTargetThroughNavmeshPath()
|
|
{
|
|
if(mainTarget == null)
|
|
{
|
|
Debug.LogWarning("Called FollowTargetThroughNavmeshPath() but no MainTarget has been set");
|
|
return;
|
|
}
|
|
|
|
if (!shouldFollowMainTarget)
|
|
return;
|
|
|
|
bool closestPoint = false;
|
|
if (agent.enabled && agent.isOnNavMesh)
|
|
{
|
|
agent.CalculatePath(mainTarget.position, navmeshPath);
|
|
|
|
if (navmeshPath.status == NavMeshPathStatus.PathInvalid)
|
|
{
|
|
NavMeshHit myNavHit;
|
|
if (NavMesh.SamplePosition(mainTarget.transform.position, out myNavHit, Mathf.Infinity, -1))
|
|
{
|
|
//Handles.Label(myNavHit.position, new GUIContent("Closest"));
|
|
agent.CalculatePath(myNavHit.position, navmeshPath);
|
|
closestPoint = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
float distanceAgentToTarget = (Vector3.Distance(agent.transform.position, mainTarget.position));
|
|
|
|
// Stopping distance check
|
|
if ((distanceAgentToTarget >= stoppingDistance && !closestPoint
|
|
&& selectedSteeringBehaviour != SteeringBehaviours.Flee && selectedSteeringBehaviour != SteeringBehaviours.FleeAtDistance)
|
|
|| (selectedSteeringBehaviour == SteeringBehaviours.RigtSideways || selectedSteeringBehaviour == SteeringBehaviours.LeftSideways)
|
|
|| (closestPoint && navmeshPath.corners.Length >= 2 && (Vector3.Distance(agent.transform.position, navmeshPath.corners[1]) >= stoppingDistance)))
|
|
{
|
|
// If there is room to walk
|
|
if (navmeshPath.corners.Length >= 2)
|
|
{
|
|
switch (selectedSteeringBehaviour)
|
|
{
|
|
case SteeringBehaviours.Seek:
|
|
agent.velocity = Seek(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.Arrive:
|
|
agent.velocity = Arrive(navmeshPath.corners[1], arriveSteeringBehaviourDeceleration);
|
|
break;
|
|
|
|
case SteeringBehaviours.RigtSideways:
|
|
agent.velocity = RightSideway(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.LeftSideways:
|
|
agent.velocity = LeftSideway(navmeshPath.corners[1]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if(selectedSteeringBehaviour == SteeringBehaviours.Flee || selectedSteeringBehaviour == SteeringBehaviours.FleeAtDistance)
|
|
{
|
|
// If there is room to walk
|
|
if (navmeshPath.corners.Length >= 2)
|
|
{
|
|
switch (selectedSteeringBehaviour)
|
|
{
|
|
case SteeringBehaviours.Flee:
|
|
agent.velocity = Flee(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.FleeAtDistance:
|
|
agent.velocity = FleeAtDistance(navmeshPath.corners[1], fleeAtDistanceValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Stop the agent
|
|
agent.velocity = Vector3.SmoothDamp(agent.velocity, Vector3.zero, ref velocity, stoppingHalt);
|
|
|
|
if (lookATransformIfStopped && directionToLookIfStopped != null && agent.velocity == Vector3.zero && !lookAtTarget)
|
|
{
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, directionToLookIfStopped.rotation, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
|
|
if (!lookATransformIfStopped && lookAtVector3IfStopped)
|
|
{
|
|
Vector3 newDirection = (vector3ToLookAtIfStopped - transform.position).normalized;
|
|
newDirection.y = 0;
|
|
Quaternion lookRotation = Quaternion.LookRotation(newDirection);
|
|
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, generalRotationSpeed * Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
if (!isTraversingLink && !isInConversation)
|
|
{
|
|
// Manage rotation
|
|
if (distanceAgentToTarget <= RCKSettings.DISTANCE_WHEN_NPCS_START_LOOK_AT)
|
|
{
|
|
if (lookAtTarget && mainTarget != null)
|
|
{
|
|
// Rotate torwards target
|
|
var lookAt = Quaternion.LookRotation(mainTarget.position - transform.position);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookAt, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
}
|
|
else if (navmeshPath.corners.Length >= 2 && navmeshPath.corners[1] != null)
|
|
{
|
|
// Rotate towards the direction of movement
|
|
var lookAt = Quaternion.LookRotation(navmeshPath.corners[1] - transform.position);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookAt, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
else
|
|
{
|
|
// Rotate in direction of the movement (?)
|
|
var lookAt = Quaternion.LookRotation((transform.forward * 2) - transform.position);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookAt, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void FollowNavmeshPath()
|
|
{
|
|
if (navmeshPath.corners.Length >= 2 && navmeshPath.corners.Length - 1 >= 0)
|
|
{
|
|
// Walk trough the path
|
|
if(agent.isOnNavMesh)
|
|
agent.CalculatePath(navmeshPath.corners[navmeshPath.corners.Length - 1], navmeshPath);
|
|
|
|
float distanceAgentFirstCorner = 0;
|
|
|
|
try
|
|
{
|
|
Vector3.Distance(agent.transform.position, navmeshPath.corners[navmeshPath.corners.Length - 1]);
|
|
}
|
|
catch { }
|
|
|
|
// stopping distance check
|
|
if (distanceAgentFirstCorner >= stoppingDistance ||
|
|
(selectedSteeringBehaviour == SteeringBehaviours.RigtSideways || selectedSteeringBehaviour == SteeringBehaviours.LeftSideways)) // THOSE BEHAVIOURS MUST BE PLAYED EVEN IF WE REACHED THE STOPPING DISTANCE
|
|
{
|
|
// If there is room to walk
|
|
if (navmeshPath.corners.Length >= 2)
|
|
{
|
|
switch (selectedSteeringBehaviour)
|
|
{
|
|
case SteeringBehaviours.Seek:
|
|
agent.velocity = Seek(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.Arrive:
|
|
agent.velocity = Arrive(navmeshPath.corners[1], arriveSteeringBehaviourDeceleration);
|
|
break;
|
|
|
|
case SteeringBehaviours.Flee:
|
|
agent.velocity = Flee(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.FleeAtDistance:
|
|
agent.velocity = FleeAtDistance(navmeshPath.corners[1], fleeAtDistanceValue);
|
|
break;
|
|
|
|
case SteeringBehaviours.RigtSideways:
|
|
agent.velocity = RightSideway(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.LeftSideways:
|
|
agent.velocity = LeftSideway(navmeshPath.corners[1]);
|
|
break;
|
|
}
|
|
|
|
cachedMainTargetPos = (navmeshPath.corners[navmeshPath.corners.Length - 1]);
|
|
cachedMainTarget = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Stop the agent
|
|
agent.velocity = Vector3.SmoothDamp(agent.velocity, Vector3.zero, ref velocity, stoppingHalt);
|
|
|
|
if (lookATransformIfStopped && directionToLookIfStopped != null && agent.velocity == Vector3.zero && !lookAtTarget)
|
|
{
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, directionToLookIfStopped.rotation, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
|
|
if(!lookATransformIfStopped && lookAtVector3IfStopped)
|
|
{
|
|
Vector3 newDirection = (vector3ToLookAtIfStopped - transform.position).normalized;
|
|
newDirection.y = 0;
|
|
|
|
Quaternion lookRotation = Quaternion.LookRotation(newDirection);
|
|
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, generalRotationSpeed * Time.deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isTraversingLink && !isInConversation)
|
|
{
|
|
if (navmeshPath != null && navmeshPath.corners.Length > 1)
|
|
{
|
|
// Rotate towards the direction of movement
|
|
var lookAt = Quaternion.LookRotation(navmeshPath.corners[1] - transform.position);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookAt, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
else
|
|
{
|
|
// Rotate in direction of the movement (?)
|
|
var lookAt = Quaternion.LookRotation((transform.forward * 2) - transform.position);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookAt, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void SetLookAtTarget(bool setLookAt)
|
|
{
|
|
if (!lookAtTarget && setLookAt)
|
|
{
|
|
lookAtTarget = true;
|
|
agent.angularSpeed = 0;
|
|
}
|
|
else if(!setLookAt && lookAtTarget)
|
|
{
|
|
lookAtTarget = false;
|
|
agent.angularSpeed = agentAngularSpeed;
|
|
}
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void FollowTargetVector()
|
|
{
|
|
// PREVIOUS ONE
|
|
shouldFollowTargetVector = true;
|
|
|
|
if (!agent.enabled || !agent.isOnNavMesh)
|
|
return;
|
|
|
|
agent.CalculatePath(targetVector, navmeshPath);
|
|
|
|
bool closestPoint = false;
|
|
if (navmeshPath.status == NavMeshPathStatus.PathInvalid)
|
|
{
|
|
NavMeshHit myNavHit;
|
|
if (NavMesh.SamplePosition(targetVector, out myNavHit, Mathf.Infinity, -1))
|
|
{
|
|
//Handles.Label(myNavHit.position, new GUIContent("Closest"));
|
|
agent.CalculatePath(myNavHit.position, navmeshPath);
|
|
closestPoint = true;
|
|
}
|
|
}
|
|
|
|
// Stopping distance check
|
|
if ((Vector3.Distance(agent.transform.position, targetVector) >= stoppingDistance && !closestPoint
|
|
&& selectedSteeringBehaviour != SteeringBehaviours.Flee && selectedSteeringBehaviour != SteeringBehaviours.FleeAtDistance)
|
|
|| (selectedSteeringBehaviour == SteeringBehaviours.RigtSideways || selectedSteeringBehaviour == SteeringBehaviours.LeftSideways)
|
|
|| (closestPoint && navmeshPath.corners.Length >= 2 && (Vector3.Distance(agent.transform.position, navmeshPath.corners[1]) >= stoppingDistance)))
|
|
{
|
|
// If there is room to walk
|
|
if (navmeshPath.corners.Length >= 2)
|
|
{
|
|
switch (selectedSteeringBehaviour)
|
|
{
|
|
case SteeringBehaviours.Seek:
|
|
agent.velocity = Seek(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.Arrive:
|
|
agent.velocity = Arrive(navmeshPath.corners[1], arriveSteeringBehaviourDeceleration);
|
|
break;
|
|
|
|
case SteeringBehaviours.RigtSideways:
|
|
agent.velocity = RightSideway(navmeshPath.corners[1]);
|
|
break;
|
|
|
|
case SteeringBehaviours.LeftSideways:
|
|
agent.velocity = LeftSideway(navmeshPath.corners[1]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Stop the agent
|
|
agent.velocity = Vector3.SmoothDamp(agent.velocity, Vector3.zero, ref velocity, stoppingHalt);
|
|
|
|
if (lookATransformIfStopped && directionToLookIfStopped != null && agent.velocity == Vector3.zero && !lookAtTarget)
|
|
{
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, directionToLookIfStopped.rotation, Time.deltaTime * generalRotationSpeed);
|
|
}
|
|
|
|
if (!lookATransformIfStopped && lookAtVector3IfStopped)
|
|
{
|
|
Vector3 newDirection = (vector3ToLookAtIfStopped - transform.position).normalized;
|
|
newDirection.y = 0;
|
|
|
|
Quaternion lookRotation = Quaternion.LookRotation(newDirection);
|
|
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, generalRotationSpeed * Time.deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void SetLookAtVector3IfStopped(Vector3 _vector)
|
|
{
|
|
vector3ToLookAtIfStopped = _vector;
|
|
lookAtVector3IfStopped = true;
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void ClearLookAtVector3IfStopped()
|
|
{
|
|
lookAtVector3IfStopped = false;
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void GetRandomTargetVectorFromNavmesh(float radius)
|
|
{
|
|
targetVector = PickRandomNavMeshPoint(radius);
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void SetTargetVector(Vector3 vector)
|
|
{
|
|
targetVector = vector;
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void GetRandomTargetVectorFromNavmeshWithCenter(float radius, Vector3 center)
|
|
{
|
|
targetVector = PickRandomNavMeshPoint(radius, center);
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void TargetGetRandomTargetVectorFromNavmesh(float radius)
|
|
{
|
|
targetVector = PickRandomNavMeshPoint(radius, mainTarget);
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void TargetGetOnlyReachableRandomTargetVectorFromNavmesh(float radius)
|
|
{
|
|
shouldFollowTargetVector = false;
|
|
StartCoroutine(GetRandomTargetVectorReachableTask(radius));
|
|
}
|
|
|
|
public bool isOnTargetVector;
|
|
[AIInvokable]
|
|
public bool IsOnTargetVector()
|
|
{
|
|
isOnTargetVector = (Vector3.Distance(transform.position, targetVector) <= stoppingDistance);
|
|
return isOnTargetVector;
|
|
}
|
|
|
|
private Vector3 PickRandomNavMeshPoint(float radius, Transform center = null)
|
|
{
|
|
if (center == null)
|
|
center = this.transform;
|
|
|
|
Vector3 randomDirection = Random.insideUnitSphere * radius;
|
|
randomDirection += center.position;
|
|
NavMeshHit hit;
|
|
Vector3 finalPosition = Vector3.zero;
|
|
if (NavMesh.SamplePosition(randomDirection, out hit, radius, NavMesh.AllAreas))
|
|
{
|
|
finalPosition = hit.position;
|
|
}
|
|
else
|
|
return targetVector;
|
|
|
|
|
|
return finalPosition;
|
|
}
|
|
|
|
private IEnumerator GetRandomTargetVectorReachableTask(float radius)
|
|
{
|
|
bool found = false;
|
|
NavMeshPath path = new NavMeshPath();
|
|
|
|
while(!found)
|
|
{
|
|
if (!agent.isOnNavMesh)
|
|
yield return null;
|
|
|
|
targetVector = PickRandomNavMeshPoint(radius);
|
|
|
|
try
|
|
{
|
|
if(agent.isOnNavMesh)
|
|
agent.CalculatePath(targetVector, path);
|
|
|
|
}
|
|
catch { }
|
|
|
|
if (path.status == NavMeshPathStatus.PathPartial || path.status == NavMeshPathStatus.PathInvalid)
|
|
found = false;
|
|
else
|
|
found = true;
|
|
|
|
yield return null;
|
|
}
|
|
|
|
shouldFollowTargetVector = true;
|
|
}
|
|
|
|
private Vector3 PickRandomNavMeshPoint(float radius, Vector3 center)
|
|
{
|
|
|
|
Vector3 randomDirection = Random.insideUnitSphere * radius;
|
|
randomDirection += center;
|
|
NavMeshHit hit;
|
|
Vector3 finalPosition = Vector3.zero;
|
|
if (NavMesh.SamplePosition(randomDirection, out hit, radius, NavMesh.AllAreas))
|
|
{
|
|
finalPosition = hit.position;
|
|
}
|
|
else
|
|
return targetVector;
|
|
|
|
|
|
return finalPosition;
|
|
}
|
|
|
|
|
|
public void CheckRotation()
|
|
{
|
|
// Remove situations where the rotation animation DOESN'T have to be applied
|
|
if (isUsingActionPoint)
|
|
{
|
|
isRotating = false;
|
|
return;
|
|
}
|
|
|
|
if (Vector3.Distance(oldEulerAngles, transform.rotation.eulerAngles) < rotationThreshold)
|
|
{
|
|
//NO ROTATION
|
|
isRotating = false;
|
|
}
|
|
else
|
|
{
|
|
oldEulerAngles = transform.rotation.eulerAngles;
|
|
isRotating = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This methods updates
|
|
/// </summary>
|
|
[AIInvokable]
|
|
public void SnapshotTargetPosition()
|
|
{
|
|
if(agent.isOnNavMesh)
|
|
agent.CalculatePath(mainTarget.position, navmeshPath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controls the movements of the AI entity while in TraversingLinks mode
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
IEnumerator TraverseNormalSpeed()
|
|
{
|
|
if (!usesMovements)
|
|
yield return null;
|
|
|
|
currentTLinkTargetVector = Vector3.zero;
|
|
Vector3 selectedDistance = (shouldFollowTargetVector) ? targetVector : (mainTarget != null) ? mainTarget.position : Vector3.zero;
|
|
|
|
while (isTraversingLink)
|
|
{
|
|
// If has a target and chasing it
|
|
if ((chaseTargetDuringTraversing && mainTarget != null) || shouldFollowTargetVector)
|
|
{
|
|
//Vector3 chasingVector = Vector3.MoveTowards(agent.transform.position, mainTarget.position, ((currentSpeed + maxSpeed) / 2) * Time.deltaTime);
|
|
selectedDistance = (shouldFollowTargetVector) ? targetVector : mainTarget.position;
|
|
|
|
if (Vector3.Distance(agent.transform.position, selectedDistance) >= stoppingDistance)
|
|
{
|
|
cachedMainTargetPos = (selectedDistance);
|
|
cachedMainTarget = true;
|
|
|
|
Vector3 targetVector = (selectedDistance - agent.transform.position).normalized;
|
|
targetVector.y = 0;
|
|
|
|
if (currentTLinkTargetVector == Vector3.zero)
|
|
currentTLinkTargetVector = targetVector;
|
|
|
|
// Obstacle avoidance
|
|
|
|
if (leftOccupied && !rightOccupied)
|
|
{
|
|
isSteeringTLink = true;
|
|
|
|
currentTLinkTargetVector += (agent.transform.right / rotationDivider);
|
|
//Debug.Log("steering right");
|
|
}
|
|
else if (rightOccupied && !leftOccupied)
|
|
{
|
|
isSteeringTLink = true;
|
|
|
|
currentTLinkTargetVector += (-agent.transform.right / rotationDivider);
|
|
//Debug.Log("steering left");
|
|
}
|
|
else if (leftOccupied && forwardOccupied && rightOccupied)
|
|
{
|
|
isSteeringTLink = true;
|
|
|
|
// We're stuck in a wall, let's try to determine the closest end of the obstacle
|
|
int counter = 2;
|
|
bool endPointFound = false;
|
|
RaycastHit leftCheck, rightCheck;
|
|
bool endPointIsRight = false;
|
|
|
|
// Determine and save which object we've hit
|
|
while (!endPointFound && counter <= 6)
|
|
{
|
|
// Left checks
|
|
if (Physics.Raycast((transform.position + (Vector3.up * 1.45f)),
|
|
transform.TransformDirection(Vector3.forward + Vector3.left * leftRightOpeningMult * counter), out leftCheck, obstacleDistance, obstacleAvoidanceMask))
|
|
{
|
|
// If the object hit by the endpoint check is different from the actual object we may have found a free spot, not always true!
|
|
if (leftHit != leftCheck.transform.gameObject)
|
|
{
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Free spot
|
|
endPointIsRight = false;
|
|
endPointFound = true;
|
|
break;
|
|
}
|
|
|
|
// Right checks
|
|
if (Physics.Raycast((transform.position + (Vector3.up * 1.45f)),
|
|
transform.TransformDirection(Vector3.forward + Vector3.right * leftRightOpeningMult * counter), out rightCheck, obstacleDistance, obstacleAvoidanceMask))
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
// Free spot
|
|
endPointIsRight = true;
|
|
endPointFound = true;
|
|
break;
|
|
}
|
|
counter++;
|
|
yield return null;
|
|
}
|
|
|
|
// Couldn't find an endpoint, go right or left in base of position
|
|
if (!endPointFound)
|
|
{
|
|
differenceLeftRight = AngleDir(agent.transform.forward, targetVector, Vector3.up);
|
|
|
|
// Best steering is on the left
|
|
if (differenceLeftRight == -1)
|
|
{
|
|
// Steer left
|
|
endPointIsRight = false;
|
|
}
|
|
// Best steering is on the Right
|
|
else
|
|
{
|
|
// Steer right
|
|
endPointIsRight = true;
|
|
}
|
|
|
|
endPointFound = true;
|
|
}
|
|
|
|
|
|
// Rotate towards the endpoint
|
|
if (endPointIsRight)
|
|
currentTLinkTargetVector += (agent.transform.right / rotationDivider);
|
|
else
|
|
currentTLinkTargetVector += -(agent.transform.right / rotationDivider);
|
|
|
|
}
|
|
else if (!leftOccupied && !rightOccupied && forwardOccupied)
|
|
{
|
|
differenceLeftRight = AngleDir(agent.transform.forward, targetVector, Vector3.up);
|
|
|
|
// Best steering is on the left
|
|
if (differenceLeftRight == -1)
|
|
{
|
|
// Steer left
|
|
currentTLinkTargetVector += (-agent.transform.right / rotationDivider);
|
|
}
|
|
// Best steering is on the Right
|
|
else
|
|
{
|
|
// Steer right
|
|
currentTLinkTargetVector += (agent.transform.right / rotationDivider);
|
|
}
|
|
}
|
|
else if (!leftOccupied && forwardOccupied)
|
|
{
|
|
// Steer left
|
|
currentTLinkTargetVector += (-agent.transform.right / rotationDivider);
|
|
}
|
|
else if (!rightOccupied && forwardOccupied)
|
|
{
|
|
// Steer right
|
|
currentTLinkTargetVector += (agent.transform.right / rotationDivider);
|
|
}
|
|
else if (!rightOccupied && !forwardOccupied && !leftOccupied)
|
|
{
|
|
isSteeringTLink = false;
|
|
|
|
currentTLinkTargetVector = Vector3.Lerp(currentTLinkTargetVector, targetVector, lerpingToOriginal);
|
|
}
|
|
|
|
if(selectedSteeringBehaviour == SteeringBehaviours.Flee || selectedSteeringBehaviour == SteeringBehaviours.FleeAtDistance)
|
|
m_Rigidbody.velocity = (currentTLinkTargetVector.normalized * currentSpeed);
|
|
else
|
|
m_Rigidbody.velocity = (currentTLinkTargetVector.normalized * currentSpeed);
|
|
}
|
|
else
|
|
{
|
|
// Stop the agent
|
|
m_Rigidbody.velocity = Vector3.SmoothDamp(m_Rigidbody.velocity, Vector3.zero, ref velocity, stoppingHalt);
|
|
}
|
|
}
|
|
else // If not has a target (wandering for the world)
|
|
{
|
|
//agent.transform.position = Vector3.MoveTowards(agent.transform.position, agent.transform.forward, maxSpeed * Time.deltaTime);
|
|
}
|
|
|
|
if (((rotateToFaceTargetDuringTraversing && mainTarget != null) || shouldFollowTargetVector) && !isInConversation)
|
|
{
|
|
var targetPos = currentTLinkTargetVector;
|
|
|
|
if (mainTarget != null && Vector3.Distance(transform.position, mainTarget.position) <= stoppingDistance + 5f)
|
|
targetPos = (mainTarget.transform.position - transform.position).normalized;
|
|
else
|
|
targetPos = (currentTLinkTargetVector);
|
|
|
|
|
|
targetPos.y = 0; //set targetPos y equal to mine, so I only look at my own plane
|
|
var targetDir = Quaternion.LookRotation(targetPos);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetDir, generalRotationSpeed * Time.deltaTime);
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
|
|
yield return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the AI is stuck in the TraversingLink mode. If it is, calls Unstuck()
|
|
/// </summary>
|
|
void CheckForStuckStatus_TLink()
|
|
{
|
|
if (!usesMovements || !isTraversingLink || mainTarget == null)
|
|
{
|
|
tLinkStuckCounter = 0.0f;
|
|
return;
|
|
}
|
|
if (Vector3.Distance(m_Rigidbody.velocity, tLinkStuckThreshold) < 1)
|
|
tLinkStuckCounter += 1.0f * Time.deltaTime;
|
|
else
|
|
tLinkStuckCounter = 0.0f;
|
|
|
|
if (tLinkStuckCounter >= tLinkStuckTime)
|
|
{
|
|
Unstuck();
|
|
tLinkStuckCounter = tLinkStuckTime / 2;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Attempts to unstuck the AI by pushing him into a free direction (if found), if he's detected to be stucked while traversing links
|
|
/// </summary>
|
|
public void Unstuck()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
m_Rigidbody.velocity = Vector3.zero;
|
|
currentTLinkTargetVector = Vector3.zero;
|
|
|
|
if (!leftOccupied)
|
|
m_Rigidbody.AddForce(transform.right * tLinkUnstuckForce, ForceMode.Impulse);
|
|
else if (!rightOccupied)
|
|
m_Rigidbody.AddForce(-transform.right * tLinkUnstuckForce, ForceMode.Impulse);
|
|
else if (!forwardOccupied)
|
|
m_Rigidbody.AddForce(transform.forward * tLinkUnstuckForce, ForceMode.Impulse);
|
|
else
|
|
{
|
|
m_Rigidbody.AddForce(-transform.forward * tLinkUnstuckForce, ForceMode.Impulse);
|
|
m_Rigidbody.AddForce(transform.right * (Random.Range(0.0f, 1.0f) * tLinkUnstuckForce), ForceMode.Impulse);
|
|
m_Rigidbody.AddForce(-transform.right * (Random.Range(0.0f, 1.0f) * tLinkUnstuckForce), ForceMode.Impulse);
|
|
}
|
|
}
|
|
|
|
int AngleDir(Vector3 fwd, Vector3 targetDir, Vector3 up)
|
|
{
|
|
Vector3 perp = Vector3.Cross(fwd, targetDir);
|
|
float dir = Vector3.Dot(perp, up);
|
|
|
|
if (dir > 0.0f)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (dir < 0f)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows for Obstacle Avoidance while in TraversingLinks mode, the AI is able to detect and obstacle and attempt to get around it, while continuing moving towards its mainTarget.
|
|
/// This method looks for the surroundings and determines if the left, foward or the right is occupied.
|
|
/// </summary>
|
|
void TLinkObstacleAvoidance()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
if (movementType != MovementType.TraversingLinks)
|
|
return;
|
|
|
|
switch (tLinkObstacleAvoidancePrecision)
|
|
{
|
|
case TLinkObstacleAvoidancePrecision.Full:
|
|
leftOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward + Vector3.left * leftRightOpeningMult), out feetLeftHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 1.45f)), transform.TransformDirection(Vector3.forward + Vector3.left * leftRightOpeningMult), out bodyLeftHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 2.0f)), transform.TransformDirection(Vector3.forward + Vector3.left * leftRightOpeningMult), out headLeftHit, obstacleDistance, obstacleAvoidanceMask));
|
|
|
|
|
|
forwardOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward), out feetForwardHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 1.45f)), transform.TransformDirection(Vector3.forward), out bodyForwardHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 2.0f)), transform.TransformDirection(Vector3.forward), out headForwardHit, obstacleDistance, obstacleAvoidanceMask));
|
|
|
|
rightOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward + Vector3.right * leftRightOpeningMult), out feetRightHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 1.45f)), transform.TransformDirection(Vector3.forward + Vector3.right * leftRightOpeningMult), out bodyRightHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 2.0f)), transform.TransformDirection(Vector3.forward + Vector3.right * leftRightOpeningMult), out headRightHit, obstacleDistance, obstacleAvoidanceMask));
|
|
|
|
break;
|
|
|
|
|
|
case TLinkObstacleAvoidancePrecision.Good:
|
|
leftOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward + Vector3.left * leftRightOpeningMult), out feetLeftHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 1.45f)), transform.TransformDirection(Vector3.forward + Vector3.left * leftRightOpeningMult), out bodyLeftHit, obstacleDistance, obstacleAvoidanceMask));
|
|
|
|
forwardOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward), out feetForwardHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 1.45f)), transform.TransformDirection(Vector3.forward), out bodyForwardHit, obstacleDistance, obstacleAvoidanceMask));
|
|
|
|
rightOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward + Vector3.right * leftRightOpeningMult), out feetRightHit, obstacleDistance, obstacleAvoidanceMask) ||
|
|
Physics.Raycast((transform.position + (Vector3.up * 1.45f)), transform.TransformDirection(Vector3.forward + Vector3.right * leftRightOpeningMult), out bodyRightHit, obstacleDistance, obstacleAvoidanceMask));
|
|
break;
|
|
|
|
|
|
case TLinkObstacleAvoidancePrecision.Minimum:
|
|
leftOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward + Vector3.left * leftRightOpeningMult), out feetLeftHit, obstacleDistance, obstacleAvoidanceMask));
|
|
|
|
forwardOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward), out feetForwardHit, obstacleDistance, obstacleAvoidanceMask));
|
|
|
|
rightOccupied = (Physics.Raycast((transform.position + (Vector3.up / 3.0f)), transform.TransformDirection(Vector3.forward + Vector3.right * leftRightOpeningMult), out feetRightHit, obstacleDistance, obstacleAvoidanceMask));
|
|
break;
|
|
|
|
|
|
case TLinkObstacleAvoidancePrecision.Null:
|
|
leftOccupied = false;
|
|
forwardOccupied = false;
|
|
rightOccupied = false;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
public void StopFollowingPath()
|
|
{
|
|
forceToStopPath = true;
|
|
StopCoroutine(UnityNavmeshFollowPath());
|
|
}
|
|
|
|
|
|
[AIInvokable]
|
|
[ContextMenu("FollowCurrentPath")]
|
|
public void FollowCurrentPath()
|
|
{
|
|
if (aiPath == null)
|
|
return;
|
|
|
|
forceToStopPath = false;
|
|
lookAtTarget = false;
|
|
|
|
shouldFollowMainTarget = true;
|
|
shouldFollowOnlyIfCanSee = false;
|
|
isFollowingAIPath = true;
|
|
|
|
StopCoroutine(UnityNavmeshFollowPath());
|
|
StartCoroutine(UnityNavmeshFollowPath());
|
|
}
|
|
|
|
public bool forceToStopPath = false;
|
|
/// <summary>
|
|
/// Used to make the AI follow an AI Path while using the Unity Navmesh System.
|
|
/// What it does is to update the MainTarget of this entity, the movements will be still handled by "HandleMovements()"
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
IEnumerator UnityNavmeshFollowPath()
|
|
{
|
|
shouldFollowAIPath = true;
|
|
bool pathCompleted = false;
|
|
|
|
if (!aiPathResumeFromCurrentIndex)
|
|
{
|
|
aiPathCurrentIndex = aiPath.startsFromIndex;
|
|
|
|
if (invertAIPath)
|
|
aiPathCurrentIndex = aiPath.points.Length - 1;
|
|
}
|
|
|
|
|
|
// Sets the first target
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject, false);
|
|
while (!pathCompleted || forceToStopPath)
|
|
{
|
|
// Check if we've reached the target at out stopping halt
|
|
if(Vector3.Distance(agent.transform.position, aiPath.points[aiPathCurrentIndex].transform.position) <= stoppingDistance)
|
|
{
|
|
// Check if we have to wait
|
|
if(aiPath.points[aiPathCurrentIndex].wait)
|
|
{
|
|
if (aiPath.points[aiPathCurrentIndex].faceDirection)
|
|
{
|
|
directionToLookIfStopped = aiPath.points[aiPathCurrentIndex].DirectionToFace;
|
|
|
|
while (transform.rotation != aiPath.points[aiPathCurrentIndex].DirectionToFace.rotation)
|
|
{
|
|
transform.rotation = Quaternion.RotateTowards(transform.rotation, aiPath.points[aiPathCurrentIndex].DirectionToFace.rotation, 100 * Time.deltaTime);
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
yield return new WaitForSeconds(aiPath.points[aiPathCurrentIndex].waitTime);
|
|
}
|
|
|
|
switch(aiPath.type)
|
|
{
|
|
case AICustomPathType.BackAndForthLooped:
|
|
// Check if we're going in positive +
|
|
if(!invertAIPath)
|
|
{
|
|
// If we've finished the path
|
|
if(aiPathCurrentIndex == aiPath.points.Length-1)
|
|
{
|
|
// We have to go backwards
|
|
invertAIPath = true;
|
|
aiPathCurrentIndex--;
|
|
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
else
|
|
{
|
|
// Set the next target
|
|
aiPathCurrentIndex++;
|
|
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we've finished the path
|
|
if (aiPathCurrentIndex == 0)
|
|
{
|
|
// We have to go backwards
|
|
invertAIPath = false;
|
|
aiPathCurrentIndex++;
|
|
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
else
|
|
{
|
|
// Set the next target
|
|
aiPathCurrentIndex--;
|
|
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AICustomPathType.Looped:
|
|
if(!invertAIPath)
|
|
{
|
|
// If it's the last point
|
|
if(aiPathCurrentIndex == aiPath.points.Length-1)
|
|
{
|
|
// Set the first as current
|
|
aiPathCurrentIndex = 0;
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
else // set next
|
|
{
|
|
aiPathCurrentIndex++;
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If it's the first point
|
|
if (aiPathCurrentIndex == 0)
|
|
{
|
|
// Set the last as current
|
|
aiPathCurrentIndex = aiPath.points.Length - 1;
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
else // set next
|
|
{
|
|
// Set the last as current
|
|
aiPathCurrentIndex--;
|
|
SetTarget(aiPath.points[aiPathCurrentIndex].gameObject);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
|
|
isFollowingAIPath = false;
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
[AIInvokable]
|
|
public void SetOverrideShouldFollowMainTarget(bool _active, bool _shouldFollow)
|
|
{
|
|
overrideShouldFollowMainTargetActive = _active;
|
|
overrideShouldFollowMainTarget = _shouldFollow;
|
|
|
|
if (overrideShouldFollowMainTargetActive)
|
|
shouldFollowMainTarget = overrideShouldFollowMainTarget;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables the TraversingLinks mode.
|
|
/// </summary>
|
|
public void EnableTraversingLinks()
|
|
{
|
|
if (!usesMovements || movementType == MovementType.Offline)
|
|
return;
|
|
|
|
if (!rightOccupied && !forwardOccupied && !leftOccupied)
|
|
{
|
|
isSteeringTLink = false;
|
|
}
|
|
|
|
if (!isTraversingLink)
|
|
{
|
|
UseTraversingLinks();
|
|
}
|
|
}
|
|
|
|
private void OnTriggerStay(Collider other)
|
|
{
|
|
if (other.CompareTag("RPG Creation Kit/NavmeshLink"))
|
|
{
|
|
if (!isTraversingLink)
|
|
EnableTraversingLinks();
|
|
}
|
|
|
|
if (other.transform.CompareTag("RPG Creation Kit/Door"))
|
|
{
|
|
doorInTheWay = other.gameObject.GetComponent<Door>();
|
|
|
|
if (!doorInTheWay.isOpened && !doorInTheWay.teleports)
|
|
{
|
|
// AI OPENS DOOR
|
|
if (doorInTheWay.anim != null)
|
|
{
|
|
if (doorInTheWay.locked == true) {
|
|
bool HasKey = (Inv.GetItemCount(doorInTheWay.key.ItemID) > 0);
|
|
if (HasKey == false) {
|
|
StopMovingForSeconds(0.5f);
|
|
}
|
|
if (Inv.HasItem(doorInTheWay.key)){
|
|
openThisDoor();
|
|
}
|
|
} else {
|
|
openThisDoor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnTriggerExit(Collider other)
|
|
{
|
|
if (other.CompareTag("RPG Creation Kit/NavmeshLink"))
|
|
{
|
|
if (isTraversingLink)
|
|
{
|
|
UseUnitysNavmesh();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjusts the Animator of the AI accordingly of the Movements he's making
|
|
/// </summary>
|
|
public void HandleAnimations()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
if (movementType == MovementType.UnityNavmesh)
|
|
{
|
|
var localVelocity = transform.InverseTransformDirection(agent.velocity);
|
|
|
|
walkModifier = (isWalking) ? .5f : 1f;
|
|
|
|
if (lookAtTarget)
|
|
{
|
|
m_Anim.SetFloat("Speed", (localVelocity.z / currentSpeed) * walkModifier, 1f, Time.deltaTime * 10);
|
|
m_Anim.SetFloat("Sideways", (localVelocity.x / currentSpeed) * walkModifier, 1f, Time.deltaTime * 10);
|
|
}
|
|
else
|
|
{
|
|
m_Anim.SetFloat("Speed", (agent.velocity.magnitude / currentSpeed) * walkModifier, 1f, Time.deltaTime * 10);
|
|
}
|
|
}
|
|
else if(movementType == MovementType.TraversingLinks)
|
|
{
|
|
var localVelocity = transform.InverseTransformDirection(m_Rigidbody.velocity);
|
|
|
|
walkModifier = (isWalking) ? .5f : 1f;
|
|
|
|
if (lookAtTarget)
|
|
{
|
|
m_Anim.SetFloat("Speed", (localVelocity.z / currentSpeed) * walkModifier, 1f, Time.deltaTime * 10);
|
|
m_Anim.SetFloat("Sideways", (localVelocity.x / currentSpeed) * walkModifier, 1f, Time.deltaTime * 10);
|
|
}
|
|
else
|
|
{
|
|
m_Anim.SetFloat("Speed", (m_Rigidbody.velocity.magnitude / currentSpeed) * walkModifier, 1f, Time.deltaTime * 10);
|
|
}
|
|
}
|
|
|
|
m_Anim.SetBool("isRotating", isRotating);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Switches to the UnityNavmesh movement type.
|
|
/// </summary>
|
|
public void UseUnitysNavmesh()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
movementType = MovementType.UnityNavmesh;
|
|
StopCoroutine("TraverseNormalSpeed");
|
|
|
|
isTraversingLink = false;
|
|
agent.enabled = true;
|
|
m_Rigidbody.isKinematic = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switches to the TraversingLink movement type.
|
|
/// </summary>
|
|
public void UseTraversingLinks()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
StopCoroutine("TraverseNormalSpeed");
|
|
|
|
movementType = MovementType.TraversingLinks;
|
|
isTraversingLink = true;
|
|
|
|
agent.enabled = false;
|
|
m_Rigidbody.isKinematic = false;
|
|
StartCoroutine("TraverseNormalSpeed");
|
|
}
|
|
|
|
public void UseCustomPathSystem()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
movementType = MovementType.CustomPathSystem;
|
|
}
|
|
|
|
public void UseNullMovement()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
movementType = MovementType.Null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reload the Movements State of the AI, used when it returns from Offline mode.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEnumerator ReloadState()
|
|
{
|
|
while (WorldManager.instance != null && WorldManager.instance.isLoading)
|
|
yield return null;
|
|
|
|
cachedMainTarget = false;
|
|
|
|
isInOfflineMode = false;
|
|
|
|
if(movementType != MovementType.InActionPoint)
|
|
UseUnitysNavmesh();
|
|
|
|
// if it was following the target ->
|
|
// UpdateTargetFromGUID();
|
|
|
|
// else if it was following the path
|
|
// UpdatePathFromGUID();
|
|
|
|
|
|
yield return null;
|
|
}
|
|
|
|
public void UpdateStateDelayed()
|
|
{
|
|
StopCoroutine(ReloadState());
|
|
StartCoroutine(ReloadState());
|
|
}
|
|
|
|
bool isSteeringTLink = false;
|
|
|
|
private float differenceLeftRight;
|
|
Vector3 currentTLinkTargetVector = Vector3.zero;
|
|
|
|
public bool failedGettingActionPoint = false;
|
|
|
|
[AIInvokable]
|
|
public void GetAndUseActionPoint()
|
|
{
|
|
if (myCellInfo == null)
|
|
return;
|
|
|
|
NPCActionPoint point = myCellInfo.GetAnUnusedActionPoint();
|
|
|
|
if (point == null)
|
|
{
|
|
isUsingActionPoint = false;
|
|
isReachingActionPoint = false;
|
|
failedGettingActionPoint = true;
|
|
return;
|
|
}
|
|
|
|
actionPoint = point;
|
|
UseNPCActionPoint(point);
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void UseCurrentActionPoint()
|
|
{
|
|
if (myCellInfo == null)
|
|
return;
|
|
|
|
if (actionPoint == null)
|
|
{
|
|
isUsingActionPoint = false;
|
|
isReachingActionPoint = false;
|
|
failedGettingActionPoint = true;
|
|
return;
|
|
}
|
|
else
|
|
UseNPCActionPoint(actionPoint);
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void UseArrayActionPoint(string[] arr)
|
|
{
|
|
if (myCellInfo == null)
|
|
return;
|
|
|
|
int tries = 0;
|
|
bool found = false;
|
|
while(found == false && tries < arr.Length)
|
|
{
|
|
tries++;
|
|
|
|
int random = Random.Range(0, arr.Length-1);
|
|
|
|
NPCActionPoint point = myCellInfo.allActionPoints[arr[random]];
|
|
if(!point.isOccupied)
|
|
{
|
|
actionPoint = point;
|
|
found = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
actionPoint = null;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (actionPoint == null)
|
|
{
|
|
isUsingActionPoint = false;
|
|
isReachingActionPoint = false;
|
|
failedGettingActionPoint = true;
|
|
return;
|
|
}
|
|
else
|
|
UseNPCActionPoint(actionPoint);
|
|
}
|
|
|
|
public bool isReachingActionPoint = false;
|
|
|
|
public void UseNPCActionPoint(NPCActionPoint _aPoint)
|
|
{
|
|
actionPoint = _aPoint;
|
|
|
|
actionPoint.OnUserAssigned(this);
|
|
|
|
SetTarget(actionPoint.pivotPoint.gameObject);
|
|
|
|
StartCoroutine(ReachingActionPoint());
|
|
}
|
|
|
|
public void ResetActionPointAgentState()
|
|
{
|
|
if(actionPoint != null)
|
|
actionPoint.OnUserForceLeaves();
|
|
|
|
StopCoroutine(ReachingActionPoint());
|
|
|
|
isReachingActionPoint = false;
|
|
actionPoint = null;
|
|
isUsingActionPoint = false;
|
|
shouldUseNPCActionPoint = false;
|
|
}
|
|
|
|
public bool fixingInPlaceForActionPoint = false;
|
|
public bool transformMovingToActionPoint = false;
|
|
public IEnumerator ReachingActionPoint()
|
|
{
|
|
isReachingActionPoint = true;
|
|
|
|
while (actionPoint != null && Vector3.Distance(transform.position, actionPoint.pivotPoint.position) > stoppingDistance)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
if (actionPoint == null)
|
|
yield break;
|
|
|
|
DisableMovements();
|
|
m_Anim.applyRootMotion = true;
|
|
|
|
movementType = MovementType.InActionPoint;
|
|
|
|
bool cancelled = false;
|
|
|
|
while (isReachingActionPoint && (transform.position != actionPoint.pivotPoint.position ||
|
|
transform.rotation != actionPoint.pivotPoint.rotation))
|
|
{
|
|
fixingInPlaceForActionPoint = true;
|
|
|
|
if (enterInCombatCalled)
|
|
{
|
|
cancelled = true;
|
|
m_Anim.applyRootMotion = false;
|
|
UseUnitysNavmesh();
|
|
|
|
isUsingActionPoint = false;
|
|
EnableMovements();
|
|
actionPoint.OnUserForceLeaves();
|
|
yield break;
|
|
}
|
|
|
|
if (transform.position != actionPoint.pivotPoint.position)
|
|
m_Anim.SetFloat("Speed", (isWalking) ? .5f : 1f);
|
|
else
|
|
m_Anim.SetFloat("Speed", 0f);
|
|
|
|
transformMovingToActionPoint = true;
|
|
|
|
|
|
yield return null;
|
|
}
|
|
|
|
if (cancelled)
|
|
yield break;
|
|
|
|
transform.position = actionPoint.pivotPoint.position;
|
|
transform.rotation = actionPoint.pivotPoint.rotation;
|
|
|
|
transformMovingToActionPoint = false;
|
|
|
|
m_Anim.CrossFade(actionPoint.npcAction.name, 0f);
|
|
playingEnterAnimationActionPoint = true;
|
|
|
|
Invoke("OnHasFinishedPlayingActionPointEnterAnimation", actionPoint.npcAction.length);
|
|
|
|
actionPoint.OnUserUses(this);
|
|
|
|
isReachingActionPoint = false;
|
|
isUsingActionPoint = true;
|
|
fixingInPlaceForActionPoint = false;
|
|
}
|
|
|
|
public void OnHasFinishedPlayingActionPointEnterAnimation()
|
|
{
|
|
playingEnterAnimationActionPoint = false;
|
|
}
|
|
|
|
public GameObject currentDynamicObject;
|
|
|
|
public void DynamicObjectStartAnimationEvent()
|
|
{
|
|
if(actionPoint.spawnDynamicObject)
|
|
{
|
|
switch(actionPoint.dynamicObjectBone)
|
|
{
|
|
case DynamicObjectBone.Rhand:
|
|
currentDynamicObject = Instantiate(actionPoint.dynamicObject, bodyData.rHand);
|
|
break;
|
|
|
|
case DynamicObjectBone.Lhand:
|
|
currentDynamicObject = Instantiate(actionPoint.dynamicObject, bodyData.lHand);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DynamicObjectEndAnimationEvent()
|
|
{
|
|
if (actionPoint.spawnDynamicObject)
|
|
{
|
|
Destroy(currentDynamicObject);
|
|
}
|
|
}
|
|
|
|
public bool playingEnterAnimationActionPoint = false;
|
|
public bool isUsingActionPoint = false;
|
|
|
|
[AIInvokable]
|
|
public void StopUsingNPCActionPoint(bool _immediate = false)
|
|
{
|
|
StopCoroutine(StopUsingNPCActionPointTask());
|
|
StartCoroutine(StopUsingNPCActionPointTask(_immediate));
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void StopUsingNPCActionPointImmediate()
|
|
{
|
|
StopCoroutine(StopUsingNPCActionPointTask());
|
|
StartCoroutine(StopUsingNPCActionPointTask(true));
|
|
}
|
|
|
|
public bool leavingActionPoint = false;
|
|
private IEnumerator StopUsingNPCActionPointTask(bool immediate = false)
|
|
{
|
|
if (!immediate)
|
|
{
|
|
while (playingEnterAnimationActionPoint || isInConversation)
|
|
yield return new WaitForEndOfFrame();
|
|
|
|
fixingInPlaceForActionPoint = false;
|
|
actionPoint.OnUserIsLeaving(this);
|
|
m_Anim.CrossFade(actionPoint.npcActionReturn.name, 0f);
|
|
leavingActionPoint = true;
|
|
yield return new WaitForSeconds(actionPoint.npcActionReturn.length);
|
|
}
|
|
else
|
|
{
|
|
m_Anim.Play("Base Locomotion", 0);
|
|
}
|
|
|
|
DisableMovements();
|
|
m_Anim.applyRootMotion = false;
|
|
UseUnitysNavmesh();
|
|
|
|
isReachingActionPoint = false;
|
|
isUsingActionPoint = false;
|
|
actionPoint.OnUserHasLeft(this);
|
|
leavingActionPoint = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjusts the current speed. (Walking/Running)
|
|
/// </summary>
|
|
[ContextMenu("Adjust Current Speed")]
|
|
private void AdjustCurrentSpeed()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
currentSpeed = (isWalking ? attributes.derivedAttributes.walkSpeed : attributes.derivedAttributes.runSpeed);
|
|
maxSpeed = (isWalking ? attributes.derivedAttributes.walkSpeed : attributes.derivedAttributes.runSpeed);
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void Movements_SwitchToRun()
|
|
{
|
|
isWalking = false;
|
|
AdjustCurrentSpeed();
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void Movements_SwitchToWalk()
|
|
{
|
|
isWalking = true;
|
|
AdjustCurrentSpeed();
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void SetFollowMainTarget(bool shouldFollow)
|
|
{
|
|
shouldFollowMainTarget = shouldFollow;
|
|
}
|
|
|
|
[AIInvokable]
|
|
public void SetAIPathFromCell(string _pathID)
|
|
{
|
|
if (CellInformation.TryToGetPath(_pathID, out aiPath))
|
|
{
|
|
// All good
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("Tried to assign the path with id '" + _pathID + "' to the AI: '" + entityID + "', but the path wasn't found. Maybe you haven't updated the cell or mispelled the PathID? " + " MyCellInfo was: " + myCellInfo.cell.ID +". \nTrying again in 1 second.");
|
|
if(!setAIPathFromCellRetrying)
|
|
{
|
|
StartCoroutine(SetAIPathFromCellRetry(_pathID));
|
|
setAIPathFromCellRetrying = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool setAIPathFromCellRetrying = false;
|
|
private IEnumerator SetAIPathFromCellRetry(string _pathID)
|
|
{
|
|
yield return new WaitForSeconds(1);
|
|
setAIPathFromCellRetrying = false;
|
|
SetAIPathFromCell(_pathID);
|
|
}
|
|
|
|
#region SteeringBehaviours
|
|
public enum Deceleration { Fast = 1, Normal = 2, Slow = 3, VerySlow = 4 };
|
|
|
|
|
|
/// <summary>
|
|
/// Returns a force that directs the agent toward the target position.
|
|
/// </summary>
|
|
/// <param name="targetPos">The target position.</param>
|
|
/// <returns></returns>
|
|
protected Vector3 Seek(Vector3 targetPos)
|
|
{
|
|
Vector3 desiredVelocity = Vector3.Normalize(targetPos - transform.position) * currentSpeed;
|
|
return (desiredVelocity - agent.desiredVelocity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a force that directs the agent away from the target position.
|
|
/// </summary>
|
|
/// <param name="targetPos">The target position.</param>
|
|
/// <returns></returns>
|
|
protected Vector3 Flee(Vector3 targetPos)
|
|
{
|
|
Vector3 desiredVelocity = Vector3.Normalize(transform.position - targetPos) * currentSpeed;
|
|
return (desiredVelocity - agent.desiredVelocity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a force that directs the agent away from the target position if the target is within the range of fleeDistance.
|
|
/// </summary>
|
|
/// <param name="targetPos">The target position.</param>
|
|
/// <param name="fleeDistance">The distance at wich the agent will start fleeing.</param>
|
|
/// <returns></returns>
|
|
protected Vector3 FleeAtDistance(Vector3 targetPos, float fleeDistance)
|
|
{
|
|
if (Vector3.Distance(transform.position, targetPos) >= fleeDistance)
|
|
return Vector3.zero;
|
|
|
|
Vector3 desiredVelocity = Vector3.Normalize(transform.position - targetPos) * currentSpeed;
|
|
return (desiredVelocity - agent.desiredVelocity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seek but with gentle halt at the target position.
|
|
/// </summary>
|
|
/// <param name="targetPos">The target position.</param>
|
|
/// <param name="deceleration">The deceleration type.</param>
|
|
/// <returns></returns>
|
|
protected Vector3 Arrive(Vector3 targetPos, Deceleration deceleration)
|
|
{
|
|
// Arrive works only when the point to arrive to is the last one of the path
|
|
if (navmeshPath.corners.Length <= 2)
|
|
{
|
|
Vector3 toTarget = targetPos - transform.position;
|
|
float dist = Vector3.Distance(transform.position, targetPos);
|
|
|
|
if (dist > 0)
|
|
{
|
|
const float decelerationTweaker = 0.3f;
|
|
|
|
currentSpeed = dist / ((float)deceleration * decelerationTweaker);
|
|
currentSpeed = Mathf.Clamp(currentSpeed, currentSpeed, maxSpeed);
|
|
|
|
Vector3 desiredVelocity = toTarget * currentSpeed / dist;
|
|
return (desiredVelocity - agent.desiredVelocity);
|
|
}
|
|
|
|
return Vector3.zero;
|
|
}
|
|
else // If there is a path that has corners we do not want to arrive on coners but just pass them
|
|
return Seek(targetPos);
|
|
}
|
|
|
|
|
|
protected Vector3 RightSideway(Vector3 targetPos)
|
|
{
|
|
Vector3 desiredVelocity = Vector3.Normalize(transform.right) * currentSpeed;
|
|
return (desiredVelocity);
|
|
}
|
|
|
|
protected Vector3 LeftSideway(Vector3 targetPos)
|
|
{
|
|
Vector3 desiredVelocity = Vector3.Normalize(-transform.right) * currentSpeed;
|
|
return (desiredVelocity);
|
|
}
|
|
|
|
protected Vector3 Pursuit()
|
|
{
|
|
return Vector3.zero;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void EnableMovements()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
// Release the AI
|
|
if (movementType == MovementType.UnityNavmesh)
|
|
agent.enabled = true;
|
|
else if (movementType == MovementType.TraversingLinks)
|
|
m_Rigidbody.isKinematic = false;
|
|
}
|
|
|
|
public void DisableMovements()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
// Stop the AI
|
|
if (movementType == MovementType.UnityNavmesh)
|
|
agent.enabled = false;
|
|
else if (movementType == MovementType.TraversingLinks)
|
|
m_Rigidbody.isKinematic = true;
|
|
}
|
|
|
|
public void EnablePhysicalCollider()
|
|
{
|
|
if(isAlive)
|
|
physicalCollider.enabled = true;
|
|
}
|
|
|
|
public void DisablePhysicalCollider()
|
|
{
|
|
physicalCollider.enabled = false;
|
|
}
|
|
|
|
public void EnableInteractableCollider()
|
|
{
|
|
interactableCollider.enabled = true;
|
|
}
|
|
|
|
public void DisableInteractableCollider()
|
|
{
|
|
interactableCollider.enabled = false;
|
|
}
|
|
|
|
public void StopMovingForSeconds(float seconds)
|
|
{
|
|
DisableMovements();
|
|
Invoke("EnableMovements", seconds);
|
|
}
|
|
|
|
private void OnTriggerEnter(Collider collision)
|
|
{
|
|
if (collision.transform.CompareTag("RPG Creation Kit/Door"))
|
|
{
|
|
doorInTheWay = collision.gameObject.GetComponent<Door>();
|
|
if (!doorInTheWay.isOpened && !doorInTheWay.teleports)
|
|
{
|
|
// AI OPENS DOOR
|
|
if (doorInTheWay.anim != null)
|
|
{
|
|
if (doorInTheWay.locked == true) {
|
|
Inv = GetComponent<Inventory>();
|
|
bool HasKey = (Inv.GetItemCount(doorInTheWay.key.ItemID) > 0);
|
|
Debug.Log("Key: "+doorInTheWay.key.ItemID);
|
|
if (HasKey == false) {
|
|
StopMovingForSeconds(1f);
|
|
HasKey = (Inv.GetItemCount(doorInTheWay.key.ItemID) > 0);
|
|
}
|
|
if (Inv.HasItem(doorInTheWay.key)){
|
|
openThisDoor();
|
|
}
|
|
} else {
|
|
openThisDoor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void openThisDoor() {
|
|
m_Anim.CrossFade("OpenDoor", 0.1f);
|
|
doorInTheWay.ForceOpenCrossFade();
|
|
StopMovingForSeconds(doorInTheWay.anim["Open"].length);
|
|
}
|
|
|
|
#region Overrides
|
|
|
|
public override void OnDialogueStarts(GameObject target)
|
|
{
|
|
base.OnDialogueStarts(target);
|
|
|
|
// Stop the AI
|
|
DisableMovements();
|
|
|
|
// Rotate Towards Target
|
|
}
|
|
|
|
public override void OnDialogueEnds(GameObject target)
|
|
{
|
|
base.OnDialogueEnds(target);
|
|
|
|
//Release the AI
|
|
EnableMovements();
|
|
|
|
// Return to do what you were doing
|
|
}
|
|
|
|
public override void Die()
|
|
{
|
|
DisableMovements();
|
|
agent.enabled = false;
|
|
DisablePhysicalCollider();
|
|
DisableInteractableCollider();
|
|
if(aiLookAt)
|
|
aiLookAt.isEnabled = false;
|
|
base.Die();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#if UNITY_EDITOR
|
|
public virtual void OnDrawGizmos()
|
|
{
|
|
if (!usesMovements)
|
|
return;
|
|
|
|
if (generalDebug)
|
|
{
|
|
switch(tLinkObstacleAvoidancePrecision)
|
|
{
|
|
case TLinkObstacleAvoidancePrecision.Full:
|
|
Gizmos.color = (leftOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.left * leftRightOpeningMult));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 2.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.left * leftRightOpeningMult));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 1.45f), transform.TransformDirection(Vector3.forward * 2 + Vector3.left * leftRightOpeningMult));
|
|
|
|
Gizmos.color = (forwardOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 1.45f), transform.TransformDirection(Vector3.forward * 2));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 2.0f), transform.TransformDirection(Vector3.forward * 2));
|
|
|
|
Gizmos.color = (rightOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.right * leftRightOpeningMult));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 1.45f), transform.TransformDirection(Vector3.forward * 2 + Vector3.right * leftRightOpeningMult));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 2.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.right * leftRightOpeningMult));
|
|
break;
|
|
|
|
case TLinkObstacleAvoidancePrecision.Good:
|
|
Gizmos.color = (leftOccupied ? Color.red : Color.green);
|
|
Gizmos.color = (leftOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.left * leftRightOpeningMult));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 1.45f), transform.TransformDirection(Vector3.forward * 2 + Vector3.left * leftRightOpeningMult));
|
|
|
|
Gizmos.color = (forwardOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 1.45f), transform.TransformDirection(Vector3.forward * 2));
|
|
|
|
Gizmos.color = (rightOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.right * leftRightOpeningMult));
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up * 1.45f), transform.TransformDirection(Vector3.forward * 2 + Vector3.right * leftRightOpeningMult));
|
|
break;
|
|
|
|
case TLinkObstacleAvoidancePrecision.Minimum:
|
|
Gizmos.color = (leftOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.left * leftRightOpeningMult));
|
|
|
|
Gizmos.color = (forwardOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2));
|
|
|
|
Gizmos.color = (rightOccupied ? Color.red : Color.green);
|
|
GizmosExtension.ArrowForGizmo(transform.position + (Vector3.up / 3.0f), transform.TransformDirection(Vector3.forward * 2 + Vector3.right * leftRightOpeningMult));
|
|
break;
|
|
|
|
case TLinkObstacleAvoidancePrecision.Null:
|
|
break;
|
|
}
|
|
|
|
if(shouldFollowTargetVector)
|
|
{
|
|
Gizmos.color = Color.white;
|
|
Gizmos.DrawWireCube(targetVector, new Vector3(.8f, .8f, .8f));
|
|
|
|
GUIStyle style = new GUIStyle();
|
|
style.normal.textColor = Color.red;
|
|
Handles.Label(targetVector, "R", style);
|
|
}
|
|
|
|
// Ground check
|
|
Gizmos.color = Color.white;
|
|
|
|
if(agent != null)
|
|
GizmosExtension.ArrowForGizmo(agent.transform.position + Vector3.up, Vector3.down);
|
|
}
|
|
|
|
if (debugPath)
|
|
{
|
|
if (navmeshPath != null)
|
|
{
|
|
Gizmos.color = Color.green;
|
|
for (int i = 0; i < navmeshPath.corners.Length - 1; i++)
|
|
Gizmos.DrawLine(navmeshPath.corners[i], navmeshPath.corners[i + 1]);
|
|
|
|
Gizmos.color = Color.gray;
|
|
for (int i = 0; i < navmeshPath.corners.Length; i++)
|
|
Handles.Label(navmeshPath.corners[i], new GUIContent(i.ToString()));
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
}
|
|
} |