Object Instantiating at a Position that it is Not Supposed To

I’m working on a dungeon generator that generates rooms based on certain rules.

Whenever a room generates, I add its position to a list and make sure that the next room generated doesn’t generate at any of the coordinates in the list.

However, there are some instances where rooms still generate in an occupied spot. I’m a bit confused on why this is, as I tried my best to include as many “failsafes” as I could.

My code is attached below, and as always, thanks in advance for any help you may have!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DungeonGenerationManager : MonoBehaviour
{
    public GameObject room_prefab;
    public int numberOfRooms;
    public int roomSize;

    private void Start()
    {
        InitialIteration();

    }

    void InitialIteration() //Create rooms
    {
        List<Vector3> occupiedCoordinates = new List<Vector3>();
        Vector3 currentPosition = Vector3.zero;

        for(int i = 0;  i < numberOfRooms; i++)
        {
            List<string> generationOptions = new List<string>();

            if (!occupiedCoordinates.Contains(new Vector3(currentPosition.x + roomSize, 0,currentPosition.z)))
            {
                if (!generationOptions.Contains("right"))
                {
                    generationOptions.Add("right");
                }
            }

            if (!occupiedCoordinates.Contains(new Vector3(currentPosition.x - roomSize, 0, currentPosition.z)))
            {
                if (!generationOptions.Contains("left"))
                {
                    generationOptions.Add("left");
                }

            }

            if (!occupiedCoordinates.Contains(new Vector3(currentPosition.x, 0, currentPosition.z + roomSize)))
            {
                if (!generationOptions.Contains("up"))
                {
                    generationOptions.Add("up");
                }

            }

            if (!occupiedCoordinates.Contains(new Vector3(currentPosition.x, 0, currentPosition.z - roomSize)))
            {
                if (!generationOptions.Contains("down"))
                {
                    generationOptions.Add("dowm");
                }
            }

            string direction = generationOptions[Random.Range(0, generationOptions.Count)];
            if(direction == "right")
            {
                currentPosition = new Vector3(currentPosition.x + roomSize, 0, currentPosition.z);
            }
            else if(direction == "left")
            {
                currentPosition = new Vector3(currentPosition.x - roomSize, 0, currentPosition.z);
            }
            else if(direction == "up")
            {
                currentPosition = new Vector3(currentPosition.x, 0, currentPosition.z + roomSize);
            }
            else if(direction == "down")
            {
                currentPosition = new Vector3(currentPosition.x, 0, currentPosition.z - roomSize);
            }

            occupiedCoordinates.Add(currentPosition);

            Instantiate(room_prefab, currentPosition, Quaternion.identity);

        }

    }

    
}

Very likely this is failing due to floating point precision, or lack thereof.

If this is meant to be on grid positions, use Vector3Int instead. Otherwise, account for imprecision. Nonetheless, you should be debugging this on your end. This should be trivial to debug.

Though seriously, what’s up with the strings and repeated code? Just have a static readonly list of coordinates, and run through said collection in a loop.

What do you mean? How would I use a list of coordinates to determine left, right, up, and down?

Because you can just use simple math to get the results you want, rather than huge if-elseif chains to interpret what a string is meant to mean.

This stuff is incredibly crucial to understand for any kind of coordinate based, tile based, and proc-gen stuff in general.

Rough example of what I mean:

public class DungeonGenerationManager : MonoBehaviour
{
	public GameObject room_prefab;
	public int numberOfRooms;
	public int roomSize;

	private static readonly List<Vector3Int> _roomDirections = new()
	{
		Vector3Int.foward,
		Vector3Int.right,
		Vector3Int.back,
		Vector3Int.left
	};

	private void Start()
	{
		InitialIteration();
	}

	void InitialIteration() //Create rooms
	{
		List<Vector3Int> occupiedCoordinates = new();
		Vector3Int currentPosition = Vector3Int.zero;

		List<Vector3Int> spawnPositions = new(); // instance the list once for reuse

		for (int i = 0;  i < numberOfRooms; i++)
		{
			for (int j = 0; j < _roomDirections.Count; j++)
			{
				Vector3Int direction = _roomDirections[j];
				Vector3Int nextPosition = currentPosition + (direction * roomSize);
				if (occupiedCoordinates.Contains(nextPosition) == false)
				{
					spawnPositions.Add(nextPosition);
				}
			}
			
			int spawnPositionIndex = Random.Range(0, spawnPositions.Count);
			Vector3Int spawnPosition = spawnPositions[spawnPositionIndex];

			Instantiate(room_prefab, spawnPosition, Quaternion.identity);
			occupiedCoordinates.Add(spawnPosition);

			spawnPositions.Clear(); // clear the reused list
			currentPosition = spawnPosition;
		}
	}
}
1 Like

Thank you! That makes sense! I’ll try this out!

Okay! I fixed my issue!

I also added to the code so that if it runs into a spot where there are no possible positions, a random occupied spot is picked and the cycle continues.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DungeonGenerationManager : MonoBehaviour
{
    public GameObject room_prefab;
    public int numberOfRooms;
    public int roomSize;

    private static readonly List<Vector3Int> roomDirections = new List<Vector3Int>()
    {
        Vector3Int.forward,
        Vector3Int.back,
        Vector3Int.right,
        Vector3Int.left
    };
    private void Start()
    {
        InitialIteration();
    }

    void InitialIteration() //Create rooms
    {
        List<Vector3Int> occupiedCoordinates = new List<Vector3Int>();
        Vector3Int currentPosition = Vector3Int.zero;

        for(int i = 0;  i < numberOfRooms; i++)
        {
            List<Vector3Int> possibleSpawnPositions = new List<Vector3Int>();

            for(int j = 0; j < roomDirections.Count; j++)
            {
                Vector3Int possiblePosition = currentPosition + (roomDirections[j] * roomSize);
                if (!occupiedCoordinates.Contains(possiblePosition))
                {
                    possibleSpawnPositions.Add(possiblePosition);
                }
            }

            if(possibleSpawnPositions.Count > 0)
            {
                Vector3Int chosenPosition = possibleSpawnPositions[Random.Range(0, possibleSpawnPositions.Count)];
                currentPosition = chosenPosition;
            
                occupiedCoordinates.Add(currentPosition);
                Instantiate(room_prefab, currentPosition, Quaternion.identity);
            }
            else
            {
                print("no more available spots!");
                currentPosition = occupiedCoordinates[Random.Range(0, occupiedCoordinates.Count)];
            }

        }

    }

    
}
1 Like