Custom Navmesh Generation
Changing and constructing the Quantum navmesh during run-time is challenging. Other pathfinding solutions are more compatible with procedural run-time level generation, for example tile based pathfinding or Flow Fields.
For procedurally generated navmeshes two approaches are feasible:
- Generate the navmesh on one client and share it with the others
- Deterministically generate
NavMeshBakeDataand bake locally on each client
Generating and Sharing A Navmesh
One client bakes the navmesh using the Unity tools and then shares it with each other client. Because of its lengthy and impactful baking process it is best to synchronize this before any client started the Quantum simulation.
- Bake the Unity navmesh
- Import it into
NavMeshBakeDatawithQuantumNavMesh.ImportFromUnity() - Bake the Quantum navmesh with
NavMeshBaker.BakeNavMesh() - Serialize the navmesh and upload it to a file server
- Download the navmesh data on the other clients
- Replace the navmesh data
- Start Quantum
Selecting the client to generate the navmesh can be done by using the admin Photon client or utilizing a custom backend.
Quantum Asset Injection and DynamicAssets can also be used to customize and share navmeshes, but this only works well when the navmesh asset is small (e.g. less than 1MB), otherwise sending so much data during startup causes a lot of network friction with the Realtime protocol.
Serialize The Navmesh
A baked navmesh is serialized into the same format that is stored in the navmesh data asset (the _data asset next to the navmesh asset).
C#
var navmesh = NavMeshBaker.BakeNavMesh(map, bakeData);
var stream = new ByteStream(new byte[1024 * 1024 * 10]);
navmesh.Serialize(stream, true);
var navmeshBytes = stream.ToArray();
// upload navmeshBytes
Navmeshes with the NavMesh Serialize Type BakeDataOnly only store the bake data and bake the navmesh when it is loaded, which reduces the data to share, see Importing A Unity Navmesh.
Replace The Navmesh Data
The navmesh deserializes its data asset when it is loaded and unloads the data asset afterwards. Replace the data before the navmesh is loaded, which is before the simulation starts.
C#
// Load the pre-existing navmesh data asset
var navmeshData = QuantumUnityDB.GetGlobalAsset(new AssetRef<BinaryData>(navmeshDataGuid));
// Replace it with the downloaded data
// Grid and world sizes etc must match, regions will be added to the map correctly
navmeshData.SetData(downloadedNavmeshBytes, compress: false);
// await SessionRunner.StartAsync(arguments)
Deterministically Generate Navmesh BakeData
This approach does not require the Unity navmesh baking. Instead, it works with parts of the navmesh being procedurally generated and then baked into a Quantum navmesh. Because the input data (NavMeshBakeData) is already a navmesh and not a collection of colliders (as used by the Unity navmesh generation) this solution works well when it can be stitched together from pre-generated navmesh parts or triangles can be generated with a simple pattern. See the section below about the internals of the NavMeshBakeData class.
Requirements:
NavMeshBakeDatacreation needs to be deterministic
The bake data is generated by each client individually in a deterministic way which removes the necessity to share it with others but late-joiners also have to go through this process.
The completed bake data is used to generate a Quantum navmesh, either as a new navmesh asset or baked into an existing one.
C#
// Bake a new navmesh asset, adds missing regions to the map
var navmesh = NavMeshBaker.BakeNavMesh(map, bakeData);
// Or bake into an existing (dummy) navmesh asset that is already referenced by the map
NavMeshBaker.BakeIntoExistingNavMesh(bakeData, existingNavmesh);
Replace The Navmesh During Start
In this code sample, an already loaded dummy navmesh is replaced with a runtime generated navmesh when the game starts or resyncs.
Replacing a navmesh during the game is also possible this way:
- Only during a verified frame
- All information and parameters to bake the navmesh have to be available on the frame (e.g. singleton component or globals) for late-joiners to be able to generate the correct navmesh when resyncing.
C#
namespace Quantum {
using UnityEngine;
public class RuntimeNavmeshBaking : QuantumCallbacks {
public override void OnGameStart(QuantumGame game, bool isResync) {
// In this sample the bake data has already been created, but it should be assembled during runtime
var bakeData = Resources.Load<BakeDataSO>("BakeData").BakeData;
// The dummy navmesh is referenced by the map and loaded with it
var navmesh = game.Frames.Verified.Map.NavMeshes["DummyNavmesh"];
// The name and the regions cannot be changed without changing the map too
NavMeshBaker.BakeIntoExistingNavMesh(bakeData, navmesh);
}
}
public class BakeDataSO : ScriptableObject {
public NavMeshBakeData BakeData;
}
}
NavMeshBakeData
The intermediate navmesh format NavMeshBakeData is a deterministic mesh with additional data that is converted to a Quantum navmesh asset by NavMeshBaker.BakeNavMesh(Map map, NavMeshBakeData bakeData) during run-time.
NavMeshBakeData Class
| Type | Field | Description |
|---|---|---|
string |
Name |
The name of the navmesh accessible inside the simulation by f.Map.NavMeshes[name]. |
FPVector3 |
Position |
The position of the navmesh. Final navmesh vertices are stored in global space and their positions are translated by this during baking. |
FP |
AgentRadius |
The radius of the largest agents that the navmesh is created for. Agents can walk up until their pivot is on the edge of the navmesh, the margin agents should keep away from walls is baked into the triangles. This value is only used to render debug graphics. |
string[] |
Regions |
All region ids that are used in this navmesh. During baking the region ids will be added to the region list of the map asset and their index is baked into the navmesh triangles region mask (NavMeshTriangle.Regions). The regions are aggregated on the map because a map can have multiple navmeshes that share the region ids. |
NavMeshBakeDataVertex[] |
Vertices |
The vertices of the navmesh. |
NavMeshBakeDataTriangle[] |
Triangles |
The triangles of the navmesh. The triangles and vertices are kept in two separate arrays and each triangle points into the vertex array to mark its 3 vertices. |
NavMeshBakeDataLink[] |
Links |
Links between positions on the same navmesh. |
NavMeshBakeDataFindClosestTriangle |
ClosestTriangleCalculation |
The Quantum navmesh uses a grid for spatial partitioning. Each grid cell will have a fallback triangle assigned. The default search is quite slow (BruteForce) while SpiralOut is more efficient, but it could result in empty fallback triangles. |
int |
ClosestTriangleCalculationDepth |
The number of grid cells to expand the SpiralOut search. |
bool |
EnableQuantum_XY |
When enabled the navmesh baking will flip Y and Z components of the vertex positions to support navmeshes generated in the XY plane. |
NavMeshBakeDataTriangle Class
Triangles are expected to have clock-wise winding order.
| Type | Field | Description |
|---|---|---|
int[] |
VertexIds |
Must have length of 3. The referenced vertices as indices into the vertex array. |
string |
RegionId |
The region that this triangle belongs to. Default is null. |
FP |
Cost |
The cost of the triangle. Default should be FP._1. |
int |
Area |
The Unity navmesh area, not required. |
NavMeshBakeDataVertex Class
| Type | Field | Description |
|---|---|---|
FPVector3 |
Position |
The position of the vertex. |
NavMeshBakeDataLink Class
| Type | Field | Description |
|---|---|---|
FPVector3 |
Start |
Start position of the link. Must be on the same navmesh. |
FPVector3 |
End |
End position of the link. Must be on the same navmesh. |
int |
StartTriangle |
The triangle index of the start position. |
int |
EndTriangle |
The triangle index of the end position. |
bool |
Bidirectional |
Can the link be traversed from both directions. |
FP |
CostOverride |
The cost of the connection. |
string |
RegionId |
The region id that the link belongs to. Default is null. |
string |
Name |
The name of the link. Can be queried by navmesh.Links[pathfinder.CurrentLink(f)].Name. |
Snippet
C#
// Generate simple navmesh bake data
var bakeData = new NavMeshBakeData() {
AgentRadius = FP._0_20,
ClosestTriangleCalculation = NavMeshBakeDataFindClosestTriangle.SpiralOut,
ClosestTriangleCalculationDepth = 1,
Name = "DynamicNavmesh",
Position = FPVector3.Zero,
Regions = new string[0],
Vertices = new NavMeshBakeDataVertex[] {
new NavMeshBakeDataVertex { Position = FPVector3.Forward },
new NavMeshBakeDataVertex { Position = FPVector3.Right },
new NavMeshBakeDataVertex { Position = -FPVector3.Forward },
new NavMeshBakeDataVertex { Position = -FPVector3.Right },
},
Triangles = new NavMeshBakeDataTriangle[] {
new NavMeshBakeDataTriangle { VertexIds = new int[] { 0, 1, 2 }, Cost = FP._1 },
new NavMeshBakeDataTriangle { VertexIds = new int[] { 0, 2, 3 }, Cost = FP._1 }
},
Links = new NavMeshBakeDataLink[0]
};
Last updated on
Back to top