- 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.