Return an object when finding closest position

I’ve got a list of constantly moving GameObjects that each need to determine the closest path to an object in another list of constantly moving GameObjects. When an object needs to select a new target I’m converting one of the GameObject lists into an array of Vector 3 positions and feeding it into MultiTargetPath. The problem is that MultiTargetPath sends me to a Vector3 coordinate when what I really need it to do is lock me onto the closest object.

I would love to figure out how to use MultiTargetPath.Construct to determine the closest object without starting the path, then assigning that GameObject to the Target for AIDestinationSetter. Because all of the objects are in motion a Vector3 coordinate isn’t useful seconds after I’ve received it—I need to be able to lock onto the closest object.

I’ve tried to pick apart the callbacks in MultiTargetPath.Construct and StartMultiPath to compare the endpoint of the shortest path (which I think is the first path in the list?) with the position of the object in the list and use that comparison to determine the GameObject to target but it feels clunky and prone to error—there’s got to be a better way.

In an ideal world, I would be able to give MultiTargetPath a list (or an array) of objects, have it determine which one is the closest, and return that object for me to use elsewhere (ie: AIDestinationSetter). I’d appreciate any guidance in how I could get there without altering any code in Seeker.cs :see_no_evil: or another approach for this functionality.

Hi

You can do something like this:

Vector3[] myListOfTargets = ...;
var path = MultiTargetPath.Construct(transform.position, myListOfTargets, null);
// Calculate the path using the AstarPath object directly instead of using a seeker
// to avoid sending the path to the movement scripts
AstarPath.StartPath(path);
// For simplicity in this example, don't calculate the path asynchronously
path.BlockUntilCalculated();
// Will be -1 if no target was found, otherwise the index in the myListOfTargets array that was the closest
var closestObjectIndex = path.chosenTarget;
1 Like

This is a much more efficient way to do what I was trying to do, thanks!

1 Like

At first glance, this seemed to be working fine but then in practice, I noticed something weird that I can’t figure out. Here’s my version of that code you posted:

    public void SeekNewOpening()
    {
        targetedOpening = _core.pathing.GetClosestOpening(_vacantOpponentOpenings);
        aiDestinationSetter.target = targetedOpening.transform;
    }


    public UnitOpening GetClosestOpening(List<UnitOpening> opponentOpenings)
    {
        Vector3[] endPoints = new Vector3[opponentOpenings.Count];

        for (int i = 0; i < endPoints.Length; i++)
        {
            endPoints[i] = opponentOpenings[i].transform.position;
        }

        var path = MultiTargetPath.Construct(transform.position, endPoints, null);
        AstarPath.StartPath(path);
        path.BlockUntilCalculated();
        var closestOpeningIndex = path.chosenTarget;
        return opponentOpenings[closestOpeningIndex];
        }

In this screenshot, all of the units are using this script but for some reason are all targeting the right opening when the left one is clearly closer to the two units on the left. I double-checked and both openings are being considered when running GetClosestOpening.

I’ve tried a few things but have no idea how to debug this so any guidance would be greatly appreciated!

Hi

Are you using a recast graph or a grid graph?

It’s using a recast graph.

Any thoughts? I’ve tried tweaking it and still can’t seem to figure it out.

Hey again @aron_granberg! I finally came back to this project and still can’t figure this out. With the code you gave me closestOpeningIndex is always 0 so it just picks whatever is first in the array. Is there a better way to go about this? Here’s my current flow for reference:

I store my target objects in a list
→ a call is made to determine the nearest object
→ I convert my list to an array and use MultiTargetPath to find the shortest path
→ somehow use that path to determine which object in the list is the closest based on pathing
→ assign that object to AIDestinationSetter so it paths to the object even as it moves

Hi

Recast graphs are typically very bad at estimating distances accurately. Since pathfinding is only done on triangle centers, what looks like a short path can be very long from the pathfinding system’s perspective.
You can read more about that at the bottom of this page: Documentation

I’d recommend that you try a grid graph instead, which will be much more predictable.
Your code looks correct from what I can see.

1 Like