for the past few months I've been making a Binding of Isaac like game with some inspiration from the older Zelda games. I previously made a post here ( Generating a Navigation Mesh at runtime for a 2d semi-procedural dungeon crawler(Unity) ) where I was struggling to even get a Navmesh to bake as I was using scenes to generate the rooms. I've since moved to using prefabbed rooms and instantiating them at runtime to place randomly in the dungeon. I've also gotten the Nav mesh surfaces to apply to each room throughout the dungeon and can see them all in the rooms they are supposed to be in, how they are supposed to look etc. In my hierarchy at run time, I am able to see the rooms and their contents in my RoomController gameObject which is part of my main scene. It shows as though they are being spawned by that room, but are all spawning inside of the start room. Below im going to provide some screenshots of what I'm seeing to better illustrate the issue. Before pressing playAfter pressing playShowing the spawner
I would like the enemies to spawn in their own individual rooms, see image 2 where "Basmement-RoomBasic" has 3 smallDemons and 4 MeleeEnemies. These enemies would spawn in that room on the nav mesh
I've tried rebaking the nav mesh, baking it at runtime with a navigation baker taken directly from Unity's guide on Runtime navigation baking, and removing sections of the tilemap that they shouldn't spawn on and making it only inside the room. I tried modifing my GridController, my DungeonGenerator, and my ObjectSpawner scripts to no avail. Below I will also provide these scripts.
First the GridController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class GridController : MonoBehaviour
{
public Room room;
[System.Serializable]
public struct Grid
{
public int columns, rows;
public float verticalOffset, horizontalOffset;
}
public Grid grid;
public GameObject gridTile;
public List<Vector2> availablePoints = new List<Vector2>();
void Awake()
{
room = GetComponentInParent<Room>();
GenerateGrid();
}
public void GenerateGrid()
{
grid.verticalOffset += room.transform.localPosition.y;
grid.horizontalOffset += room.transform.localPosition.x;
for (int y = 0; y < grid.rows; y++)
{
for (int x = 0; x < grid.columns; x++)
{
GameObject go = Instantiate(gridTile, transform);
go.GetComponent<Transform>().position = new Vector2(x - (grid.columns - grid.horizontalOffset), y - (grid.rows - grid.verticalOffset));
go.name = "X: " + x + ", Y: " + y;
availablePoints.Add(go.transform.position);
go.SetActive(false);
}
}
GetComponentInParent<ObjectRoomSpawner>().InitialiseObjectSpawning();
}
public List<Vector3> GetSources()
{
List<Vector3> sources = new List<Vector3>();
foreach (Transform child in transform)
{
if (child.gameObject.activeSelf)
{
NavMeshHit hit;
if (NavMesh.SamplePosition(child.position, out hit, 0.1f, NavMesh.AllAreas))
{
sources.Add(hit.position);
}
else
{
Debug.LogWarning("Could not find a valid position on the NavMesh for source spawn");
}
}
}
return sources;
}
}
Next the DungeonGenerator
using NavMeshPlus.Components;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DungeonGenerator : MonoBehaviour
{
public DungeonGenerationData dungeonGenerationData;
private List<Vector2Int> dungeonRooms;
public Room startingRoomPrefab;
public Room bossRoomPrefab;
public Room[] roomPrefabs;
private void Start()
{
dungeonRooms = DungeonCrawlerController.GenerateDungeon(dungeonGenerationData);
SpawnRooms(dungeonRooms);
Debug.Log("Nav mesh should be rebuilt now");
}
private void SpawnRooms(IEnumerable<Vector2Int> rooms)
{
// Load the starting room
RoomController.instance.LoadRoom(startingRoomPrefab, 0, 0);
List<Room> loadedRooms = new List<Room>();
loadedRooms.Add(startingRoomPrefab);
foreach (Vector2Int roomLocation in rooms)
{
// Check if a room already exists at the specified location
Room room = RoomController.instance.GetRoomAtPosition(roomLocation.x, roomLocation.y);
if (room != null || loadedRooms.Any(loadedRoom => loadedRoom.X == roomLocation.x && loadedRoom.Y == roomLocation.y))
{
continue;
}
// Get a random room prefab from the list
int randomIndex = Random.Range(0, roomPrefabs.Length);
Room randomRoomPrefab = roomPrefabs[randomIndex];
// Load the room prefab at the given location
RoomController.instance.LoadRoom(randomRoomPrefab, roomLocation.x, roomLocation.y);
loadedRooms.Add(randomRoomPrefab);
Debug.Log("Room spawned at x = " + roomLocation.x + ", and at y = " + roomLocation.y);
}
}
}
And finally the ObjectSpawner
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class ObjectRoomSpawner : MonoBehaviour
{
[System.Serializable]
public struct RandomSpawner
{
public string name;
public SpawnerData spawnerData;
}
public GridController grid;
public RandomSpawner[] spawnerData;
void Start()
{
//grid = GetComponentInChildren<GridController>();
}
public void InitialiseObjectSpawning()
{
foreach (RandomSpawner rs in spawnerData)
{
SpawnObjects(rs);
}
}
void SpawnObjects(RandomSpawner data)
{
int randomIteration = Random.Range(data.spawnerData.minSpawn, data.spawnerData.maxSpawn + 1);
for (int i = 0; i < randomIteration; i++)
{
int randomPos = Random.Range(0, grid.availablePoints.Count - 1);
// Get a random point on the NavMesh
NavMeshHit hit;
if (NavMesh.SamplePosition(grid.availablePoints[randomPos], out hit, 10f, NavMesh.AllAreas))
{
// Instantiate the enemy prefab at the NavMesh position
GameObject go = Instantiate(data.spawnerData.itemToSpawn, hit.position, Quaternion.identity, transform) as GameObject;
grid.availablePoints.RemoveAt(randomPos);
Debug.Log("Spawned Object at position " + hit.position);
}
else
{
Debug.LogWarning("Could not find a valid position on the NavMesh for enemy spawn");
}
}
}
}
Any information would be greatly appreciated to point me in the right direction. I've been working on this game all semester long for my graduation project, and finishing this up would make the game practically complete. Thanks in advance!
Edit # 1
I figured it could be useful to additionally show examples of the nav mesh at runtime.