Page 1 of 1
Select Closest Usable when Multiple are in Range
Posted: Sat May 18, 2024 3:42 pm
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.
Re: Select Closest Usable when Multiple are in Range
Posted: Sat May 18, 2024 4:06 pm
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.
Re: Select Closest Usable when Multiple are in Range
Posted: Thu Aug 01, 2024 9:10 am
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
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
Re: Select Closest Usable when Multiple are in Range
Posted: Thu Aug 01, 2024 9:21 am
by Tony Li
Thanks for sharing!