Remove or Disable GridGraph Node

Do you think you could post the complete code that you are using?

Ok, here is the entire map building cs file.

[details=Summary]using MathUnity;
using Pathfinding;
using System.Collections;
using UnityEngine;

public class Map : MonoBehaviour
{
/* Cached components */
[HideInInspector] public AstarPath activePath;
Find find;

// Map building variables
public bool built = false;
[SerializeField] private bool buildRock = true;

[SerializeField] private int levels = 3;
[SerializeField] private int rows = 3;
[SerializeField] private int columns = 3;
[SerializeField] private int border = 5;
[SerializeField] private int deadends = 4;
[SerializeField] private int pathfindingSubsections = 3;
[SerializeField] private bool masks = false;
[SerializeField] private bool locks = true;

[SerializeField] private int rockFillOdds = 48;
[SerializeField] private int rockSmooths = 5;

public static float floorHeight = 50.0f;
public static float floorRatio = 1 / floorHeight;
public static float maxAiColliderSize = Tile.size - 0.1f;

// Progress of generation for loading screen
public float progress = 0;

public IEnumerator build(TileSetPrefabs prefabs, RandomNumber random)
{
	// Set built flag as false
	built = false;

	// Get the find script, used to finsing all objects in the scene
	find = GetComponent<Find>();

	// Create a map silhouette from the generated maze
	find.grid = new SilhouetteGrid(levels, rows, columns, masks, locks, deadends, random);

	// Have at least one row of border tiles to help tile prop searching
	if (border < 1) border = 1;
	int borderRows = (rows * prefabs.size) + (border * 2);
	int borderColumns = (columns * prefabs.size) + (border * 2);

	Tile[,,] tiles = TileMake.InitaliseTiles(levels, borderRows, borderColumns);

	// if we are using celular automaton to build the rock formations
	if (buildRock)
	{
		// Create surrounding asteroid rock using cellular automata
		Cellular cellular = new Cellular();
		tiles = cellular.Generate(tiles, levels, borderRows, borderColumns, rockFillOdds, rockSmooths, random);
	}

	// Progress, generated cellular tiles
	progress = 0.25f;
	yield return new WaitForEndOfFrame();

	// Create new map from tile sets
	TileSet[,,] tileset = TileSetGenerator.MakeTileSets(prefabs, find.grid.layout, levels, rows, columns, random);
	tiles = TileMake.MakeTilesSets(tileset, tiles, prefabs, levels, rows, columns, border);

	// Progress, generated all tiles
	progress = 0.5f;
	yield return new WaitForEndOfFrame();

	tiles = TileCheck.FillHoles(tiles);
	progress = 0.6f;
	yield return new WaitForEndOfFrame();

	// Create invidual tile GameObjects from array 
	LayTiles(tiles);
	progress = 0.7f;
	yield return new WaitForEndOfFrame();

	// Loops through the entire map and sets floor size scores for later detail tile use
	CreateFloorSizeScores(find.tiles);
	progress = 0.8f;
	yield return new WaitForEndOfFrame();

	// Create navigation grid
	CreatePathfinding(find.tiles, pathfindingSubsections);
	progress = 0.9f;
	yield return new WaitForEndOfFrame();

	// Create navigation grid
	TrimPathfinding(find.tiles);
	progress = 0.95f;
	yield return new WaitForEndOfFrame();

	// Set loading bar to full
	progress = 1.0f;
	yield return new WaitForEndOfFrame();

	// Set built flag
	built = true;
}

// Loops though tile array and creates tile objects, enemies and all sorts
private void LayTiles(Tile[,,] tiles)
{
	TileSpawn tileSpawn = transform.parent.GetComponentInChildren<TileSpawn>();
	find.Initialise(tiles.GetLength(0), tiles.GetLength(1), tiles.GetLength(2));

	for (int level = 0; level < tiles.GetLength(0); level++)
	{
		// Create a child object for each floor and name it with the floor level
		Vector3 position = new Vector3(0, 0, 0);

		for (int row = 0; row < tiles.GetLength(1); row++)
		{
			for (int column = 0; column < tiles.GetLength(2); column++)
			{
				// Spawn all tile objects in the correct calculated position
				position = new Vector3((column * Tile.size) + Tile.halfSize, (row * Tile.size) + Tile.halfSize, level * floorHeight);
				find.AddTileToFloor(tileSpawn.LayTile(tiles[level, row, column], position), level, row, column);
			}
		}
	}
}

// Create an a-star grid based path for the AI to use
private void CreatePathfinding (GameObject[,,] tileObjects, int divide)
{
	// Grid variables
	float nodeSize = Tile.size / divide;
	int depth = tileObjects.GetLength(1) * divide;
	int width = tileObjects.GetLength(2) * divide;

	// Create paths for AI
	Initialise(width, depth, nodeSize, maxAiColliderSize, LayerMasks.gridpath, "Graph AI");

	// Cache active path so we can rescan when changing floors and update the graph data
	activePath = AstarPath.active;
	activePath.Scan(); // This seems to disable the node removal code for some reason
}

// Create an a-star grid based path for the AI to use
private void TrimPathfinding(GameObject[,,] tileObjects)
{
	// Loop through tile objects and trim path
	for (int level = 0; level < tileObjects.GetLength(0); level++)
	{
		for (int row = 0; row < tileObjects.GetLength(1); row++)
		{
			for (int column = 0; column < tileObjects.GetLength(2); column++)
			{
				TileProp tile = tileObjects[level, row, column].GetComponent<TileProp>();

				activePath.AddWorkItem(new AstarPath.AstarWorkItem(force =>
				{
					GridNode node = activePath.GetNearest(tile.transform.position).node as GridNode;
					node.Walkable = false;
					activePath.QueueWorkItemFloodFill();
					return true;
				}));
			}
		}
	}
}


/* Creates the A* Pathfinding Project Grid For The Path System To Use */
public void Initialise(int width, int depth, float nodeSize, float diameter, LayerMask layerMask, string name)
{
	// This holds all graph data
	AstarData data = AstarPath.active.astarData;
	data.cacheStartup = true;

	// Disable path logs.
	// AstarPath.active.logPathResults = PathLog.None;

	// This creates a Grid Graph
	GridGraph gridGraph = data.AddGraph(typeof(GridGraph)) as GridGraph;

	// Name both grid graphs so we can reference them
	gridGraph.name = name;

	// Setting up the default parameters.
	gridGraph.width = width;
	gridGraph.depth = depth;
	gridGraph.nodeSize = nodeSize;

	// Because it's 2d we are rotating to face the camera, which looks down the z - axis
	gridGraph.rotation.x = 90.0f;

	// Calculating the centre based on node size and number of nodes
	gridGraph.center.x = (width * nodeSize) / 2;
	gridGraph.center.y = (depth * nodeSize) / 2;
	gridGraph.center = new Vector3(gridGraph.center.x, gridGraph.center.y, gridGraph.center.z);

	// Enabled corner cutting, disable slop detection and change slop axis to the Z
	gridGraph.cutCorners = false;
	gridGraph.neighbours = NumNeighbours.Eight;
	gridGraph.maxClimb = 0;
	gridGraph.maxClimbAxis = 2;

	// Setting to use 2d grid collision detection
	gridGraph.collision.use2D = true;
	gridGraph.collision.type = ColliderType.Sphere;
	gridGraph.collision.diameter = diameter;
	gridGraph.collision.mask = layerMask;

	// Updates internal size from the above values
	gridGraph.UpdateSizeFromWidthDepth();
}

// Finds the maximum square floor size and position and stores it for use when setting the detail tiles
public void CreateFloorSizeScores(GameObject[,,] tileObjects)
{
	for (int level = 0; level < tileObjects.GetLength(0); level++)
	{
		for (int row = tileObjects.GetLength(1) - 1; row >= 0; row--)
		{
			for (int column = tileObjects.GetLength(2) - 1; column >= 0; column--)
			{
				TileProp tile = tileObjects[level, row, column].GetComponent<TileProp>();

				if (tile.IsFloorSize())
				{
					// We assume that floor tiles are surrounded by other tiles
					TileProp northTile = tileObjects[level, row + 1, column].GetComponent<TileProp>();
					TileProp eastTile = tileObjects[level, row, column + 1].GetComponent<TileProp>();
					TileProp northEastTile = tileObjects[level, row + 1, column + 1].GetComponent<TileProp>();

					// If any tiles to the top and right aren't floor size tiles the score is 1
					if (!northTile.IsFloorSize() || !eastTile.IsFloorSize()|| !northEastTile.IsFloorSize()) tile.floorSize = 1;

					// Else the score is the minimum score from the three tiles, plus 1.
					else tile.floorSize = Maths.Min(northTile.floorSize, eastTile.floorSize, northEastTile.floorSize) + 1;
				}
			}
		}
	}
}

}
[/details]

Hm… Ok, that looks perfectly fine to me. I’m not sure why it is not working as it should be.
Maybe there’s some odd behavior in the 3.8 branch…
Just to make sure, do you think you could add the line

AstarPath.active.FlushWorkItems();

to the end of the TrimPathfinding method. This will make sure all work items are executed immediately instead of possibly the next frame or before the next path request is processed.

That doesn’t work either. :sob:

If I re-add the first scan in CreatePathfinding() and add the flush then no nodes are disabled.

If I flash without first doing a scan, I get A LOT (over 600) of those null errors.

NullReferenceException: Object reference not set to an instance of an object
Map+<TrimPathfinding>c__AnonStorey1.<>m__0 (Boolean force) (at Assets/Scripts/Generation/Map.cs:159)
AstarPath.ProcessWorkItems (Boolean force) (at Assets/Addons/AstarPathfindingProject/Core/AstarPath.cs:1052)

At least that is reasonable. You get those errors because you are trying to find the closest node to a point, but no nodes exist yet so it cannot find any.

Are you sure you don’t do any graph updates or scans in any other part of your code?

Each prop I spawn has a graph update which it does if the prop gets moved, in case it blocks a passage etc, but I am not spawning any in my current test.

Is there any optimization settings that might affect this? What about the settings page?

Also, the project is in a “Addons” subfolder. Would that make any difference?

As a test, try to change the TrimPathfinding method to contain:

AstarPath.active.AddWorkItem(new AstarPath.AstarWorkItem(force => {
    AstarPath.active.astarData.gridGraph.GetNodes(node => {
          node.Walkable = false;
          return true;
    });
}));
AstarPath.active.FlushWorkItems();

and see if that changes anything. This really should have some effect.

I’m getting a compile error with that…

Severity	Code	Description	Project	File	Line	Suppression State
Error	CS1593	Delegate 'Action' does not take 1 arguments	remote	
C:\Users\tomdo\Documents\Work\Unity\Remote\remote\Assets\Scripts\Generation\Map.cs	201	Active

Forgot a trailing paren. I updated the code above.

Sorry, it still doesn’t work.

Added a return = true; Hold on.

1 Like

Yup. Just saw I missed a return statement.

What is that supposed to do, disable all the nodes?

It doesn’t seem to do anything.

Yeah. It should disable all nodes.
Can you make sure you have A* Inspector -> Settings -> Debug -> Show Unwalkable Nodes enabled.

Yeah, they are visible, as they are working for being disabled by wall colliders.

Hm… I really don’t know what could be causing this. Something is modifying the graph after that method is called. The only thing I can think of is that you do one of

  • Scan the graph somewhere else
  • Call AstarPath.UpdateGraphs somewhere
  • Use a GraphUpdateObject or DynamicGridObstacle component somewhere

Yeah, I have found another reference to scan. Commenting this out fixed the problem.

That said, I use this to re-scan the map when I enable / disable the active floor. Is there a way of safely updating the graph data with a scan that doesn’t upset this disable node code?

Great!

Not really. Scanning a graph will recalculate everything from scratch, it doesn’t keep any information.
Is it not possible for you to use colliders on those obstacles so that they are picked up automatically by the scan instead of you having to manually disable the nodes using code?

It is possible. But it means added a collider for every single space tile in the scene, which I was trying to avoid.

If I do this node pass after the scan, would that achieve the same results?

Yes, it would work.