Search code examples
c#unity-game-enginespawnprocedural-generation

Procedural world generator spawns overlapping ground tiles - Unity3D C#


I am a bit of a noob at coding so sorry if I'm missing something obvious.

I am working on procedurally generating a junkyard of finite boundaries. I used a 2D unity tutorial (part 1 of 3 found here: https://www.youtube.com/watch?v=qAf9axsyijY&ab_channel=Blackthornprod ) to make a skeleton for my code but I have had issues with ground tiles spawning overlapping. I have tried a few fixes online but nothing gets the job completely done.

Physics.Overlap has made the code work much better but there are still regular overlaps. I suspect the overlapping tiles may be spawning at the exact same instance which makes the Overlap command return 0 colliders errantly, but I have no clue how to test that or avoid it.

Here is my code. It is almost a direct copy from the vid above.:

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

public class spawnTile : MonoBehaviour
{
    public int openingDirection;
    //1 = N
    //2 = E
    //3 = S
    //4 = W

    private GroundTemplates templates;
    //Stores tiles to spawn

    private int rand;
    private bool spawned = false;
    private bool IsInBounds = false;
    public LayerMask checkBox;
    //Layermask to check if there is already ground at spawn point
    public Vector3 chunkScale;
    //size of check area for dup spawns
    void Start()
    {
        templates = GameObject.FindGameObjectWithTag("GroundTiles").GetComponent<GroundTemplates>();
        StartCoroutine(Spawn());
    }
    public IEnumerator Spawn()
    {
        Collider[] hitColliders = Physics.OverlapBox(transform.position, chunkScale , Quaternion.identity, checkBox); // makes an array of objects inside chunk with layer mask checkBox. Used for checking if ground is present
        yield return new WaitForSeconds(.2f); 
        Debug.Log("colliders: " + hitColliders.Length);
        if (spawned == false )
        {
            if (openingDirection == 1 && IsInBounds == true && hitColliders.Length == 0)
            {
                rand = Random.Range(0, templates.NorthRooms.Length);
                Instantiate(templates.NorthRooms[rand], transform.position, templates.NorthRooms[rand].transform.rotation);
            }
            else if (openingDirection == 2 && IsInBounds == true && hitColliders.Length == 0)
            {
                rand = Random.Range(0, templates.EastRooms.Length);
                Instantiate(templates.EastRooms[rand], transform.position, templates.EastRooms[rand].transform.rotation);
            }
            else if (openingDirection == 3 && IsInBounds == true && hitColliders.Length == 0)
            {
                rand = Random.Range(0, templates.SouthRooms.Length);
                Instantiate(templates.SouthRooms[rand], transform.position, templates.SouthRooms[rand].transform.rotation);
            }
            else if (openingDirection == 4 && IsInBounds == true && hitColliders.Length == 0)
            {
                rand = Random.Range(0, templates.WestRooms.Length);
                Instantiate(templates.WestRooms[rand], transform.position, templates.WestRooms[rand].transform.rotation);
            }
            spawned = true;
           
        }
    }



    private void OnTriggerEnter(Collider other)
    {
        
             if (other.CompareTag("Spawn") && (other.GetComponent<spawnTile>().spawned == true))
        {
            Destroy(gameObject);
            Debug.Log("Destroyed");
        }
             else if (other.CompareTag("InBounds"))
        {
            IsInBounds = true;
        }
    }
}

Some pics of the output:

a generated map

two overlapping tiles

spawn point prefab that code is attached to

ground tile prefab example

I assume there are some redundancies in the code from multiple debugging attempts, so lmk if I can streamline things.


Solution

  • A quick san of the videos showed no indication that the original author use a Coroutine to generate the new tile.

    For each tile you generate, you check to see if there's an overlap, then you wait 0.2 seconds. It's that waiting there that could very easily allow a different tile to also check the same space and find no tiles overlapping. So two or more tiles will see no overlaps, and all write to the same position.

    Might I suggest cleaning up the code, remove that forced delay and trying this instead:

    void Start ( )
    {
        templates = GameObject.FindGameObjectWithTag ( "GroundTiles" ).GetComponent<GroundTemplates> ( );
    
        var hitColliders = Physics.OverlapBox ( transform.position, chunkScale, Quaternion.identity, checkBox ); // makes an array of objects inside chunk with layer mask checkBox. Used for checking if ground is present
        Debug.Log ( "colliders: " + hitColliders.Length );
        if ( !spawned )
        {
            if ( IsInBounds && hitColliders.Length == 0 )
            {
                GameObject [ ] rooms = null;
                switch ( openingDirection )
                {
                    case 1: rooms = templates.NorthRooms; break;
                    case 2: rooms = templates.EastRooms; break;
                    case 3: rooms = templates.SouthRooms; break;
                    case 4: rooms = templates.WestRooms; break;
                    default: return;
                }
    
                var room = rooms [ UnityEngine.Random.Range ( 0, rooms.Length ) ];
                Instantiate ( room, transform.position, room.transform.rotation );
            }
            spawned = true;
        }
    }