Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Closest gameobject in Line of Sight?

Discussion in 'Scripting' started by RaGEn, Apr 21, 2017.

  1. RaGEn

    RaGEn

    Joined:
    Feb 3, 2015
    Posts:
    88
    Hello

    I'm attempting to create some AI for my game. I have them using a closest target function. I'm using unity reference code to find the closest targets.

    Code (CSharp):
    1. GameObject FindClosestEnemy()
    2.     {
    3.         GameObject[] gos;
    4.         gos = GameObject.FindGameObjectsWithTag(EnemyFaction);
    5.         GameObject closest = null;
    6.         float distance = Mathf.Infinity;
    7.         Vector3 position = transform.position;
    8.         foreach (GameObject go in gos)
    9.         {
    10.             Vector3 diff = go.transform.position - position;
    11.             float curDistance = diff.sqrMagnitude;
    12.             if (curDistance < distance)
    13.             {
    14.                 closest = go;
    15.                 distance = curDistance;
    16.             }
    17.         }
    18.         return closest;
    19.     }
    However my AI sometimes finds the closest target which is on the other side of a wall. I know I have to use raycasts to find the line of sight of the target. However I was wondering if anyone knows away how this FindClosestEnemy function can check to see if the target is in a line of sight?

    Because I have it so the AI won't attack enemies it can't see, however if an enemy is standing beside it in another room it'll attempt to target it instead of an enemy in front of it just slightly further away.

    Thank you for you time.
     
  2. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    8,988
  3. RaGEn

    RaGEn

    Joined:
    Feb 3, 2015
    Posts:
    88
  4. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    8,988
    yes, can put it there, maybe could check Dot first (to see if the target is in view at all),
    only then check for distances to get closest within view.
     
    Meri and RaGEn like this.
  5. RaGEn

    RaGEn

    Joined:
    Feb 3, 2015
    Posts:
    88
    Thank you for the help. I got it running. You are beyond amazing!
     
  6. GCMSA

    GCMSA

    Joined:
    Mar 29, 2017
    Posts:
    16
    Hi,

    Please, check this code. I wrote it to find the nearest exit. Then, at the movement phase I checked the distance to the nearest exit until the agent reached it.

    I hope it can be helpful for you and others.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine.AI;
    5.  
    6. public class AgentController : MonoBehaviour {
    7.     public float preMouvementTime = 0f ;
    8.     private bool preMovementPhaseFinished = false;
    9.     public GameObject[] knownExits;
    10.     private GameObject target;
    11.     NavMeshAgent navMeshAgent;
    12.     //Animator animator;
    13.  
    14.     // Use this for initialization
    15.     void Awake ()
    16.     {
    17.         navMeshAgent = GetComponent<NavMeshAgent>();
    18.         target = new GameObject();
    19.     }
    20.  
    21.     void Start()
    22.     {
    23.         target = nearestExit();
    24.  
    25.        
    26.         //Debug.Log(target);
    27.     }
    28.    
    29.     // Update is called once per frame
    30.     void Update ()
    31.     {
    32.         float dist = Mathf.Infinity;
    33.  
    34.         if (!preMovementPhaseFinished)
    35.             StartCoroutine(preMouvementPhase());
    36.         else
    37.         {
    38.             //Mouvement Phase
    39.             navMeshAgent.destination = target.transform.position;
    40.            
    41.  
    42.             //calculate remaining distance to reach the target
    43.             dist = calculatePathLength(target.transform.position);
    44.            
    45.             if (dist <= navMeshAgent.stoppingDistance + 0.1f)
    46.             {
    47.                 navMeshAgent.gameObject.SetActive(false);
    48.                 Debug.Log(navMeshAgent.gameObject.name + " Exit time = " + Time.realtimeSinceStartup + "s");
    49.  
    50.             }
    51.         }
    52.  
    53.         //Debug.Log(dist);
    54.  
    55.  
    56.     }
    57.  
    58.     GameObject nearestExit()
    59.     {
    60.         GameObject nearestTarget = null;
    61.  
    62.         float[] distancesToKnownExits = new float[knownExits.Length];
    63.  
    64.         float minDistance = 9999f;
    65.  
    66.         for (int i = 0; i < knownExits.Length; i++)
    67.         {
    68.             distancesToKnownExits[i] = calculatePathLength(knownExits[i].transform.position);
    69.  
    70.             //Debug.Log(knownExits[i] + " " +  distancesToKnownExits[i]);
    71.         }
    72.  
    73.         for (int i = 0; i < distancesToKnownExits.Length; i++)
    74.         {
    75.             if (distancesToKnownExits[i] <= minDistance)
    76.             {
    77.                 minDistance = distancesToKnownExits[i];
    78.                 nearestTarget = knownExits[i];
    79.             }
    80.         }
    81.  
    82.         return nearestTarget;
    83.     }
    84.  
    85.     float calculatePathLength(Vector3 targetPosition)
    86.     {
    87.         NavMeshPath path = new NavMeshPath();
    88.        
    89.         float pathLength = 0f;
    90.  
    91.         if (navMeshAgent.enabled)
    92.         {
    93.             NavMesh.CalculatePath(transform.position,targetPosition,NavMesh.AllAreas, path);
    94.         }
    95.  
    96.         for (int i = 0; i < path.corners.Length - 1; i++)
    97.             Debug.DrawLine(path.corners[i], path.corners[i + 1], Color.red);
    98.  
    99.         Vector3[] allWayPoints = new Vector3[path.corners.Length + 2];
    100.  
    101.         allWayPoints[0] = this.gameObject.transform.position - this.gameObject.transform.up;
    102.         allWayPoints[allWayPoints.Length-1] = targetPosition;
    103.  
    104.         //Debug.Log(path.corners.Length);
    105.         //Debug.Log(allWayPoints.Length);
    106.  
    107.         for (int i = 0; i < path.corners.Length; i++)
    108.         {
    109.             allWayPoints[i + 1] = path.corners[i];
    110.         }
    111.  
    112.         for (int i = 0; i < allWayPoints.Length-1; i++)
    113.         {
    114.             pathLength += Vector3.Distance(allWayPoints[i], allWayPoints[i+1]);
    115.         }
    116.  
    117.  
    118.        
    119.         return pathLength;
    120.     }
    121.  
    122.     private IEnumerator preMouvementPhase()
    123.     {
    124.         //Debug.Log("Pre Movement Phase Started");
    125.         yield return new WaitForSeconds(preMouvementTime);
    126.         preMovementPhaseFinished = true;
    127.  
    128.     }
    129. }
    130.  
     
    RaGEn likes this.
  7. RaGEn

    RaGEn

    Joined:
    Feb 3, 2015
    Posts:
    88
    Hey thanks for the extra information! I'm very grateful! :D
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Here's what I do. I create sensors that can be attached as components to a GameObject. Which are then referenced in my AI scripts as possible eyes.
    https://github.com/lordofduct/spacepuppy-unity-framework/tree/master/SpacepuppyAI/AI/Sensors/Visual

    They get to see 'VisualAspects' that are attached to the entities that are visible.

    These sensors will return true if an entity is inside the confines of said sensor. Here you can see the outline of the sensor on our zombies in our current game:


    Now how it might be used is like here in my AI script:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy;
    7. using com.spacepuppy.AI;
    8. using com.spacepuppy.AI.Sensors;
    9. using com.spacepuppy.AI.Sensors.Visual;
    10. using com.spacepuppy.Pathfinding;
    11. using com.spacepuppy.Utils;
    12.  
    13. using com.mansion.Logic;
    14.  
    15. namespace com.mansion.Entities.Actors.Mobs
    16. {
    17.  
    18.     public class SenseEntityLogic : SPComponent, ILogic
    19.     {
    20.  
    21.         #region Fields
    22.  
    23.         [SerializeField]
    24.         [EnumFlags]
    25.         private IEntity.EntityType _targetEntityType = IEntity.EntityType.Player;
    26.  
    27.         [SerializeField()]
    28.         [DefaultFromSelf()]
    29.         private Sensor _eyes;
    30.  
    31.         [SerializeField]
    32.         [AIVariableName]
    33.         private string _targetVariable;
    34.  
    35.         [SerializeField]
    36.         [TypeRestriction(typeof(IAIState))]
    37.         private Component _onEntityFoundState;
    38.  
    39.  
    40.         //internal stuff
    41.         [System.NonSerialized()]
    42.         private IEntity _entity;
    43.    
    44.         #endregion
    45.  
    46.         #region CONSTRUCTOR
    47.  
    48.         protected override void Awake()
    49.         {
    50.             base.Start();
    51.  
    52.             _entity = SPEntity.Pool.GetFromSource<IEntity>(this);
    53.         }
    54.  
    55.         #endregion
    56.    
    57.         #region ILogic Interface
    58.  
    59.         void ILogic.OnEnterState(AILogicController ai, IAIState lastState)
    60.         {
    61.  
    62.         }
    63.  
    64.         void ILogic.OnExitState(AILogicController ai, IAIState nextState)
    65.         {
    66.  
    67.         }
    68.  
    69.         ActionResult ILogic.Tick(AILogicController ai)
    70.         {
    71.             if (_entity.Stalled) return ActionResult.None;
    72.  
    73.             var aspect = (from a in _eyes.SenseAll()
    74.                           let e = SPEntity.Pool.GetFromSource<IEntity>(a)
    75.                           where e.Type.HasAnyFlag(_targetEntityType)
    76.                           select a).FirstOrDefault();
    77.  
    78.             IEntity entity;
    79.             if (aspect != null && SPEntity.Pool.GetFromSource<IEntity>(aspect, out entity))
    80.             {
    81.                 ai.Variables[_targetVariable] = entity;
    82.                 ai.States.ChangeState(_onEntityFoundState as IAIState);
    83.                 return ActionResult.Success;
    84.             }
    85.  
    86.             return ActionResult.None;
    87.         }
    88.  
    89.         #endregion
    90.  
    91.     }
    92.  
    93. }
    94.  
    The code in question is this:

    Code (csharp):
    1.  
    2.             var aspect = (from a in _eyes.SenseAll()
    3.                           let e = SPEntity.Pool.GetFromSource<IEntity>(a)
    4.                           where e.Type.HasAnyFlag(_targetEntityType)
    5.                           select a).FirstOrDefault();
    6.  
    I'm basically saying give me all entities that the eyes overlap, that have a Entity script, and that Entity type matches the type this sensor targets.

    This will grab just anything though... now if we want to do by 'distance' and 'in front of' like you ask in OP... I do this.

    Code (csharp):
    1.  
    2.         private void AttemptActivate()
    3.         {
    4.             var trans = _entity.transform;
    5.             var pos = trans.position.SetY(0f);
    6.             var forw = trans.forward.SetY(0f);
    7.             var aspect = (from a in _actionSensor.SenseAll()
    8.                           where a.gameObject.EntityHasComponent<IEntity>()
    9.                           let p = a.transform.position.SetY(0f)
    10.                           orderby -a.Precedence, VectorUtil.AngleBetween(p, forw), Vector3.Distance(p, pos) ascending
    11.                           select a).FirstOrDefault();
    12.             var go = ObjUtil.GetAsFromSource<GameObject>(aspect);
    13.  
    14.             if (go != null)
    15.             {
    16.                 IInteractable comp;
    17.                 if (go.FindComponent<IInteractable>(out comp))
    18.                 {
    19.                     switch (comp.InteractWith(_entity))
    20.                     {
    21.                         case InteractionType.None:
    22.                         case InteractionType.Press:
    23.                             break;
    24.                         case InteractionType.Hold:
    25.                             if (_heldInteractable != null) _heldInteractable.Release();
    26.                             _heldInteractable = comp;
    27.                             break;
    28.                     }
    29.                 }
    30.             }
    31.         }
    32.  
    This is actually on my player motor, and it's how when the player presses 'activate' button I decide which of any nearby objects activate.

    Here is the specific linq statement to do it:

    Code (csharp):
    1.  
    2.             var trans = _entity.transform;
    3.             var pos = trans.position.SetY(0f);
    4.             var forw = trans.forward.SetY(0f);
    5.             var aspect = (from a in _actionSensor.SenseAll()
    6.                           where a.gameObject.EntityHasComponent<IEntity>()
    7.                           let p = a.transform.position.SetY(0f)
    8.                           orderby -a.Precedence, VectorUtil.AngleBetween(p, forw), Vector3.Distance(p, pos) ascending
    9.                           select a).FirstOrDefault();
    10.  
    If get all the aspects that are in my sensors view area.
    They must have an Entity script (it's just the way I identify things in my game).
    I get the position of them, which has the y zero'd out because I don't care about the height of it (my game is flat).
    Then I sort them by:
    Precedence - so that high priority objects will always be selected first
    Angle of forward - so that we get the thing closest to in front of the player
    Distance - so it's the closest object to the player

    Note the order of these in the 'orderby' is important. It will impact the order of sorting.

    In your code you could get a similar result like this:
    Code (csharp):
    1.  
    2. var gos = GameObject.FindGameObjectsWithTag(EnemyFaction);
    3. var pos = this.transform.position;
    4. pos.y = 0f;
    5. var forw = this.transform.forward;
    6. forw.y = 0f;
    7. var aspect = (from a in gos
    8.              let p = a.transform.position.SetY(0f)
    9.              orderby Vector.Angle(p, forw), Vector3.Distance(p, pos) ascending
    10.              select a).FirstOrDefault();
    11.  
     
    Last edited: Apr 21, 2017
    RaGEn likes this.
  9. RaGEn

    RaGEn

    Joined:
    Feb 3, 2015
    Posts:
    88
    Thank you for that in depth information! That is a brilliant idea. You deserve some kind of medal for this code and explanation! I'll give this a try! :D