using System.Collections; using System.Collections.Generic; using UnityEngine; using RPGCreationKit; using UnityEngine.AI; using UnityEditor; using RPGCreationKit.CellsSystem; namespace RPGCreationKit.AI { /// /// Defines the current MovementType this AI entity is currently using. /// public enum MovementType { Null = 0, UnityNavmesh = 1, TraversingLinks = 2, CustomPathSystem = 3, InActionPoint = 4, Offline = 5 }; /// /// Defines the precision of Obstacle Avoidance while the AI entity is traversing a Link. /// The more the precision, the more the performances used. /// 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; /// /// When the AI walks he could encounter a door, if he's in front of a door, this reference will be set. /// 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; } /// /// Wait for scene to be loaded and get cellinfo /// /// 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 /// /// Attempts to set the MainTarget to the GUID corresponding performing different checks over time. /// /// public void WaitAndUpdateTargetFromGUID() { StopCoroutine(WaitAndUpdateTargetFromGUIDTask()); StartCoroutine(WaitAndUpdateTargetFromGUIDTask()); } /// /// Immediatly set the MainTarget to the GUID corresponding. It can fail if it is used while the scene where the GUIDReference is being loaded. /// [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(); } } /// /// Attempts to set the MainTarget to the GUID corresponding performing different checks over time. /// /// public void WaitAndUpdatePathFromGUID() { StopCoroutine(WaitAndUpdatePathFromGUIDTask()); StartCoroutine(WaitAndUpdatePathFromGUIDTask()); } /// /// Immediatly set the MainTarget to the GUID corresponding. It can fail if it is used while the scene where the GUIDReference is being loaded. /// public void UpdatePathFromGUID() { GameObject rTest = iGUIDReferences.TryToGetReferenceGameObject(AIGUIDReferenceType.CurrentPath); if (rTest != null) { aiPath = rTest.GetComponent(); } } private IEnumerator WaitAndUpdatePathFromGUIDTask() { bool set = false; GameObject rTest = null; while (!set) { rTest = iGUIDReferences.TryToGetReferenceGameObject(AIGUIDReferenceType.CurrentPath); if (rTest != null) { aiPath = rTest.GetComponent(); 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 /// /// Handles the different types of movement this AI entity can use. /// 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; } } /// /// This methods updates /// [AIInvokable] public void SnapshotTargetPosition() { if(agent.isOnNavMesh) agent.CalculatePath(mainTarget.position, navmeshPath); } /// /// Controls the movements of the AI entity while in TraversingLinks mode /// /// 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; } /// /// Checks if the AI is stuck in the TraversingLink mode. If it is, calls Unstuck() /// 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; } } /// /// Attempts to unstuck the AI by pushing him into a free direction (if found), if he's detected to be stucked while traversing links /// 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; } } /// /// 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. /// 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; /// /// 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()" /// /// 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; } /// /// Enables the TraversingLinks mode. /// 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(); 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(); } } } /// /// Adjusts the Animator of the AI accordingly of the Movements he's making /// 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); } /// /// Switches to the UnityNavmesh movement type. /// public void UseUnitysNavmesh() { if (!usesMovements) return; movementType = MovementType.UnityNavmesh; StopCoroutine("TraverseNormalSpeed"); isTraversingLink = false; agent.enabled = true; m_Rigidbody.isKinematic = true; } /// /// Switches to the TraversingLink movement type. /// 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; } /// /// Reload the Movements State of the AI, used when it returns from Offline mode. /// /// 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; } /// /// Adjusts the current speed. (Walking/Running) /// [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 }; /// /// Returns a force that directs the agent toward the target position. /// /// The target position. /// protected Vector3 Seek(Vector3 targetPos) { Vector3 desiredVelocity = Vector3.Normalize(targetPos - transform.position) * currentSpeed; return (desiredVelocity - agent.desiredVelocity); } /// /// Returns a force that directs the agent away from the target position. /// /// The target position. /// protected Vector3 Flee(Vector3 targetPos) { Vector3 desiredVelocity = Vector3.Normalize(transform.position - targetPos) * currentSpeed; return (desiredVelocity - agent.desiredVelocity); } /// /// Returns a force that directs the agent away from the target position if the target is within the range of fleeDistance. /// /// The target position. /// The distance at wich the agent will start fleeing. /// 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); } /// /// Seek but with gentle halt at the target position. /// /// The target position. /// The deceleration type. /// 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(); if (!doorInTheWay.isOpened && !doorInTheWay.teleports) { // AI OPENS DOOR if (doorInTheWay.anim != null) { if (doorInTheWay.locked == true) { Inv = GetComponent(); bool HasKey = (Inv.GetItemCount(doorInTheWay.key.ItemID) > 0); 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 } }