NodePath creation

(Note up front - it needs to be node point map due to gravity based physics. I am also using collision physics map which works absolutely perfectly and I can update just the area of instantiation. 10/10).

I apologize, I appear to be absolutely stupid. I’m mildly upsetty spaghetti at the moment as well, because I bought Pro hoping it would resolve my issue and it very much did not. Not that I have any issue supporting the dev, to be clear! It just felt like a bit of a bait and switch. So now I really need assistance.

When I create an object on a grid, I create an array of Vector3 around it. So for example, I create a floor square. I need to create 3 nodes - x-1/y+1, x/y+1, x+1/y+1, put them in to _createdPosArray.
So I have my array, thats easy.

Originally, I was moving gameobjects in the parent of the transform for node in to the positions. Then, I need to scan JUST THE NEW NODES, to add them to the pathing area.
It said on the pro upgrade that you can indeed scan just an area to update the nodes for a point graph, and not have to iterate the entire world. Which is great, because my world is about 36,000 possible nodes, recalculating just the 200 I have already causes a noticable few frames using the Scan function.
Well as it turns out, diving further, if you are using Gameobjects to act as nodes, you can’t scan just the bounds that contains those objects and add them to the point mesh? You have to do it through some magical non-gameobject method?

So please help me out here, because I cannot make sense of the documentation;
1 - create a new node at a coordinate
2 - connect it to cardinal direction nodes

Please explain like I am a drunken Canadian. Because currently I am.

public void UpdateTheNodes() {
foreach (vector3Int pos in _createdPosArray) {
???----create node at pos!!!----
Vector3int _posUp = new Vector3Int(pos.x, pos.y +1, posz)
???----connect node from pos to _posUp----
}}

Again, I know I am a little upsetty right now, but please dont tell me to just look at the documentation, I don’t understand it and am clearly stupid and it won’t help.

Hopefully thank you all in advance, and may you all spell Colour properly despite unity demanding the incorrect spelling.

Followup on this a few days later, as I am still desperate for assistance;

I have two maps: a collision based grid map (works perfectly, can collide, can update just the area where new collision objects are placed. 10/10, no problem).
The second map is a point graph, and that’s where I have the problem. My current setup is using gameobjects in the pathmapused root to create a point to point map. The scan takes half a second, which is totally fine during compile, but extremely noticable during runtime if I need to update the map.

This is because the Point map has currently just under 200 objects in it. From what I have read, if you are using objects, AStar must scan the entire object list to recalculate all connecting objects.
What I need to do, and where my understanding is lacking, is create nodes without the use of gameobjects, and just make them exist. Somehow. And then scan those nodes after creating them, and this will add to the map.

On start of game/when all the tiles are generated;
AstarPath.active.Scan();
_pointGraph = AstarPath.active.data.pointGraph;

When I update the collision map;
(in building script) astar.ScanUpdate(this.gameObject.GetComponent().bounds);
(in astar)
public void ScanUpdate(Bounds _updateArea) { AstarPath.active.UpdateGraphs(_updateArea); }

What I need is the code snippet to tell me how to make a node, then scan the area around it. I’m not currently using weight values, I just need blunt connection. This is what I currently have, but it doesn’t appear to actually create the node visibly, let alone actually connect. My debug CLAIMS it is creating the nodes, but the map never updates.

public void CreateNode(Vector3Int vec3toAdd)
{
Int3 _nodePos = new Int3(vec3toAdd.x, vec3toAdd.y, 0);
AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => {
var graph = AstarPath.active.data.pointGraph;
var node1 = _pointGraph.AddNode(_nodePos);
Debug.Log("In theory added a node at " + _nodePos);
}));
AstarPath.active.FlushWorkItems();
}

Can someone smarter than me please give me the code snippets I need and/or at least explain the fundamental concepts I may be missing so I can pursue looking it up myself in a more educated manner? Can gameobject based point maps not get along with magical created node maps?
Can I not scan the area or do I have to manually make connections to each node created?
If it is not possible to do an area scan, I can live with that and make a manual check to all four directions, I just need to know that I HAVE to given that it is quite the hassle and if it isnt the correct way I would prefer to do it via scan.
Why are my nodes not visibly creating via the code above?

Thank you for your time, I can’t move forward on my project until I figure out the pathing.

Hi

Something like this should work. Though I haven’t tested the code myself:

public void CreateNode(Vector3Int vec3toAdd) {
	Vector3 nodePos = new Vector3(vec3toAdd.x, vec3toAdd.y, 0);
	AstarPath.active.AddWorkItem(() => {
		var graph = AstarPath.active.data.pointGraph;
		var node = graph.AddNode(nodePos);
		// Update a small region around the node.
		// All connections that intersect this bounding box will be recalculated.
		// The size doesn't really matter just as long as it's larger than zero.
		AstarPath.active.UpdateGraphs(new Bounds(nodePos, Vector3.one * 0.1f));
	});
}

Note that you were converting your Vector3Int to an Int3 directly before. I think this was incorrect, because I suspect your Vector3Int was in world space using meters as the unit, while Int3 uses millimeters.

so for clarity: I can use Vector3 (not int) to create graph points? All the documentation uses Int3 (a thing you made up, I think?) that implied it must be an integer. If I can use straight up Vector3 that is incredibly helpful.

I absolutely appreciate you coming back with a response during summer vacation. I’m sure you are aware but if not: you quite literally have saved countless projects in Unity, and the Brackey’s tutorial is undoubtedly responsible for a lot of game developers.

I will do edit that code when I get home and try it out. Its so simple, and I just didn’t know how to do it. Thank you.

1 Like

The nodes store their position internally as an Int3 to avoid floating point calculations in the core. But it can be cast back and forth (it will get rounded to millimeter precision):

Vector3 a = ...;
Int3 b = (Int3)a;
a = (Vector3)b;

Looks like I made a typo earlier. You need to cast it to an Int3 to pass it to the AddNode method:

graph.AddNode((Int3)nodePos);

Sorry to come back: I tried to implement, and I am getting a graph update issue.
The coordinates of the nodes I am making for both int3 and vec3 match the real world space I want them to exist in (matching against world objects just to confirm. Node count goes up on the point graph as expected - starts at 196, script executes, number goes up by 3 as it should.
(Edit: the thick path line is where the AI path is trying to go to. The following was a red herring. Leaving it in so people can learn from my mistakes --“What is odd is that in the screenshot, you can see a thicker path line running across the gameobject nodes that pre-exist in the scene. They are not there until the scan claims to try to execute. Additionally, no matter how high up I make the new nodes, the path line only shows in the gameobject nodes area.”–)

Execution script;

public void CreateNode(Vector3 vec3toAdd)
{
    AstarPath.active.AddWorkItem(() => {
        var graph = AstarPath.active.data.pointGraph;
        var node = graph.AddNode((Int3)vec3toAdd);
        Debug.Log("added node int3 " + (Int3)vec3toAdd + " which is Vec3 " + vec3toAdd);
        // Update a small region around the node.
        // All connections that intersect this bounding box will be recalculated.
        // The size doesn't really matter just as long as it's larger than zero.
        AstarPath.active.UpdateGraphs(new Bounds(vec3toAdd, Vector3.one * 1.1f));
    });
}

Error while updating graphs;

> System.Exception: Error while updating graphs. ---> System.ArgumentNullException: Value cannot be null.
> Parameter name: collection
>   at System.Collections.Generic.List`1[T].InsertRange (System.Int32 index, System.Collections.Generic.IEnumerable`1[T] collection) [0x00003] in <ecf276dcb8654fa5bed2c5ea1a4ad870>:0 
>   at System.Collections.Generic.List`1[T].AddRange (System.Collections.Generic.IEnumerable`1[T] collection) [0x00000] in <ecf276dcb8654fa5bed2c5ea1a4ad870>:0 
>   at Pathfinding.PointGraph+PointGraphUpdatePromise.Apply (Pathfinding.IGraphUpdateContext ctx) [0x00136] in .\Packages\com.arongranberg.astar\Graphs\PointGraph.cs:666 
>   at Pathfinding.GraphUpdateProcessor.ProcessGraphUpdatePromises (System.Collections.Generic.List`1[T] promises, Pathfinding.IGraphUpdateContext context, System.Boolean force) [0x00189] in .\Packages\com.arongranberg.astar\Core\Pathfinding\GraphUpdateProcessor.cs:263 
>    --- End of inner exception stack trace ---
> UnityEngine.Debug:LogError (object)
> Pathfinding.GraphUpdateProcessor:ProcessGraphUpdatePromises (System.Collections.Generic.List`1<System.ValueTuple`2<Pathfinding.IGraphUpdatePromise, System.Collections.Generic.IEnumerator`1<Unity.Jobs.JobHandle>>>,Pathfinding.IGraphUpdateContext,bool) (at ./Packages/com.arongranberg.astar/Core/Pathfinding/GraphUpdateProcessor.cs:265)
> Pathfinding.GraphUpdateProcessor:ProcessGraphUpdates (Pathfinding.IWorkItemContext,bool) (at ./Packages/com.arongranberg.astar/Core/Pathfinding/GraphUpdateProcessor.cs:184)
> Pathfinding.WorkItemProcessor:ProcessWorkItems (bool,bool) (at ./Packages/com.arongranberg.astar/Core/Misc/WorkItemProcessor.cs:333)
> Pathfinding.WorkItemProcessor:ProcessWorkItemsForUpdate (bool) (at ./Packages/com.arongranberg.astar/Core/Misc/WorkItemProcessor.cs:416)
> AstarPath:PerformBlockingActions (bool) (at ./Packages/com.arongranberg.astar/Core/AstarPath.cs:895)
> AstarPath:Update () (at ./Packages/com.arongranberg.astar/Core/AstarPath.cs:878)

I dont think I actually hit reply on the last one, just post, so hitting reply so I can ping you!

Hi

That exception is actually a bug in the latest version. A fix will be included in the next update.

Thank ya kindly! Please let me/us know when its compiled.
If you are on vacation still though, please just advise of a general ETA so I’m not biting my nails :slight_smile:

You can check the #releases channel in the discord for notifications about updates.

1 Like

Got an email saying the thing is available to update, so thats cool! (bought via unity store).
Updated this morning, unfortunately it appears it is still not populating the nodes (at least not in any visible manner). The nodes are being added - somewhere. The node total goes from 196 to 199, as expected. It says that it is creating them where it should (the mid point of the y+1 of the object). You can confirm the placement values from the object in one screenshot, which also works perfectly for the local area of the collision map which does update properly.

Code as follows, screenshot I was using value 1*1.1f to test so extends was 0.55, tried 0,1f through 500.1f

public void CreateNode(Vector3 vec3toAdd)
{
    AstarPath.active.AddWorkItem(() => {
        var graph = AstarPath.active.data.pointGraph;
        var node = graph.AddNode((Int3)vec3toAdd);
        Debug.Log("added node int3 " + (Int3)vec3toAdd + " which is Vec3 " + vec3toAdd);
        // Update a small region around the node.
        // All connections that intersect this bounding box will be recalculated.
        // The size doesn't really matter just as long as it's larger than zero.
        Bounds _updateZone = new Bounds(vec3toAdd, Vector3.one * 0.1f);
        AstarPath.active.UpdateGraphs(_updateZone);
    });
}

Please let me know if I can get you more info. (Edit, forgot screenshots)


Respectfully requesting an update on this?

Nodes added manually currently aren’t visualized with a box around them like nodes created from Game Objects. However, if you connect them to other nodes, the connections should show up.

You can see in the supplied pictures that it says it is creating them there, but it isnt connecting them. I am fairly colourblind but I am relatively sure I can’t see the lines from around the new square connecting?

The pathing checker will not accept that they are able to move there either - poor Goblinna is stuck!

Edit: adding pictures


I’m going to be on vacation for a few weeks, but if you might be able to look at this before my return it would be greatly appreciated. I can send you the build I am using if it would be helpful.

Hi

I have added a unit test for this, and it seems to work just fine.
One change I am making for the next update is to always draw the created nodes in the graph, even when the nodes are created from a script.

Here’s the unit test, maybe you can find some difference to what you are doing?

var graph = AstarPath.active.data.AddGraph<PointGraph>();
graph.maxDistance = 11;
graph.optimizeForSparseGraph = true;
AstarPath.active.AddWorkItem(() => {
	graph.AddNode((Int3)new Vector3(0, 0, 0));
	graph.AddNode((Int3)new Vector3(10, 0, 0));
	graph.AddNode((Int3)new Vector3(20, 0, 0));
	graph.AddNode((Int3)new Vector3(20, 10, 0));
});
AstarPath.active.FlushWorkItems();
var n1 = graph.nodes[0];
var n2 = graph.nodes[1];
var n3 = graph.nodes[2];
var n4 = graph.nodes[3];
// Recalculate all connections
AstarPath.active.UpdateGraphs(new Bounds(Vector3.zero, Vector3.one*100));
AstarPath.active.FlushGraphUpdates();
Assert(!n1.Destroyed);

Assert(graph.GetNearest(new Vector3(0, 0, 0)).node == n1);
Assert(n1.ContainsOutgoingConnection(n2));
Assert(n2.ContainsOutgoingConnection(n1));
Assert(n2.ContainsOutgoingConnection(n3));
Assert(n3.ContainsOutgoingConnection(n4));
Assert(!n4.ContainsOutgoingConnection(n2));

var path = ABPath.Construct(new Vector3(0, 0, 0), new Vector3(20, 10, 0));
StartAndWaitForPath(path);

AssertPath(path, true);
Assert(path.vectorPath.Count == 4);
AssertEqv(path.vectorPath[0], new Vector3(0, 0, 0));
Assert(path.path[0] == n1);
Assert(path.path[1] == n2);
Assert(path.path[2] == n3);
Assert(path.path[3] == n4);

Thank you for getting back to me. This is substantially longer than the code snippet you gave me a few weeks ago. I cannot test it though as I do not know what “assert” is in this context.
It is also advising that StartAndWaitForPath(path) does not exist, nor does AssertEqv.

Is there a specific place I am supposed to be putting the above? The previous code snippet was put in a standalone StartScan script.
I apologize if these answers are obvious or self-evident, please assume I am stupid (I have only been coding for 8 months).

Old script in its entirety.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
using Unity.VisualScripting;

public class StartScan : MonoBehaviour
{
    Bounds _colliderArea;
    private bool _waiting;
    PointGraph _pointGraph;
    void Start()
    {
        AstarPath.active.Scan();
        _pointGraph = AstarPath.active.data.pointGraph;
    }

    public void ScanUpdate(Bounds _updateArea) 
    {
        StartCoroutine(ScanMe(_updateArea)); 
    }
    public IEnumerator ScanMe(Bounds _updateArea) 
    {
        if (!_waiting)
        {
            _waiting = true;
            yield return new WaitForSeconds(0.25f);
            AstarPath.active.UpdateGraphs(_updateArea);
            _waiting = false;
        }
    }
    public void CreateNode(Vector3 vec3toAdd)
    {
        AstarPath.active.AddWorkItem(() =>
        {
            var graph = AstarPath.active.data.pointGraph;
            var node = graph.AddNode((Int3)vec3toAdd);
            Debug.Log("added node int3 " + (Int3)vec3toAdd + " which is Vec3 " + vec3toAdd);
            // Update a small region around the node.
            // All connections that intersect this bounding box will be recalculated.
            // The size doesn't really matter just as long as it's larger than zero.
            Bounds _updateZone = new Bounds(vec3toAdd, Vector3.one * 1.1f);
            AstarPath.active.UpdateGraphs(_updateZone);
        });
    }
}

The reference to the node list - I already created the vector3 positions to list previously;

astar = GameObject.Find("AStar").GetComponent<StartScan>();
foreach (Vector3Int vec3 in _nodeList)
{
    Vector3 _vec3Non = new Vector3(vec3.x + 0.5f, vec3.y + 1.5f, vec3.z);
    astar.CreateNode(_vec3Non);
}

This was just the raw code from my unit test. Assert is just a standard method in unit tests which check that a particular condition is true. AssertEqv checks that two values are the same. You can remove all asserts.

I’m going to wait for the update that makes the newly instanced nodes visible, so I can troubleshoot better. I trust you that it works, I just cant see it for myself or figure out how to use it.

In the meantime, how would one DELETE a node that was created via the code created node method above? Something like get all nodes within bounds around the vector3, and delete them, and delete the paths.

EDIT : While trying to figure this out more, I encountered the following;
Create 2 tiles, which makes 6 nodes (that I cant see and dont seem to connect/path)
I can see the number go up on the Point graph Nodes list and Walkable list.
If I then hit “scan” manually, the nodes and walkable nodes goes back down to my original total - is scan only finding things in my establish root folder? Is this all happening because I have some objects in the root folder, and the ones I am attempting to create via code are not objects?
If this is the case, can I merge these two graphs or do I have to either remove all objects (go tag only) or make it all objects?

Scanning a graph will make it be recalculated completely scratch. It will delete all nodes (including manually added ones) and re-create it based on the inspector settings.