using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; namespace Invector.vCharacterController.AI { public static class ProtectedMethodInvekerHelper { /// /// Invoke all method that contains the /// /// /// public static void InvokeInitAI(this T target) where T : vControlAI { var methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic ).Where(m => m.GetCustomAttributes(typeof(vInitAIAttribute), false).Length > 0) .ToArray(); for (int i = 0; i < methods.Length; i++) { try { methods[i].Invoke(target, null); } catch { } } } } [SelectionBase] [vClassHeader("AI BASIC CONTROLLER", iconName = "AI-icon")] public partial class vControlAI : vAIMotor, vIControlAI { #region Inspector Variables [vEditorToolbar("Start")] public bool disableAgentOnStart = true; [vEditorToolbar("Agent", order = 5)] [SerializeField] protected bool useNavMeshAgent = true; [SerializeField] protected vAIUpdateQuality updatePathQuality = vAIUpdateQuality.Medium; [SerializeField][Range(1f, 10f)] protected float aceleration = 8f; [SerializeField][Range(0.05f, 10f)] protected float _stopingDistance = 0.2f; [Header("Increase StoppingDistance by speed")] [SerializeField][Range(0.05f, 10f)] protected float _walkingStopingDistance = 0.0f; [SerializeField][Range(0.05f, 10f)] protected float _runningStopingDistance = 0.1f; [SerializeField][Range(0.05f, 10f)] protected float _sprintingStopingDistance = 0.15f; [vEditorToolbar("Waypoint", order = 6)] [vHelpBox("You can create a new WaypointArea at the Invector/AIController/Components/Create new WaypointArea", vHelpBoxAttribute.MessageType.Info)] [SerializeField] protected vWaypointArea _waypointArea; [SerializeField] protected float _changeWaypointDistance; [SerializeField] protected bool _invertWaypointsOrder; [SerializeField] protected bool _randomStartingPoint = true; [SerializeField] protected bool _randomWaypoint = true; [SerializeField] protected bool startUsingSpecificWaypoint = false; [SerializeField] protected int startWaypointIndex; [SerializeField] protected bool startUsingNearWayPoint = false; [SerializeField] protected bool _selfStartingPoint; [SerializeField] protected Transform _customStartingPoint; [vEditorToolbar("Detection", order = 7)] [vHelpBox("Use a empty trasform inside the headBone transform as reference to the character Eyes", vHelpBoxAttribute.MessageType.None)] public Transform detectionPointReference; [SerializeField, vEnumFlag] public vAISightMethod sightMethod = vAISightMethod.Center | vAISightMethod.Top; [SerializeField] protected vAIUpdateQuality findTargetUpdateQuality = vAIUpdateQuality.High; [SerializeField] protected vAIUpdateQuality canseeTargetUpdateQuality = vAIUpdateQuality.Medium; [SerializeField, Tooltip("find target with current target found")] protected bool findOtherTarget = false; [SerializeField][Range(1, 100)] protected int maxTargetsDetection = 10; [SerializeField] protected float _changeTargetDelay = 2f; [SerializeField] protected bool findTargetByDistance = true; [SerializeField] protected float _fieldOfView = 90f; [SerializeField] protected float _minDistanceToDetect = 3f; [SerializeField] protected float _maxDistanceToDetect = 6f; [SerializeField][vReadOnly] protected bool _hasPositionOfTheTarget; [SerializeField][vReadOnly] protected bool _targetInLineOfSight; [vHelpBox("Considerer maxDistanceToDetect value + lostTargetDistance", vHelpBoxAttribute.MessageType.None)] [SerializeField] protected float _lostTargetDistance = 4f; [SerializeField] protected float _timeToLostWithoutSight = 5f; [Header("--- Layers to Detect ----")] [SerializeField] protected LayerMask _detectLayer; [SerializeField] protected vTagMask _detectTags; [SerializeField] protected LayerMask _obstacles = 1 << 0; [vEditorToolbar("Targets")] [vSeparator("Inside Range")] [SerializeField] protected List targetsInRange = new List(); [vSeparator("Current")] [SerializeField] protected vAITarget _currentTarget; [vSeparator("Storage")] [SerializeField] protected List _secundaryTargets = new List(); [vEditorToolbar("Debug")] [vHelpBox("Debug Options")] [SerializeField] protected bool _debugVisualDetection; [SerializeField] protected bool _debugRaySight; [SerializeField] protected bool _debugLastTargetPosition; [SerializeField] protected vAIReceivedDamegeInfo _receivedDamage = new vAIReceivedDamegeInfo(); internal vAIHeadtrack _headtrack; protected Collider[] targetsBuffer; public List secundaryTargets { get=> _secundaryTargets; set=> _secundaryTargets=value; } protected Vector3 _lastTargetPosition; protected int _currentWaypoint; protected float lostTargetTime; protected Vector3 lastValidDestination; protected UnityEngine.AI.NavMeshHit navHit; protected float changeTargetTime; public virtual void CreatePrimaryComponents() { if (GetComponent() == null) { gameObject.AddComponent(); var rigidbody = GetComponent(); rigidbody.mass = 50f; rigidbody.constraints = RigidbodyConstraints.FreezeRotation; } if (GetComponent() == null) { var capsuler = gameObject.AddComponent(); animator = GetComponent(); if (animator) { var foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot); var hips = animator.GetBoneTransform(HumanBodyBones.Hips); var height = (float)System.Math.Round(Vector3.Distance(foot.position, hips.position) * 2f, 2); capsuler.height = height; capsuler.center = new Vector3(0, (float)System.Math.Round(capsuler.height * 0.5f, 2), 0); capsuler.radius = (float)System.Math.Round(capsuler.height * 0.15f, 2); } } if (GetComponent() == null) gameObject.AddComponent(); } public virtual void CreateSecondaryComponents() { } protected bool isWaypointStarted; #endregion #region NavMeshAgent Variables protected Vector3 _destination; protected Vector3 lasDestination; [HideInInspector] public UnityEngine.AI.NavMeshAgent navMeshAgent; protected UnityEngine.AI.NavMeshHit navMeshHit; protected float updatePathTime; protected float updateFindTargetTime; protected float canseeTargetUpdateTime; protected float timeToResetOutDistance; protected float forceUpdatePathTime; protected bool isOutOfDistance; protected int findAgentDestinationRadius; #endregion #region OVERRIDE METHODS. AI protected override void OnDrawGizmos() { base.OnDrawGizmos(); if (_debugLastTargetPosition) { if (currentTarget.transform && _hasPositionOfTheTarget) { var color = _targetInLineOfSight ? Color.green : Color.red; color.a = 0.2f; Gizmos.color = color; Gizmos.DrawLine(transform.position + Vector3.up * 1.5f, lastTargetPosition + Vector3.up * 1.5f); color.a = 1; Gizmos.color = color; Gizmos.DrawLine(lastTargetPosition, lastTargetPosition + Vector3.up * 1.5f); var forward = (lastTargetPosition - transform.position).normalized; forward.y = 0; var right = Quaternion.AngleAxis(90, Vector3.up) * forward; var p1 = lastTargetPosition + Vector3.up * 1.5f - forward; var p2 = lastTargetPosition + Vector3.up * 1.5f + forward * 0.5f + right * 0.25f; var p3 = lastTargetPosition + Vector3.up * 1.5f + forward * 0.5f - right * 0.25f; Gizmos.DrawLine(p1, p2); Gizmos.DrawLine(p1, p3); Gizmos.DrawLine(p3, p2); Gizmos.DrawSphere(lastTargetPosition + Vector3.up * 1.5f, 0.1f); } } } protected override void Start() { _receivedDamage = new vAIReceivedDamegeInfo(); changeWaypointDistance = _changeWaypointDistance; selfStartPosition = (!_selfStartingPoint && _customStartingPoint) ? _customStartingPoint.position : transform.position; _destination = transform.position; lasDestination = _destination; navMeshAgent = GetComponent(); if (!navMeshAgent) return; navMeshAgent.updatePosition = false; navMeshAgent.updateRotation = false; if (isOnNavMesh) navMeshAgent.enabled = true; RotateTo(transform.forward); if (currentTarget.transform != null) currentTarget.InitTarget(currentTarget.transform); _headtrack = GetComponent(); targetsBuffer = new Collider[maxTargetsDetection]; base.Start(); aiComponents = new Dictionary(); var _aiComponents = GetComponents(); for (int i = 0; i < _aiComponents.Length; i++) { if (!aiComponents.ContainsKey(_aiComponents[i].ComponentType)) { aiComponents.Add(_aiComponents[i].ComponentType, _aiComponents[i]); } } StartCoroutine(AlignDetectionPoint()); this.InvokeInitAI(); } protected virtual IEnumerator AlignDetectionPoint() { yield return new WaitForSeconds(.1f); if (detectionPointReference) detectionPointReference.rotation = transform.rotation; } protected override void UpdateAI() { base.UpdateAI(); CalcMovementDirection(); HandleTarget(); if (receivedDamage != null) receivedDamage.Update(); } public override void ResetRagdoll() { base.ResetRagdoll(); if (_headtrack) _headtrack.canLook = true; } public override void EnableRagdoll() { base.EnableRagdoll(); if (_headtrack) _headtrack.canLook = false; } public override void RemoveComponents() { base.RemoveComponents(); if (removeComponentsAfterDie) Destroy(navMeshAgent); } protected override void OnAnimatorMove() { try { if (Time.deltaTime == 0 || animator == null) return; if (!customAction && useNavMeshAgent && navMeshAgent && navMeshAgent.enabled) { navMeshAgent.velocity = ((animator.deltaPosition) / Time.deltaTime) * Mathf.Clamp(remainingDistanceWithoutAgent - stopingDistance, 0, 1f); //navMeshAgent.speed = Mathf.Clamp((float)System.Math.Round((double)(animator.deltaPosition / Time.deltaTime).magnitude , 2), 0.5f, maxSpeed); navMeshAgent.speed = Mathf.Lerp(navMeshAgent.speed, maxSpeed, aceleration * Time.deltaTime); navMeshAgent.nextPosition = animator.rootPosition; } } catch (Exception e) { Debug.LogException(e, this); } base.OnAnimatorMove(); } public override void Stop() { base.Stop(); if (useNavMeshAgent && navMeshAgent && navMeshAgent.isOnNavMesh && !navMeshAgent.isStopped) { //_turnOnSpotDirection = transform.forward; //temporaryDirection = transform.forward; navMeshAgent.isStopped = true; this.destination = transform.position; ForceUpdatePath(); navMeshAgent.ResetPath(); } } public override void DisableAIController() { if (disableAgentOnStart && navMeshAgent) navMeshAgent.enabled = false; base.DisableAIController(); } #endregion #region METHODS. AIAgent/Interfaces #region Protected methods protected virtual Dictionary aiComponents { get; set; } protected virtual vWaypoint GetWaypoint() { if (waypointArea == null) return null; var waypoints = waypointArea.GetValidPoints(_invertWaypointsOrder); if (!isWaypointStarted) { if (startUsingSpecificWaypoint) _currentWaypoint = startWaypointIndex % waypoints.Count; else if (startUsingNearWayPoint) _currentWaypoint = GetNearPointIndex(); else if (_randomWaypoint) _currentWaypoint = UnityEngine.Random.Range(0, waypoints.Count); else _currentWaypoint = 0; } if (isWaypointStarted) { if (_randomWaypoint) _currentWaypoint = UnityEngine.Random.Range(0, waypoints.Count); else _currentWaypoint++; } if (!isWaypointStarted) { isWaypointStarted = true; visitedWaypoints = new List(); } if (_currentWaypoint >= waypoints.Count) _currentWaypoint = 0; if (waypoints.Count == 0) return null; if (visitedWaypoints.Count == waypoints.Count) visitedWaypoints.Clear(); if (visitedWaypoints.Contains(waypoints[_currentWaypoint])) return null; return waypoints[_currentWaypoint]; } public virtual int GetNearPointIndex() { var waypoint = waypointArea.GetValidPoints(_invertWaypointsOrder); int targetWay = 0; var dist = Mathf.Infinity; for (int i = 0; i < waypoint.Count; i++) { var d = Vector3.Distance(transform.position, waypoint[i].position); if (d < dist) { targetWay = i; dist = d; } } return targetWay; } protected virtual float GetUpdateTimeFromQuality(vAIUpdateQuality quality) { return quality == vAIUpdateQuality.VeryLow ? 2 : quality == vAIUpdateQuality.Low ? 1f : quality == vAIUpdateQuality.Medium ? 0.75f : quality == vAIUpdateQuality.High ? .25f : 0.1f; } protected virtual Vector3 destination { get { return _destination; } set { _destination = value; } } protected virtual void UpdateAgentPath() { updatePathTime -= Time.deltaTime; if (updatePathTime > 0 && forceUpdatePathTime <= 0f && navMeshAgent.hasPath) return; forceUpdatePathTime -= Time.deltaTime; updatePathTime = GetUpdateTimeFromQuality(updatePathQuality); if (!isDead && !isJumping && isGrounded) { var destin = _destination; if ((movementSpeed != vAIMovementSpeed.Idle && destin != lasDestination) || !navMeshAgent.hasPath) { if (navMeshAgent.enabled && navMeshAgent.isOnNavMesh) { if (UnityEngine.AI.NavMesh.SamplePosition(destin, out navHit, _capsuleCollider.radius + findAgentDestinationRadius, navMeshAgent.areaMask) && (navHit.position - navMeshAgent.destination).magnitude > stopingDistance) { navMeshAgent.destination = (navHit.position); lasDestination = destin; } else if ((navHit.position - navMeshAgent.destination).magnitude > stopingDistance) { findAgentDestinationRadius++; if (findAgentDestinationRadius >= 10) { findAgentDestinationRadius = 0; } } } } } } protected virtual void CalcMovementDirection() { if (isDead || isJumping) return; if (useNavMeshAgent && navMeshAgent) { ControlNavMeshAgent(); UpdateAgentPath(); } bool forceMovement = !navMeshAgent.hasPath && remainingDistanceWithoutAgent > navMeshAgent.stoppingDistance + _capsuleCollider.radius; var dir = !forceMovement && navMeshAgent != null && navMeshAgent.enabled && useNavMeshAgent ? desiredVelocity * (!isInDestination ? 1 : 0) : ((new Vector3(destination.x, transform.position.y, destination.z) - transform.position).normalized * Mathf.Clamp(remainingDistanceWithoutAgent - stopingDistance, 0, 1f)); //Convert Direction to Input var movementInput = transform.InverseTransformDirection(dir); if (useNavMeshAgent && navMeshAgent.enabled) { var data = navMeshAgent.currentOffMeshLinkData; if (navMeshAgent.isOnOffMeshLink) { dir = (data.endPos - transform.position); movementInput = transform.InverseTransformDirection(dir); } } if (movementInput.magnitude > 0.1f) { if (temporaryDirectionTime <= 0 && isStrafing == false) SetMovementInput(movementInput, aceleration); else SetMovementInput(movementInput, temporaryDirectionTime <= 0 ? transform.forward : temporaryDirection, aceleration); } else input = Vector3.zero; if (!isGrounded || isJumping || isRolling) navMeshAgent.enabled = false; } public virtual Vector3 desiredVelocity => navMeshAgent.desiredVelocity; protected virtual void CheckAgentDistanceFromAI() { if (!useNavMeshAgent || !navMeshAgent || !navMeshAgent.enabled) return; if (Vector3.Distance(transform.position, navMeshAgent.nextPosition) > stopingDistance * 1.5f && !isOutOfDistance) { timeToResetOutDistance = 3f; isOutOfDistance = true; } if (isOutOfDistance) { timeToResetOutDistance -= Time.deltaTime; if (timeToResetOutDistance <= 0) { isOutOfDistance = false; if (Vector3.Distance(transform.position, navMeshAgent.nextPosition) > stopingDistance) { navMeshAgent.enabled = false; } } } } protected virtual void ControlNavMeshAgent() { if (isDead) return; if (useNavMeshAgent && navMeshAgent) navMeshAgent.stoppingDistance = stopingDistance; if (Time.deltaTime == 0 || navMeshAgent.enabled == false) { if (!ragdolled && !isJumping && isGrounded && !navMeshAgent.enabled && isOnNavMesh) { navMeshAgent.enabled = true; } } if (navMeshAgent.enabled && isOnJumpLink && !isJumping && isGrounded) { var jumpDir = navMeshAgent.currentOffMeshLinkData.endPos - transform.position; var jumpTarget = transform.position + jumpDir.normalized * (jumpDir.magnitude + stopingDistance); JumpTo(jumpTarget); } if (isJumping || !isGrounded || ragdolled) navMeshAgent.enabled = false; CheckAgentDistanceFromAI(); } protected virtual bool CheckCanSeeTarget() { if (currentTarget != null && currentTarget.transform != null && currentTarget.collider == null && InFOVAngle(currentTarget.transform.position, _fieldOfView)) { if (sightMethod == 0) return true; var eyesPoint = detectionPointReference ? detectionPointReference.position : transform.position + Vector3.up * (selfCollider.bounds.size.y * 0.8f); if (!Physics.Linecast(eyesPoint, currentTarget.transform.position, _obstacles)) { if (_debugRaySight) Debug.DrawLine(eyesPoint, currentTarget.transform.position, Color.green, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); return true; } else { if (_debugRaySight) Debug.DrawLine(eyesPoint, currentTarget.transform.position, Color.red, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); } } else if (currentTarget.collider) return CheckCanSeeTarget(currentTarget.collider); return false; } protected virtual bool CheckCanSeeTarget(Collider target) { if (target != null && InFOVAngle(target.bounds.center, _fieldOfView)) { if (sightMethod == 0) return true; var detectionPoint = detectionPointReference ? detectionPointReference.position : transform.position + Vector3.up * (selfCollider.bounds.size.y * 0.8f); if (sightMethod.Contains(vAISightMethod.Center)) if (!Physics.Linecast(detectionPoint, target.bounds.center, _obstacles)) { if (_debugRaySight) Debug.DrawLine(detectionPoint, target.bounds.center, Color.green, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); return true; } else { if (_debugRaySight) Debug.DrawLine(detectionPoint, target.bounds.center, Color.red, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); } if (sightMethod.Contains(vAISightMethod.Top)) if (!Physics.Linecast(detectionPoint, target.transform.position + Vector3.up * target.bounds.size.y * 0.9f, _obstacles)) { if (_debugRaySight) Debug.DrawLine(detectionPoint, target.transform.position + Vector3.up * target.bounds.size.y * 0.9f, Color.green, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); return true; } else { if (_debugRaySight) Debug.DrawLine(detectionPoint, target.transform.position + Vector3.up * target.bounds.size.y * 0.9f, Color.red, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); } if (sightMethod.Contains(vAISightMethod.Bottom)) if (!Physics.Linecast(detectionPoint, target.transform.position + Vector3.up * target.bounds.size.y * 0.1f, _obstacles)) { if (_debugRaySight) Debug.DrawLine(detectionPoint, target.transform.position + Vector3.up * target.bounds.size.y * 0.1f, Color.green, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); return true; } else { if (_debugRaySight) Debug.DrawLine(detectionPoint, target.transform.position + Vector3.up * target.bounds.size.y * 0.1f, Color.red, GetUpdateTimeFromQuality(canseeTargetUpdateQuality)); } } return false; } protected virtual bool InFOVAngle(Vector3 viewPoint, float fieldOfView) { var eyesPoint = (detectionPointReference ? detectionPointReference.position : _capsuleCollider.bounds.center); if (Vector3.Distance(eyesPoint, viewPoint) < _minDistanceToDetect) return true; if (Vector3.Distance(eyesPoint, viewPoint) > _maxDistanceToDetect) return false; var lookDirection = viewPoint - eyesPoint; var rot = Quaternion.LookRotation(lookDirection, Vector3.up); var detectionAngle = detectionPointReference ? detectionPointReference.eulerAngles : transform.eulerAngles; var newAngle = rot.eulerAngles - detectionAngle; var fovAngleY = newAngle.NormalizeAngle().y; var fovAngleX = newAngle.NormalizeAngle().x; if (fovAngleY <= (fieldOfView * 0.5f) && fovAngleY >= -(fieldOfView * 0.5f) && fovAngleX <= (fieldOfView * 0.5f) && fovAngleX >= -(fieldOfView * 0.5f)) return true; return false; } protected virtual void HandleTarget() { if (_hasPositionOfTheTarget && currentTarget.transform) lastTargetPosition = currentTarget.transform.position; canseeTargetUpdateTime -= Time.deltaTime; if (canseeTargetUpdateTime > 0) return; if (currentTarget != null && currentTarget.transform) { _targetInLineOfSight = CheckCanSeeTarget(); if (!_targetInLineOfSight || targetDistance >= (_maxDistanceToDetect + _lostTargetDistance)) { if (lostTargetTime < Time.time) { _hasPositionOfTheTarget = false; lostTargetTime = Time.time + _timeToLostWithoutSight; } } else { lostTargetTime = Time.time + _timeToLostWithoutSight; _hasPositionOfTheTarget = true; currentTarget.isLost = false; } } else { _targetInLineOfSight = false; _hasPositionOfTheTarget = false; } HandleLostTarget(); HandleTargetsInRange(); canseeTargetUpdateTime = GetUpdateTimeFromQuality(canseeTargetUpdateQuality); } protected virtual void HandleLostTarget() { if (currentTarget != null && currentTarget.transform != null) { if ((currentTarget.isDead || targetDistance > (_maxDistanceToDetect + _lostTargetDistance) || (!targetInLineOfSight && !_hasPositionOfTheTarget))) { if (currentTarget.isFixedTarget) currentTarget.isLost = true; else currentTarget.ClearTarget(); } else if ((currentTarget.transform == null || !currentTarget.transform.gameObject.activeSelf || targetDistance > (_maxDistanceToDetect + _lostTargetDistance) || (!targetInLineOfSight && !_hasPositionOfTheTarget))) { if (currentTarget.isFixedTarget) currentTarget.isLost = true; else currentTarget.ClearTarget(); } } } protected virtual void HandleTargetsInRange() { for (int i = 0; i < targetsInRange.Count; i++) { var t = targetsInRange[i]; if (t.transform) { if (t.transform == currentTarget.transform) _currentTarget=t; var distance = Vector3.Distance(t.transform.position, transform.position); if (distance > _maxDistanceToDetect + _lostTargetDistance) { targetsInRange.RemoveAt(i); break; } } else { targetsInRange.RemoveAt(i); break; } } } protected virtual bool IsInLayerMask(int layer, LayerMask layermask) { return layermask == (layermask | (1 << layer)); } #endregion #region Public methods public float fieldOfView { get => _fieldOfView; set => _fieldOfView = value; } public float minDistanceToDetect { get => _minDistanceToDetect; set => _minDistanceToDetect = value; } public float maxDistanceToDetect { get => _maxDistanceToDetect; set => _maxDistanceToDetect = value; } public vAISightMethod SightMethod { get => sightMethod; set => sightMethod = value; } public vAIUpdateQuality UpdatePathQuality { get => updatePathQuality; set => updatePathQuality = value; } public vAIUpdateQuality FindTargetUpdateQuality { get => findTargetUpdateQuality; set => findTargetUpdateQuality = value; } public vAIUpdateQuality CanseeTargetUpdateQuality { get => canseeTargetUpdateQuality; set => canseeTargetUpdateQuality = value; } public virtual void SetDetectionLayer(LayerMask mask) { _detectLayer = mask; } public virtual void SetDetectionTags(List tags) { _detectTags = tags; } public virtual void SetObstaclesLayer(LayerMask mask) { _obstacles = mask; } public virtual void SetLineOfSight(float fov = -1, float minDistToDetect = -1, float maxDistToDetect = -1, float lostTargetDistance = -1) { if (fov != -1) _fieldOfView = fov; if (minDistToDetect != -1) _minDistanceToDetect = minDistToDetect; if (maxDistToDetect != -1) _maxDistanceToDetect = maxDistToDetect; if (lostTargetDistance != -1) _lostTargetDistance = lostTargetDistance; } public virtual vAIReceivedDamegeInfo receivedDamage { get { return _receivedDamage; } protected set { _receivedDamage = value; } } public virtual bool targetInLineOfSight { get { return _targetInLineOfSight; } } public virtual vAITarget currentTarget { get { return _currentTarget; } protected set { _currentTarget = value; } } public virtual Vector3 lastTargetPosition { get { return _lastTargetPosition; } protected set { _lastTargetPosition = value; } } public virtual float targetDistance { get { if (currentTarget == null || currentTarget.isDead) return Mathf.Infinity; return Vector3.Distance(currentTarget.transform.position, transform.position); } } public virtual List GetTargetsInRange() { return targetsInRange; } public virtual void FindTarget() { FindSpecificTarget(_detectTags, _detectLayer, true); } public virtual void FindTarget(bool checkForObstacles) { FindSpecificTarget(_detectTags, _detectLayer, checkForObstacles); } public virtual void FindSpecificTarget(List m_detectTags, LayerMask m_detectLayer, bool checkForObstables = true) { if (updateFindTargetTime > Time.time) return; updateFindTargetTime = Time.time + GetUpdateTimeFromQuality(findTargetUpdateQuality); int targetsCount = Physics.OverlapSphereNonAlloc(transform.position + transform.up, _maxDistanceToDetect, targetsBuffer, m_detectLayer); if (targetsCount > 0) { Transform target = currentTarget != null && _hasPositionOfTheTarget ? currentTarget.transform : null; var _targetDistance = target && targetInLineOfSight ? targetDistance : Mathf.Infinity; bool targetFound = false; for (int i = 0; i < targetsCount; i++) { if (targetsBuffer[i] != null && targetsBuffer[i].transform != transform && m_detectTags.Contains(targetsBuffer[i].gameObject.tag)) { ///Add new target in range if(!targetsInRange.Exists(t=>t.transform == targetsBuffer[i].transform)) { vAITarget t = new vAITarget(targetsBuffer[i].transform); if(t.isDead==false) targetsInRange.Add(new vAITarget(targetsBuffer[i])); } if (!findOtherTarget && currentTarget.transform) continue; if (currentTarget.transform && currentTarget.isFixedTarget && !findOtherTarget) continue; ///Store new target if ( (findTargetByDistance || !targetFound) && (!checkForObstables || CheckCanSeeTarget(targetsBuffer[i]))) { if (findTargetByDistance) { var newTargetDistance = Vector3.Distance(targetsBuffer[i].transform.position, transform.position); if (newTargetDistance < _targetDistance) { target = targetsBuffer[i].transform; targetFound = true; _targetDistance = newTargetDistance; } } else { targetFound = true; target = targetsBuffer[i].transform; } } } } if (currentTarget == null || target != null && target != currentTarget.transform) { if (target != null) SetCurrentTarget(targetsInRange.Find(t=>t==target)); } } } public virtual bool TryGetTarget(out vAITarget target) { return TryGetTarget(_detectTags, out target); } public virtual bool TryGetTarget(string tag, out vAITarget target) { var ts = targetsInRange.FindAll(c => c != null && c.transform!=null && c.transform.gameObject.CompareTag(tag)); if (ts != null && ts.Count > 1) { ts= ts.OrderBy(t => Vector3.Distance(t.transform.position, transform.position)).ToList(); } if (ts != null && ts.Count > 0) { target = ts[0]; return true; } target = null; return false; } public virtual bool TryGetTarget(List detectTags, out vAITarget target) { var ts = targetsInRange.FindAll(c => c != null && c.transform != null && detectTags.Contains(c.transform.gameObject.tag)); if (ts != null && ts.Count > 1) { ts = ts.OrderBy(t => Vector3.Distance(t.transform.position, transform.position)).ToList(); } if (ts != null && ts.Count > 0) { target = ts[0]; return true; } target = null; return false; } public virtual void SetCurrentTarget(Transform target) { SetCurrentTarget(target, false); } public virtual void SetCurrentTarget(vAITarget target) { SetCurrentTarget(target, false); } public virtual void SetCurrentTarget(Transform target, bool overrideCanseTarget) { if (target == null) return; changeTargetTime = _changeTargetDelay + Time.time; currentTarget.InitTarget(target); if (overrideCanseTarget) { currentTarget.isLost = false; _targetInLineOfSight = true; _hasPositionOfTheTarget = true; } updateFindTargetTime = 0f; updatePathTime = 0f; lastTargetPosition = target.position; LookToTarget(target, 2); } public virtual void SetCurrentTarget(vAITarget target, bool overrideCanseTarget) { if (target.transform == null) return; changeTargetTime = _changeTargetDelay + Time.time; currentTarget = target; if (overrideCanseTarget) { currentTarget.isLost = false; _targetInLineOfSight = true; _hasPositionOfTheTarget = true; } updateFindTargetTime = 0f; updatePathTime = 0f; lastTargetPosition = target.transform.position; LookToTarget(target, 2); } public virtual void RemoveCurrentTarget() { currentTarget.ClearTarget(); } public virtual void LookAround() { if (_headtrack) _headtrack.LookAround(); } public virtual void LookTo(Vector3 point, float stayLookTime = 1, float offsetLookHeight = -1) { if (_headtrack) _headtrack.LookAtPoint(point, stayLookTime, offsetLookHeight); } public virtual void LookToTarget(Transform target, float stayLookTime = 1, float offsetLookHeight = -1) { if (_headtrack) _headtrack.LookAtTarget(target, stayLookTime, offsetLookHeight); } public virtual void SetSpeed(vAIMovementSpeed movementSpeed) { if (this.movementSpeed != movementSpeed) { if (movementSpeed == vAIMovementSpeed.Idle) { Stop(); } base.movementSpeed = movementSpeed; } } public virtual bool isInDestination { get { if (useNavMeshAgent && (remainingDistance <= stopingDistance || navMeshAgent.hasPath && remainingDistance > stopingDistance && desiredVelocity.magnitude < 0.1f)) return true; return remainingDistance <= stopingDistance; } } public virtual bool isMoving { get { return input.sqrMagnitude > 0.1f; } } public virtual float remainingDistance { get { return navMeshAgent && navMeshAgent.enabled && useNavMeshAgent && isOnNavMesh ? navMeshAgent.remainingDistance : remainingDistanceWithoutAgent; } } protected virtual float remainingDistanceWithoutAgent { get { return Vector3.Distance(transform.position, new Vector3(destination.x, transform.position.y, destination.z)); } } public virtual Collider selfCollider { get { return _capsuleCollider; } } public virtual bool isOnJumpLink { get { if (!useNavMeshAgent) return false; if (navMeshAgent.isOnOffMeshLink && navMeshAgent.currentOffMeshLinkData.linkType == UnityEngine.AI.OffMeshLinkType.LinkTypeJumpAcross) return true; var linkData = navMeshAgent.currentOffMeshLinkData.offMeshLink; if (linkData != null) { if (linkData.area == UnityEngine.AI.NavMesh.GetAreaFromName("Jump")) { return true; } } return false; } } public virtual bool isOnNavMesh { get { if (!useNavMeshAgent) return false; if (navMeshAgent.enabled) return navMeshAgent.isOnNavMesh; if (UnityEngine.AI.NavMesh.SamplePosition(transform.position, out navMeshHit, _capsuleCollider.radius, navMeshAgent.areaMask)) { return true; } return false; } } public virtual void MoveTo(Vector3 newDestination, vAIMovementSpeed speed = vAIMovementSpeed.Walking) { if (isStrafing) updatePathTime = 0; SetFreeLocomotion(); SetSpeed(speed); var dir = newDestination - transform.position; dir.y = 0; this.destination = newDestination; temporaryDirection = transform.forward; temporaryDirectionTime = 0; } public virtual void StrafeMoveTo(Vector3 newDestination, Vector3 targetDirection, vAIMovementSpeed speed = vAIMovementSpeed.Walking) { if (useNavMeshAgent && navMeshAgent && navMeshAgent.isOnNavMesh && navMeshAgent.isStopped) navMeshAgent.isStopped = false; SetStrafeLocomotion(); SetSpeed(speed); destination = newDestination; temporaryDirection = targetDirection; temporaryDirectionTime = 2f; } public virtual void StrafeMoveTo(Vector3 newDestination, vAIMovementSpeed speed = vAIMovementSpeed.Walking) { if (useNavMeshAgent && navMeshAgent && navMeshAgent.isOnNavMesh && navMeshAgent.isStopped) navMeshAgent.isStopped = false; SetStrafeLocomotion(); SetSpeed(speed); destination = newDestination; } public virtual void RotateTo(Vector3 targetDirection) { targetDirection.y = 0; if (Vector3.Angle(transform.forward, targetDirection) > 20) { temporaryDirection = targetDirection; temporaryDirectionTime = 2f; } } public virtual Vector3 targetDestination { get { return _destination; } } public virtual float stopingDistance { get { return stopingDistanceRelativeToSpeed + _stopingDistance; } set { _stopingDistance = value; } } protected virtual float stopingDistanceRelativeToSpeed { get { return movementSpeed == vAIMovementSpeed.Idle ? 1 : movementSpeed == vAIMovementSpeed.Running ? _runningStopingDistance : movementSpeed == vAIMovementSpeed.Sprinting ? _sprintingStopingDistance : _walkingStopingDistance; } } public virtual Vector3 selfStartPosition { get; set; } public virtual vWaypointArea waypointArea { get { return _waypointArea; } set { if (value != null && value != _waypointArea) { var waypoints = value.GetValidPoints(); if (_randomStartingPoint) _currentWaypoint = UnityEngine.Random.Range(0, waypoints.Count); } _waypointArea = value; } } public virtual vWaypoint targetWaypoint { get; protected set; } public virtual List visitedWaypoints { get; set; } public virtual bool selfStartingPoint { get { return _selfStartingPoint; } protected set { _selfStartingPoint = value; } } public virtual float changeWaypointDistance { get; protected set; } public virtual bool customStartPoint { get { return !selfStartingPoint && _customStartingPoint != null; } } public virtual Vector3 customStartPosition { get { return customStartPoint ? _customStartingPoint.position : transform.position; } } public virtual void NextWayPoint() { targetWaypoint = GetWaypoint(); } public override void TakeDamage(vDamage damage) { base.TakeDamage(damage); if (damage.damageValue > 0) { //Check condition to add a new target if (!currentTarget.transform || (currentTarget.transform && !currentTarget.isFixedTarget || (currentTarget.isFixedTarget && findOtherTarget))) { //Check if new target is in detections settings if (damage.sender && IsInLayerMask(damage.sender.gameObject.layer, _detectLayer) && _detectTags.Contains(damage.sender.gameObject.tag)) { SetCurrentTarget(damage.sender, false); } } receivedDamage.UpdateDamage(damage); updatePathTime = 0f; } } public virtual void ForceUpdatePath(float timeInUpdate = 1f) { forceUpdatePathTime = timeInUpdate; } public virtual bool HasComponent() where T : vIAIComponent { if (aiComponents == null) return false; return aiComponents.ContainsKey(typeof(T)); } public virtual T GetAIComponent() where T : vIAIComponent { return aiComponents.ContainsKey(typeof(T)) ? (T)aiComponents[typeof(T)] : default(T); } protected List triggers = new List(); float checkTriggerFrequency; protected override void OnTriggerEnter(Collider other) { base.OnTriggerEnter(other); if (!triggers.Contains(other)) triggers.Add(other); } protected override void OnTriggerExit(Collider other) { if (triggers.Contains(other)) triggers.Remove(other); } protected override void OnTriggerStay(Collider other) { base.OnTriggerStay(other); if (checkTriggerFrequency < Time.time) { triggers = triggers.FindAll(c => c != null); checkTriggerFrequency = Time.time + 2f; } } public bool IsInTriggerWithTag(string tag) { return triggers.Exists(c => c != null && c.gameObject.CompareTag(tag)); } public bool IsInTriggerWithName(string name) { return triggers.Exists(c => c != null && c.gameObject.name.Equals(name)); } public bool IsInTriggerWithTag(string tag, out Collider result) { var _c = triggers.Find(c => c != null && c.gameObject.CompareTag(tag)); result = _c; return _c != null; } public bool IsInTriggerWithName(string name, out Collider result) { var _c = triggers.Find(c => c != null && c.gameObject.name.Equals(name)); result = _c; return _c != null; } #endregion #endregion } }