Select Closest Usable when Multiple are in Range

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
nok nok
Posts: 1
Joined: Sat May 18, 2024 3:33 pm

Select Closest Usable when Multiple are in Range

Post by nok nok »

Hi! I was wondering if there was a quick way or workaround to ensure that, when two usables are in range simultaneously, the closest one is prioritized, rather than defaulting to the first one?

The issue right now is if the usable range for, say, an NPC which can be conversed with at a longer distance completely encapsulates the usable range for an item which is usable at a short distance, the item is never usable because the NPC will always be selected first.

Or, if two usable ranges intersect, the player will use the object they approached first, and will have to exit it's use range before the second is accessible.
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: Select Closest Usable when Multiple are in Range

Post by Tony Li »

Hi,

Are you using the Selector component, Proximity Selector, or something else?(The Selector/Proximity Selector aren't mandatory. They're just provided as a convenience if you want to use them.) Assuming you're using a Proximity Selector, you can make a subclass of ProximitySelector and override the UseCurrentSelection() method to use the closest Usable among the elements in the usablesInRange list.
Roel365
Posts: 1
Joined: Thu Aug 01, 2024 8:34 am

Re: Select Closest Usable when Multiple are in Range

Post by Roel365 »

Hi Tony,

Thanks for the suggestion! I also had the same question, but I also wanted the usable-names to change when another NPC has moved closer to the player and not only when you start ('use') a dialogue, so I used a slightly different approach by overriding the CheckTriggerEnter (which doesn't select the Usable anymore) and the Update (which now finds+selects the closest one, and fires some optional events), this seems to work :D unless I overlooked something ;)

The event ClosestUsableChanged<oldUsable, newUsable> could optionally be used to disable a highlight on the old Usable and set a highlight on the new (for example).

ClosestProximitySelector.cs

Code: Select all

using PixelCrushers.DialogueSystem;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// Autoselect the closest Usable in range
/// </summary>
public class ClosestProximitySelector : ProximitySelector
{
    public UnityEvent<Usable, Usable> ClosestUsableChanged;
    public UnityEvent<Usable> ClosestUsableDetected;
    public UnityEvent<Usable> ClosestUsableLost;
    
    [SerializeField] Usable closest;
    [SerializeField] bool debugging;

    /// <summary>
    /// Override Update()
    /// </summary>
    protected override void Update()
    {
        // Exit if disabled or paused:
        if (!enabled || (Time.timeScale <= 0)) return;

        // Call the original ProximitySelector.Update()
        base.Update();

        // Find the closest Usable
        SetClosestUsableAsCurrent();
    }

    /// <summary>
    /// Override CheckTriggerEnter()
    /// Do not set the current usable here, only add it to the usablesInRange list
    /// </summary>
    /// <param name="other"></param>
    protected override void CheckTriggerEnter(GameObject other)
    {
        if (!enabled) return;
        Usable usable = other.GetComponent<Usable>();
        if (usable != null && usable.enabled)
        {
            //SetCurrentUsable(usable);
            if (!usablesInRange.Contains(usable)) usablesInRange.Add(usable);
            //OnSelectedUsableObject(usable);
            //toldListenersHaveUsable = true;
        }
    }

    /// <summary>
    /// Set the closest Usable as the current one
    /// </summary>
    void SetClosestUsableAsCurrent()
    {
        bool isPreviousClosestNull = (closest == null);

        Usable newClosest = null;

        // Only search if usablesInRange is not empty
        if (usablesInRange.Count > 0)
        {
            newClosest = ClosestUsable(this.transform.position, usablesInRange);
        }

        // First, send out our custom events, while we still have the 'previous' + 'new' Usables
        if (isPreviousClosestNull && newClosest != null)
        {
            if(debugging) Debug.Log("ClosestUsableDetected " + (newClosest == null ? "NULL" : newClosest.GetName()));

            ClosestUsableDetected.Invoke(newClosest);
        }
        else if (!isPreviousClosestNull && newClosest == null)
        {
            if (debugging) Debug.Log("ClosestUsableLost " + (newClosest == null ? "NULL" : newClosest.GetName()));

            ClosestUsableLost.Invoke(closest);
        }
        if (newClosest != closest)
        {
            if (debugging) Debug.Log("ClosestUsableChanged from " + (closest == null ? "NULL" : closest.GetName()) + " to " + (newClosest == null ? "NULL" : newClosest.GetName()));

            ClosestUsableChanged.Invoke(closest, newClosest);
        }

        // Now set the CurrentUsable to the closest
        closest = newClosest;
        base.SetCurrentUsable(closest);
        base.OnSelectedUsableObject(closest);
        base.toldListenersHaveUsable = true;
    }

    /// <summary>
    /// Find the closest Usable from a list of Usables
    /// </summary>
    /// <param name="source"></param>
    /// <param name="usables"></param>
    /// <returns></returns>
    private Usable ClosestUsable(Vector3 source, List<Usable> usables)
    {
        Usable closest = null;
        float closestDistance = Mathf.Infinity;

        for (var i = 0; i < usables.Count; i++)
        {
            var offset = source - usables[i].transform.position;
            float distance = offset.sqrMagnitude;

            if (distance < closestDistance)
            {
                closestDistance = distance;
                closest = usables[i];
            }
        }
        return closest;
    }
}
Best regards,
Roel
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: Select Closest Usable when Multiple are in Range

Post by Tony Li »

Thanks for sharing!
Post Reply