therealkf
therealkf

Reputation: 49

Finding the closest target to me in Unity

I have a player class in Unity which all works fine except for my ClosestTarget function. The function works how I wanted it to but now it only ever picks Cube 2 (The last element in the list) even if I'm closer to another cube.

The class is as shown:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Player : MonoBehaviour
{

    public int health; //Current health
    public int stamina; //Current stamina
    public int maxHealth = 100; //Constant for max health
    public int maxStamina = 500; //Constant for max stamina
    protected CharacterController chCtrl; //Reference to the character controller
    protected CharacterMotor chMotor; //Reference to the character motor
    public float walkSpeed = 3; //Speed at which the player walks
    public float runSpeed = 20; //Speed at which the player runs
    public bool isWalking = false; //Check for whether the player is walking
    public bool isRunning = false; //Check for whether the player is running
    public bool isFatigued = false; //Check for whether the player is fatigued
    public List<Transform> targets; //Create a list of transforms
    public Transform currentTarget;
    float distance = Mathf.Infinity;

    // Use this for initialization
    void Start ()
    {
        //Get the character controller assigned to the current game object
        chCtrl = GetComponent<CharacterController> ();
        //Get the character motor assigned to the current game object
        chMotor = GetComponent<CharacterMotor> ();
        //Set the stamina to the max stamina
        stamina = maxStamina;
        //Set the health to the max health
        health = maxHealth;
        //Initialise the targets list
        targets = new List<Transform>();
        //Call the function to retrieve all buttons in the scene
        AddAllButtons();
    }

    // Update is called once per frame
    void Update ()
    {
        //Call the function to set the speed of the player
        SetSpeed ();

        ClosestTarget();
    }

    public void SetSpeed ()
    {
        //Set the player to walking speed by default
        float speed = walkSpeed;

        //If the stamina is less than or equal to 0
        if (stamina <= 0) 
        {
            //Set the player as fatigued
            isFatigued = true;
            //Set the player to walking
            speed = walkSpeed;
            //Set stamina to 0
            stamina = 0;
        }
        //If the stamina is greater than or equal to max stamina
        if(stamina >= maxStamina)
        {
            //Set the stamina to the max stamina
            stamina = maxStamina;
            //Set the player as not fatigued
            isFatigued = false;
        }
        //If the player is moving along either the x or y axis
        if (Input.GetAxis("Horizontal") !=0 || Input.GetAxis("Vertical") !=0)
         {
        //If the player is fatigued
        if(isFatigued == true)
        {
                //Set the player to walking speed
                speed = walkSpeed;
                //Player is not running
                isRunning = false;
                //Player is walking
                isWalking = true;
                //Start increasing stamina
                stamina++;
        }
        //If the player is touching the ground and the user is either pressing left shift or right shift
        else if (chCtrl.isGrounded && Input.GetKey ("left shift") || Input.GetKey ("right shift") && isFatigued == false ) 
            {
                //Set the player to running speed
                speed = runSpeed;
                //Player is running
                isRunning = true;
                //Player is not walking
                isWalking = false;
                //Start reducting stamina
                stamina--;
            }
            else
            {
                //Set the player to walking speed
                speed = walkSpeed;
                //Player is not running
                isRunning = false;
                //Player is walking
                isWalking = true;
                //Start increasing stamina
                stamina++;
            }
        }
        else
        {
            //Player is not running
            isRunning = false;
            //Player is not walking
            isWalking = false;
            //Start increasing stamina
            stamina++;
        }
        //Set the players speed to either walkSpeed or runSpeed
        chMotor.movement.maxForwardSpeed = speed;
    }

    void AddAllButtons()
    {
        //Create an array that contains all game objects tagged with 'button'
        GameObject[] buttons = GameObject.FindGameObjectsWithTag("Button"); 
        //For each of the game objects in the array
        foreach(GameObject button in buttons)
        {
            //Add the transform of the button
            AddButton(button.transform);
        }
    }

    void AddButton(Transform button)
    {
        //Add the transform of the button into the targets list
        targets.Add(button);
    }

    void ButtonCheck(Transform button)
    {   
        Vector3 dir = (button.position - transform.position).normalized;
        float direction = Vector3.Dot(dir, transform.forward);
        Debug.Log(direction);

        if(Input.GetKeyDown(KeyCode.E))
        if(direction > 0.7F && Vector3.Distance(currentTarget.position, transform.position) < 2.0F)
        {   
            print("Button has been clicked");
        }
    }

    void ClosestTarget()
    {
        foreach (Transform button in targets) 
        { 
        Vector3 diff = (button.position - transform.position); 
        float curDistance = Vector3.Distance(transform.position, button.position );
        Debug.Log(curDistance);
        if (curDistance < distance ) 
        { 
          currentTarget = button;
          distance = curDistance;
        } 
        }
    }
}

As said, the problem is with the ClosestTargets function. It's finding the distance for every cube but it only ever selects the last element in the targets list.

http://puu.sh/4Sh9U.png

It still shows the distance from every cube in the console.

http://puu.sh/4Shbk.png

Upvotes: 1

Views: 7389

Answers (2)

MichaelTaylor3D
MichaelTaylor3D

Reputation: 1665

I use this extension method to find the closest gameObject (warning it uses linq):

public static GameObject ReturnClosestObject(this GameObject go, float radius, LayerMask layerMask)
{               
    Collider[] closeObjects = Physics.OverlapSphere(go.transform.position, radius, layerMask);

    closeObjects = closeObjects.OrderBy((collider) => Vector3.Distance(collider.gameObject.transform.position, go.transform.position)).ToArray();

    GameObject returnedObject = null;

    if(closeObjects.FirstOrDefault().Exist())
    {
        returnedObject = closeObjects.FirstOrDefault().gameObject;
    }

    return returnedObject;              
}

The downside to this method is that it will only detect objects that overlapwith the projected sphere and miss objects that are inside it completly. So I will often compliment it with this recursive function that projects a finit amount of sphere at various distances.

Note while they have similier names, the above is a extension method and below is a instance method. They work together hand and hand.

public GameObject FindClosestGameObject(int startingLookUpDistance, int maxLookUpDistance, int numberOfSteps)
{
    GameObject closestGameObject = gameObject.ReturnClosestObject(startingLookUpDistance, LayerMasks.gameObjects);      

    bool gameObjectNotFound = (closestGameObject.Equals(null));

    if(gameObjectNotFound)
    {
        if(startingLookUpDistance <= maxLookUpDistance)
        {
            float stepDistance = maxLookUpDistance / numberOfSteps;
            return FindClosestBuildingObject((int)(startingLookUpDistance + stepDistance), maxLookUpDistance, numberOfSteps);
        }           

        return null;
    }
    else
    {
        return closestGameObject;       
    }
}

This top method works by specifying your starting distance and your max distance, you then specify how many iterations of the function you want to perform. Based on the number of iterations it will cast a n spheres equal distances apart in between your start and stop bounds.

Upvotes: 1

Kay
Kay

Reputation: 13146

My guess: distance is class member and correctly initialised to Mathf.Infinity but it's never reset. So on the first call to ClosestTarget () Cube2 might be the one closest to player as Player.Update might called later. So distance contains a value too tiny (0?) for other objects to have a chance.

Upvotes: 1

Related Questions