Arcana Affinity
Arcana Affinity

Reputation: 307

Finding gameObject via direction of input

Example

I have a list of gameObjects added to a List within the area as shown. What I need is a directional input to choose a target from the origin point. I have got it work to get this origin point.

My first attempt of this was to get it via rayCast, but by doing that there were times directional inputs need to directly hit target object by the ray. This is no problem if input was done like case #1.

However, what I need it to really work is when input direction was as if case #2 or #3 the input will still get a target. My second attempt was to do this with sphereCast, but it still needed a target in sphere proximity and multiple targets hit by sphereCast still needed to result in only one and more accurate target selection by input.

Since I have all the transform.position of all the possible targets as well as the origin point I wondered there would be a more elegant way of resolving this via comparing vector3's of these coordinates(origin and targets in the general direction).

Here's my latest approach:

//
// m_targetList is the list containing all GameObjects as GameObjects in other script m_collector. All m_collector does is this.
//

using System.Collections.Generic;
using UnityEngine;

public class TargetSwitcher : MonoBehaviour
{
    private TargetCollector m_collector;
    private Transform m_origin;

    public bool m_targetChanged = false;

    public GameObject m_target;

    public LayerMask m_targetMask;

    private Dictionary<Vector3, float> m_targetInfo = new Dictionary<Vector3, float>();

    private void Awake()
    {
        m_collector = GetComponent<TargetCollector>();
        m_origin = GameObject.Find("TargetOrigin").transform;
        m_tracker = GameObject.Find("TargetTracker").transform;
        m_bound = GetComponent<BoxCollider>();
    }

    public void UpdateBearing(GameObject origin)
    {
        m_origin = origin.transform;

        foreach (GameObject target in m_collector.m_targetList)
        {
            Vector3 dir = (target.transform.position - origin.transform.position).normalized
            float dist = Vector3.Distance(origin.transform.position, target.transform.position);

            m_targetInfo.Add(dir, dist);
        }
    }

    public void SwitchTarget()
    {
        if (!m_targetChanged)
        {
            Vector2 dir = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).normalized;

            // Find closest direction value from Dictionary to dir of here
            // Compare distance from origin if multiple targets and choose the nearest
        }
    }

    public void ReturnToIdle()
    {
        m_origin.position = m_target.transform.position;
        m_targetChanged = false;
        m_targetInfo.Clear();
    }

    public struct TargetInfo
    {
        public Vector3 bearing;
        public float distance;

        public TargetInfo(Vector3 bearing, float distance)
        {
            this.bearing = bearing;
            this.distance = distance;
        }
    }
}

Generally, I'm trying to compare the normalized vector of directional input to the normalized vector from the origin to each target before SwitchTarget(). The input method here is Gamepad axis x and y as Horizontal and Vertical.

Reposting this question since a provided answer was very far from the question and marked as duplicate(Given answer was about finding gameObject by distance only, this question is about direction and distance portion is to compare second-handedly when multiple items were found in the direction)

Edit

After some trials with dot product now I'm sure this is much likely where I want to head. There are much inconsistency I need to get on with, though. Here's my most recent attempt:

private void Update()
{
    UpdateBearing();

    Vector3 input = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0);

    if (input != Vector3.zero)
    {
        SwitchTarget();
    }
}

public void UpdateBearing(GameObject origin)
{
    m_origin.position = origin.transform.position;

    foreach (GameObject target in m_collector.m_targetList)
    {
        Vector3 dir = (target.transform.position - origin.transform.position).normalized;
        if (!m_targetInfo.ContainsKey(target))
        {
            m_targetInfo.Add(target, dir);
        }
    }
}

public void SwitchTarget()
{
    GameObject oldTarget = m_collector.m_target;

    if (!m_targetChanged)
    {
        Vector3 dir = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0).normalized;
        Debug.DrawRay(m_origin.position, dir * 100, Color.yellow, 0.5f);

        foreach (KeyValuePair<GameObject, Vector3> possibleTarget in m_targetInfo)
        {
            float dot = Vector3.Dot(dir, possibleTarget.Value);

            if (dot > 0.5f) // Compare DP difference of added dot and input dot
            {
                GameObject newTarget = possibleTarget.Key;

                if (oldTarget != newTarget)
                {
                    Debug.Log(possibleTarget.Value + " // " + dot);

                    m_target = newTarget;
                    m_collector.m_target = newTarget;
                    m_targetChanged = true;
                }
            }
        }
    }
}

With this, I'm kind of getting gameObject selection without raycasting and missing any targets. However, I'm sure I need better case comparison than if(dot > 0.5f). Also, my rough assumption is that if I don't update the value of the dictionary m_targetInfo for each Key I'd have another inconsistency if those targets ever move. Anyways, I'm still confused how properly utilize this to achieve my end goal.

Upvotes: 3

Views: 978

Answers (1)

Fiffe
Fiffe

Reputation: 1256

Since you have all the desired game objects in the area you can create a for loop and check the angle between your look direction and their position, if it is lower than some value (you can make it super low so it's precise or a little bit higher to allow for some margin of error) put it in a list of gameobjects and if there's more than one object there get the closest one.

The code for getting closest object in angle would look something like this:

GameObject CheckObjects()
{
    List<GameObject> InAngle = new List<GameObject>();

    for(int i = 0; i < YourObjectsList.Count; i++)
    {
        GameObject tested = YourObjectsList[i];

        Vector3 dir = tested.transform.position - origin.transform.forward;

        // I'm assuming here that youre rotating your origin with the 
        directional input, not then instead of origin.transform.forward 
        place there your directional vector3

        float angle = Vector3.Angle(dir, tested.transform.position);

        if(angle <= desiredAngle)
        {
            InAngle.Add(tested);
        }
    }

    GameObject closest = null;

    for(int j = 0; j < InAngle.Count; i++)
    {
        GameObject tested = InAngle[i];

        Vector3 dir1 = tested.transform.position - origin.transform.position;
        Vector3 dir2 = closest.transform.position - origin.transform.position;

        if(!closest)
        {
            closest = tested;
        }
        else
        {
            if(dir2.sqrMagnitude > dir1.sqrMagnitude)
            {
                closest = tested;
            }
        }
    }

    return closest;
}

Upvotes: 3

Related Questions