Update/assistance in explaining documentation: IsPathPossible, Areas, GetNearest

  • A* version: [5.x]

Howdy all. The more I program, the more obvious it is that people who write documentation know exactly what they mean, and expect others to as well. Unfortunately, a lot of people are… me! Relatively intelligent but if they don’t know what they don’t know, it takes hours to figure out what they didn’t know. Today, I will explain a few things that helped my script.

I am using combined grid graph (zero) and a point graph (one). The grid is to create a basic “wall” collision function that blocks point connections, regardless of range. The point graph is where the entities create their path and travel.
My goblins are underground, with food above them on the other side of a wall, and alongside them, relatively far away, but actually reachable.

I had two objectives: find which is closer, and, find if I can even reach it.

First problem is, I was finding all nodes, both graph nodes and point graph nodes. My query of;
GraphNode node1 = AstarPath.active.data.pointGraph.GetNearest(this.transform.position, null, 2.1f).node;
This would always find a graph node near my population position, but it would find it in ALL graphs, not in the graph I wanted - which was only to see if the pop was near the point graph.

So I needed to change it to only find point graph nodes, not grid graph nodes. As far as I can tell, AStar doesn’t really care what is what, a node is a node, except when being used. So I needed to specify which graph to search instead. This is where I used an adjustment to the constraints;
NNConstraint.Walkable.graphMask = 1;
All this does it set “walkable” constraint to only check for targets on graph 1. In my setup, graph 1 is my pointgraph.
Now, we can find points ONLY on the pointgraph, pretty sweet!

GraphNode node1 = _pointGraph.GetNearest(this.transform.position, NNConstraint.Walkable).node;
GraphNode node2 = _pointGraph.GetNearest(_temptransform, NNConstraint.Walkable).node;

Remember how I said that it will only seek nodes? Def correct me if I am wrong, but when I run the getnearest, it always results in a graphnode. Well, I need a pointnode, and you found a pointnode, so now I need to made those two graphnodes in to pointnodes! Why the conversion? I don’t know, but if theres a better way please let me know!

PointNode n1 = node1 as PointNode; PointNode n2 = node2 as PointNode;

Now, refer to my above screenshot. My son, who has colourvision, assures me that the different lines are not the same colour. Which means the lower food and upper food, well, they just don’t connect to each other. So how is my hungry goblin supposed to know that she cant path up there?
First thought was “isPathPossible”, everyone’s go-to. Well your WRONG. Of course its possible, gridgraph allows it. But I don’t care what gridgraph thinks, I care what pointgraph thinks!
Thats where areas come in.
Now that we converted node1 and node2 in to pointnodes, they can check which coloured line they touch. Those coloured lines are “areas”. When you look at most gridgraph documentation it shows triangles of different colours. Well, same thing, but this is for points.
IsPathPossible, as far as I can tell, checks all graphs. Not helpful, since we need to know just pointgraph.

if (n1.Area == n2.Area)

We cast node1 in to n1, and check what area it is in. Green? Cool.
We case node2 in to n2, and check what area it is in. Red? Cool.
Well those arent in the same area, therefore, not connected on the same gridgraph! No reason to run the rest of the check, right?

This all brings us to checking if indeed the food directly above the goblin, if it WAS on the same pointgraph area, is closer in path distance than the food on the ground with a longer linear distance but much shorter path distance.

 if (n1 != null && n2 != null) if (n1.Area == n2.Area)
 {
     Vector3 v1 = (Vector3)node1.position; Vector2 v2 = (Vector3)node2.position; Path p = null; _doneTPComp = false;
     p = _seeker.StartPath(v1, v2, OnPathComplete, 1); while (!_doneTPComp) yield return null;
     if (p != null) { _distance = p.GetTotalLength(); Debug.Log("done distance " + _distance); }
     else _distance = 99999999f;
     if (_distance < _nearestDistance)
     {
         _target = _transformsWithNeeded[i];
         _targetName = _transformsWithNeeded[i].name;
         _nearestDistance = _distance;
     }
 }

If you look at the above code, you can see specifically;

_seeker.StartPath(v1, v2, OnPathComplete, 1)

The seeker is attached to my goblin. I didn’t use ABConstruct because it cannot seem to take an overload for one specific graph. It will let you overload for tags, but my pointgraph isnt using tags. So, I am instead using the seeker.
It is important to note, and possibly helpful to others: my seeker is used in my movement script as well as this path check. They don’t interfere with each other, because they have different “onpathcomplete” functions, one in this script, and one in my mover script. So yes, you can use the same mover, for more than one thing!
In the seeker above, you can see it is only going to use graph 1 - thats the one after “onpathcomplete”. Graph 1 is my pointgraph. Therefore this will craft a path only on graph 1.
My complete tells me if its done the temporary path completion. I feel like there could be a call to check if the seeker is done instead, but I was getting spammed with incomplete paths due to my bad code. So I added this instead, and now it waits until the path is done, and only gives me a viable path.

To find out which is closest for path distance, you have “getTotalLength” from the returned path. As far as I can tell, all this does is check how many nodes/waypoints are in the path. so to go all the way to floor food, its 17 nodes. To go to the above food, it has to go all the way to the end of the cliff, then up the cliff, then back to above me - 41 nodes!
Therefore, even though the above food is closer linearly, it is farther pathing.

Aron is a fucking champion, and he literally put in pointgraph creation functions because I asked him to. I am trying to get my game to run on a potato here - if it has to search all 321600 grid graph nodes, it would add up very quickly when having hundreds of goblins. But pathing within only a few thousand pointnodes? Instant.
For anyone who got this far, please remember: you can flip unity assets that look beautiful, but your game will probably lag to heck (and not back because of the frame drops). Optimize for the system you want to run on, build it for what you want to run on. You want Oxygen Not Included to run on a mobile phone? Then you need to build for it now, not later.

I hope these examples help.

void OnPathComplete(Path p) { if (p.error) Debug.Log("No valid path"); _doneTPComp = true; Debug.Log("Finished path"); }
for (int i = 0; i < _transformsWithNeeded.Count; i++)
{
    GraphNode node1 = _pointGraph.GetNearest(this.transform.position, NNConstraint.Walkable).node;
    Vector3 _temptransform = new Vector3(_transformsWithNeeded[i].transform.position.x + 0.5f, _transformsWithNeeded[i].transform.position.y + 1.5f, _transformsWithNeeded[i].transform.position.z);
    GraphNode node2 = _pointGraph.GetNearest(_temptransform, NNConstraint.Walkable).node;
    PointNode n1 = node1 as PointNode; PointNode n2 = node2 as PointNode;
    Debug.Log("n1 " + n1.Area + " n2 " + n2.Area);
    while (!_doneTPComp) yield return null;
    if (n1 != null && n2 != null) if (n1.Area == n2.Area)
    {
        Vector3 v1 = (Vector3)node1.position; Vector2 v2 = (Vector3)node2.position; Path p = null; _doneTPComp = false;
        p = _seeker.StartPath(v1, v2, OnPathComplete, 1); while (!_doneTPComp) yield return null;
        if (p != null) { _distance = p.GetTotalLength(); Debug.Log("done distance " + _distance); }
        else _distance = 99999999f;
        if (_distance < _nearestDistance)
        {
            _target = _transformsWithNeeded[i];
            _targetName = _transformsWithNeeded[i].name;
            _nearestDistance = _distance;
        }
    }
}

Lets go make some fun games.

1 Like

Very fun read :slight_smile: Thanks for sharing!

I’m actually most intrigued by this- even looking at the documentation it says data.pointGraph is a “Shortcut to the first PointGraph.” but it’s checking both graphs?? Very interesting, maybe even worth looking into?

As I state frequently, I am dumb and bad. If you have a better solution, absolutely please write the code out here.
Mine is by no stretch the “right” solution. Its the solution I found that works based on my inability to understand the documentation.

1 Like

Yeah I just wanna look into it and see if it’s an issue :smiley: Might help others as well. I gave it a little test using the following little snippet:

    void DoShortcutTest(Vector3 targetPos){
        Debug.Log(AstarPath.active.data.recastGraph.GetNearest(targetPos).position);
        Debug.Log(AstarPath.active.data.pointGraph.GetNearest(targetPos).position);
    }

And it seemed to work for me- neither position was identical or even near each other so I’m pretty sure they aren’t referencing the same graph. Mine was with a Recast and Point graph, maybe I should try it with Grid Graph and Point Graph? Let me know if you see any error in my methodology that you might’ve gleamed from already tackling this :smiley:

(strongly disagree :slight_smile:)

1 Like

This is exactly why I posted - there was a better solution than the one I posted, because I don’t know how to read the documentation.

If you have any control over the documentation, can you please update it with examples? It would really help people like me who are learning. Could save a whole lot of time. Aron posted the examples I made for the pointgraph node creation/deletion code I created to help others, thats what Im after!

“Pranks are seen as ways to lighten spirits and to keep gnomes humble, not as ways for pranksters to triumph over those they trick.”
Saying I don’t know everything is about staying humble. :slight_smile:

Yeah for sure I can tag him in this! I don’t have any access to documentation editing but I’ll definitely let @aron_granberg know :smiley:

I guess what I’m confused about is why did my code:

Debug.Log(AstarPath.active.data.pointGraph.GetNearest(targetPos).position);

Get the right position but your code:

GraphNode node1 = AstarPath.active.data.pointGraph.GetNearest(this.transform.position, null, 2.1f).node

didn’t work? :thinking: I know these two codes aren’t analogous for many reasons, but I wouldn’t think getting the node rather than just debugging the name would yield much different results…maybe? Testing time :slight_smile:

Yeah I tried that line you didn’t have success with almost verbatim and just logged the position and got expected results:

   GraphNode pointNode = AstarPath.active.data.pointGraph.GetNearest(target.transform.position, null, Mathf.Infinity).node;
   Debug.Log(pointNode.position);
   
   GraphNode recastNode = AstarPath.active.data.recastGraph.GetNearest(target.transform.position, null, Mathf.Infinity).node;
   Debug.Log(recastNode.position);

By any chance are you using a version before 5.1.5? I did find this in the patch notes, and I’m wondering if it’s related:

Fixed PointGraph.GetNearest could throw an exception in some situations if there was no acceptable node nearby.

Using 5.2.4. Theres probably some other error happening somewhere that I wasn’t aware of and somehow magically fixed while hacking away at my hack code. Maybe what I linked above wasn’t actually the oldest version of the code, and was actually a mid-way fix. Sorry, it was a LONG day/night before I posted in the morning.

1 Like

You’re totally fine! :smiley: No apologies required. At any rate, I let Aron know about this post so maybe we can see some notated documentation :+1:

I hate wasting peoples time, thats why I apologize. The entire point of my post was to help NOT waste peoples time.
Hope you can edit it in to something more usable for the documentation examples.

My time is wholly not wasted, promise that :slight_smile: Every post is a learning opportunity and every well thought out post like this is even more so. But yeah we’ll see what we can gleam here and how it can be applied forward :smiley:

1 Like