Pathfinding Guide
intermediate tasksTerrain Grid System 2 · Common Tasks
Pathfinding Guide
Terrain Grid System 2 includes a built-in A* pathfinder that works with hexagonal, box, and irregular topologies. This guide explains the methods available, the main options you can tweak, and shows two common setups: standard two-way movement and a one-way connection between adjacent cells.
TGS. All examples assume a TerrainGridSystem tgs reference, typically obtained with TerrainGridSystem.instance or a serialized field.
Pathfinding Methods
TGS exposes several entry points for computing a path. Pick the one that matches how your game consumes the result.
| Method | Use When |
|---|---|
FindPath(int start, int end, ...) |
Synchronous. Returns a List<int> of cell indices, or null if no path is found. Fastest for short paths computed occasionally. |
FindPath(int start, int end, out float totalCost, ...) |
Same as above but also returns the accumulated cost of the path. |
FindPath(int start, int end, List<int> results, out float totalCost, ...) |
Fills a caller-provided list to avoid per-call allocations. Recommended when paths are recomputed every frame. |
FindPath(int start, int end, List<int> results, out float totalCost, FindPathOptions options) |
Overload that takes a FindPathOptions object. Use this when you need to customize masks, costs, or cross-cell callbacks per call. |
FindPathAsync(int start, int end, Action<FindPathAsyncResult> onComplete, FindPathOptions options = null) |
Runs A* on a background thread and invokes the callback when done. Use for long paths or large grids to avoid frame hitches. |
Task<FindPathAsyncResult> FindPathAsync(int start, int end, FindPathOptions options = null) |
Task-based variant compatible with async/await. |
TGSMultiGridPathFinder |
Separate helper class for computing paths that span multiple TerrainGridSystem instances joined by custom connections. See demo scene Demo33_MultiGrid_PathFinding. |
Global Settings
The following properties on the TerrainGridSystem component act as defaults for every call:
pathFindingHeuristicFormula— Manhattan, Euclidean, MaxDXDY, DiagonalShortCut. Affects path shape on box grids.pathFindingMaxSteps— hard cap on the number of cells evaluated.pathFindingMaxCost— hard cap on the accumulated cost (used whenmaxSearchCostis 0).pathFindingUseDiagonals— whether box grids can cut corners.pathFindingHeavyDiagonalsCost— multiplier applied to diagonal moves.pathFindingIncludeInvisibleCells— whether hidden cells participate in searches.
FindPathOptions
When calling the overload that takes FindPathOptions, you can override per-call:
maxSearchCost,maxSteps— local overrides of the global caps.cellGroupMask,cellGroupMaskExactComparison— restrict which cell groups are walkable.canCrossCheckType— control how thecanCrossflag is evaluated on start/end cells (useful to let units leave an obstacle tile they already stand on).ignoreCellCosts— compute shortest-distance paths ignoring terrain costs.includeInvisibleCells— local override of the global flag.minClearance— require a minimum number of adjacent walkable cells (for wide units).maxCellCrossCost— reject any cell whose total crossing cost exceeds this threshold. Very useful together with high side costs for hard blocks.OnPathFindingCrossCell— delegate invoked during the search to return an extra dynamic cost for a cell.
Cell-Level Walkability and Costs
Before calling FindPath, configure which cells and sides can be traversed. All setters live on the TerrainGridSystem component:
| Method | Purpose |
|---|---|
CellSetCanCross(int cellIndex, bool canCross) | Marks a cell as walkable or blocked entirely (both directions). |
CellSetCrossCost(int cellIndex, float cost, CELL_DIRECTION direction) | Sets a per-cell entering or exiting cost. Defaults to Entering. |
CellSetSideCrossCost(int cellIndex, CELL_SIDE side, float cost, CELL_DIRECTION direction) | Sets the cost of crossing one specific side of a cell. Supports directional costs. |
CellSetGroup(int cellIndex, int group) | Assigns a group used together with cellGroupMask to filter walkable cells. |
100000f) and pass a smaller maxCellCrossCost in FindPathOptions, or check the returned totalCost and reject the path if it exceeds your expected budget.
Use Case 1 — General Two-Way Pathfinding
This is the default setup: units can move freely in both directions, terrain types have different costs, and a few tiles are blocked as obstacles.
using UnityEngine;
using TGS;
public class BasicPathfinding : MonoBehaviour {
public TerrainGridSystem tgs;
public int startCell;
public int endCell;
readonly List<int> pathBuffer = new List<int>(64);
void Start() {
// 1. Mark water tiles as impassable.
foreach (int waterIndex in GetWaterCells()) {
tgs.CellSetCanCross(waterIndex, false);
}
// 2. Mark forest tiles with higher movement cost.
foreach (int forestIndex in GetForestCells()) {
tgs.CellSetCrossCost(forestIndex, 3f, CELL_DIRECTION.Any);
}
}
public bool TryMove() {
pathBuffer.Clear();
int steps = tgs.FindPath(startCell, endCell, pathBuffer, out float totalCost);
if (steps <= 0) {
Debug.Log("No path available");
return false;
}
Debug.Log($"Path found, {steps} cells, cost {totalCost}");
// pathBuffer[0] is the first step after startCell.
return true;
}
IEnumerable<int> GetWaterCells() { /* your logic */ yield break; }
IEnumerable<int> GetForestCells() { /* your logic */ yield break; }
}
Key points:
CellSetCanCross(false)blocks a cell symmetrically, both as a destination and as a transit tile.CellSetCrossCostwithCELL_DIRECTION.Anymakes terrain costs symmetric, which is what you want for normal movement.- Reusing the
pathBufferlist avoids allocations on every recomputation.
Use Case 2 — One-Way Connection (A → B, but not B → A)
Scenarios like ramps, ledges, escalators, portals, or locked gates need asymmetric movement: a unit can go through in one direction but not the opposite. TGS supports this through the CELL_DIRECTION parameter on CellSetSideCrossCost.
How It Works
The enum has three values:
CELL_DIRECTION.Exiting— the cost applies when leaving the given cell through the given side.CELL_DIRECTION.Entering— the cost applies when entering the given cell through the given side (internally this writes the cost on the opposite side of the neighbor cell).CELL_DIRECTION.Any— applies both entering and exiting costs. This is the default.
By setting a very high exiting cost in one direction only, you make the A* planner avoid that transition, while the opposite direction remains cheap.
Example
Assume two adjacent cells, aIndex and bIndex, and you want to allow movement from A to B but block it from B back to A.
using UnityEngine;
using TGS;
public class OneWayBridge : MonoBehaviour {
public TerrainGridSystem tgs;
public int aIndex;
public int bIndex;
const float BLOCKED = 100000f;
void Start() {
// Find the side of B that faces A.
if (!TryGetSideBetween(bIndex, aIndex, out CELL_SIDE sideBtoA)) {
Debug.LogWarning("Cells are not adjacent");
return;
}
// Block exiting B through the side that leads to A.
// A can still enter B from the opposite side because
// A's own exiting cost toward B is not touched.
tgs.CellSetSideCrossCost(bIndex, sideBtoA, BLOCKED, CELL_DIRECTION.Exiting);
}
bool TryGetSideBetween(int fromCell, int toCell, out CELL_SIDE side) {
for (int s = 0; s < 8; s++) {
CELL_SIDE candidate = (CELL_SIDE)s;
if (tgs.GetAdjacentCellCoordinates(fromCell, candidate, out int or, out int oc, out _)) {
if (tgs.CellGetIndex(or, oc) == toCell) {
side = candidate;
return true;
}
}
}
side = default;
return false;
}
}
For a longer chain like A → B → C allowed, C → B → A forbidden, repeat the same step at B (blocking exit toward A) and at C (blocking exit toward B).
Enforcing a Hard Block
A high cost alone still lets A* pick the forbidden side if no other route exists. Two options to make it truly impassable:
- Pass a
FindPathOptionswithmaxCellCrossCostset below the blocked value:var options = new FindPathOptions { maxCellCrossCost = 10000f // BLOCKED is 100000, so it never fits }; tgs.FindPath(startCell, endCell, pathBuffer, out float totalCost, options); - Or check
totalCostafter the call and reject paths whose cost exceeds your normal budget.
CellSetCanCross(cell, false) disables a cell in both directions, so it cannot be used to create a one-way link. Always use CellSetSideCrossCost with CELL_DIRECTION.Exiting or Entering for directional control.
See Also
- Pathfinding API — full signature reference.
- Cells API — cell properties, groups, and side methods.
- Demo scenes: Demo12_PathFinding, Demo14_TerrainPathFinding, Demo33_MultiGrid_PathFinding, Demo34_AsyncPathFinding.
Suggest an improvement
Help us improve this documentation page.