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