SpawnOnBake (Sample Script)

Provided as a part of the Samples

Spawn On Bake (Sample)

Namespace: MegaCrush.NavmeshBaker.Sample

Purpose

Safely spawn characters or AI agents after the first successful runtime bake, avoiding errors like:

“Failed to create agent because there is no valid NavMesh.”


How it works

  • Subscribes to BakerEvents.OnBakeCompleted.

  • On each completed bake:

    • Chooses a spawn position (from Spawn Point or the GameObject this script is attached to).

    • Calls NavMesh.SamplePosition() to snap to a valid NavMesh point.

    • Instantiates the prefab.

  • If Only Once is enabled, ignores subsequent bakes.


Inspector

  • Prefab (GameObject) The object to spawn (e.g., enemy, NPC, player). If it’s an AI, it should include a NavMeshAgent.

  • Spawn Point (Transform, optional) If set, the prefab spawns here; otherwise it spawns at this component’s transform.

  • Only Once (bool) If enabled, spawns on the first completed bake only.

  • Sample Max Distance (float) Radius around the desired position to search for a valid NavMesh point.

  • Area Mask (int) NavMesh area mask for sampling (default: NavMesh.AllAreas).


Example usage

  1. Ensure your scene has a BakerCoordinator with a NavMeshBakeProfile and Center Target.

  2. Add one or more DynamicNavMeshSurface components for the regions you want baked.

  3. Create an empty GameObject and add SpawnOnBake.

  4. Assign a Prefab (e.g., an enemy with NavMeshAgent).

  5. Optionally assign a Spawn Point.

  6. Press Play — once the first bake finishes, the prefab spawns on valid NavMesh.


Why it’s useful

  • Guarantees AI never spawns off-navmesh.

  • Robust for procedural or streaming worlds where navmesh is generated at runtime.

  • Demonstrates listening to BakerEvents.OnBakeCompleted to gate gameplay logic.


Script

using MegaCrush.RuntimeNavmeshBaker;
using UnityEngine;
using UnityEngine.AI;

namespace MegaCrush.NavmeshBaker.Sample
{
    /// <summary>
    /// Spawns a prefab after the first completed runtime bake.
    /// Listens to BakerEvents instead of directly binding to the service.
    /// </summary>
    public sealed class SpawnOnBake : MonoBehaviour
    {
        [Header("Prefab to Spawn")]
        [SerializeField] private GameObject prefab;

        [Header("Spawn Settings")]
        [SerializeField] private Transform spawnPoint;
        [SerializeField] private bool onlyOnce = true;

        [Header("NavMesh Sampling")]
        [SerializeField, Min(0f)] private float sampleMaxDistance = 5f;
        [SerializeField] private int areaMask = NavMesh.AllAreas;

        private bool _hasSpawned;

        private void OnEnable()
        {
            BakerEvents.OnBakeCompleted += HandleBakeCompleted;
        }

        private void OnDisable()
        {
            BakerEvents.OnBakeCompleted -= HandleBakeCompleted;
        }

        private void HandleBakeCompleted(DynamicNavMeshSurface surface, Bounds region, float durationSeconds)
        {
            if (!prefab) return;
            if (onlyOnce && _hasSpawned) return;

            Vector3 desired = spawnPoint ? spawnPoint.position : transform.position;

            if (NavMesh.SamplePosition(desired, out NavMeshHit hit, sampleMaxDistance, areaMask))
            {
                Instantiate(prefab, hit.position, Quaternion.identity);
                _hasSpawned = true;
#if UNITY_EDITOR
                Debug.Log($"[SpawnOnBake] Spawned '{prefab.name}' at {hit.position} (region {region.center}±{region.extents})");
#endif
            }
            else
            {
#if UNITY_EDITOR
                Debug.LogWarning($"[SpawnOnBake] No valid NavMesh within {sampleMaxDistance}m of {desired}. " +
                                 "Increase Sample Max Distance or adjust Spawn Point.");
#endif
            }
        }
    }
}

Last updated