Search code examples
c#unity-game-enginegameobjectprocedural-generationprefab

Object Insatiate with a plane that is procedurally generated


I am new to procedural generation in Unity and I am making a simple game with a player in a plane that is procedurally generated, the requirement is that I am trying to bring up some walls simultaneously with the generation of the plane which is not working. The code so far for this is below:

    public GameObject WorldPlane;
    public GameObject Cube;
    public GameObject walls;

    private int radius = 5;
    private int planeOffset = 10;

    private Vector3 startPos = Vector3.zero;

    private int XPlayerMove => (int)(Cube.transform.position.x - startPos.x);
    private int ZPlayerMove => (int)(Cube.transform.position.z - startPos.z);

    private int XPlayerLocation => (int)Mathf.Floor(Cube.transform.position.x / planeOffset) * planeOffset;
    private int ZPlayerLocation => (int)Mathf.Floor(Cube.transform.position.z / planeOffset) * planeOffset;

    Hashtable tilePlane = new Hashtable();

    void Update()
    {
        generateWorld();
    }

    private void generateWorld()
    {
        if (startPos == Vector3.zero)
        {
            for (int x = -radius; x < radius; x++)
            {
                for (int z = -radius; z < radius; z++)
                {
                    Vector3 pos = new Vector3((x * planeOffset + XPlayerLocation),
                    0,
                    (z * planeOffset + ZPlayerLocation));

                    if (!tilePlane.Contains(pos))
                    {
    
                        GameObject tile = Instantiate(WorldPlane, pos, Quaternion.identity); 
                        tilePlane.Add(pos, tile);
                    }
                }
            }
        }
        if (hasPlayerMoved(XPlayerMove, ZPlayerMove))
        {
            for (int x = -radius; x < radius; x++)
            {
                for (int z = -radius; z < radius; z++)
                {
                    Vector3 pos = new Vector3((x * planeOffset + XPlayerLocation),
                    0,
                    (z * planeOffset + ZPlayerLocation));

                    if (!tilePlane.Contains(pos))
                    {
                        GameObject Wall=Instantiate(walls, pos, Quaternion.identity);
                        GameObject tile = Instantiate(WorldPlane,pos,Quaternion.identity);
                        tilePlane.Add(pos, tile); 
                        tilePlane.Add(pos, Wall);

                    }
                }
            }
        }
    }

    private bool hasPlayerMoved(int playerX, int playerZ)
    {
        if (Mathf.Abs(XPlayerMove) >= planeOffset || Mathf.Abs(ZPlayerMove) >= planeOffset)
        {
            return true;
        }
        return false;
    }

Here the cube is the player, the world plane is the prefab for the plane which should be generated, and the walls are the prefab for the game object that I am trying to instantiate with the plane, as of now the plane is getting generated without any issues. I really stuck with this, requesting help.


Solution

  • This condition is alway true:

    if (startPos == Vector3.zero)
    

    Because you never change startPos. Therefore, you already assigned a Tile to pos via tilePlane.Add(pos, tile);

    So in the lower if you check this:

    if (!tilePlane.Contains(pos)) // fails, because you already added a Plane above.
    

    To solve this, you can set StartPos to something else than zero after running the double-loop once. So later, when the cube(player) moves, new tiles + wall get generated using the lower if.

    But you should not add 2 entries to one hashtable-key. So this will throw an error:

     tilePlane.Add(pos, tile); 
     tilePlane.Add(pos, Wall);
    
     ArgumentException: Item has already been added. Key in dictionary: '(0.00, 0.00, 70.00)'  Key being added: '(0.00, 0.00, 70.00)'
    

    So you either need 2 Hashtables (1 for tiles, 1 for wall) or you need a struct/class to hold both.

    Example:

    class Helper {
        public GameObject wall;
        public GameObject tile;
    }
    

    then in your code:

    GameObject Wall = Instantiate(walls, pos, Quaternion.identity);
    GameObject tile = Instantiate(WorldPlane,pos,Quaternion.identity);
                        
    Helper help = new Helper();
    help.wall = Wall;
    help.tile = tile;
    tilePlane.Add(pos, help); // add the class containing both to key `pos`.
    

    enter image description here


    Edit: Yes it's procedural. I added random rotation to the walls to make it a maze. (Wall is an empty GameObject with the wall as child, so that I could offset the wall so it rotates around the tiles center.)

    Check it out:

    enter image description here

    Here is the code:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PlaneGeneration : MonoBehaviour
    {
        // Start is called before the first frame update
        public GameObject WorldPlane;
        public GameObject Cube;
        public GameObject walls;
    
        private int radius = 5;
        private int planeOffset = 10;
    
        private bool startAreaGenerated = false;
        private Vector3 startPos = Vector3.zero;
        
    
        private int XPlayerMove => (int)(Cube.transform.position.x - startPos.x);
        private int ZPlayerMove => (int)(Cube.transform.position.z - startPos.z);
    
        private int XPlayerLocation => (int)Mathf.Floor(Cube.transform.position.x / planeOffset) * planeOffset;
        private int ZPlayerLocation => (int)Mathf.Floor(Cube.transform.position.z / planeOffset) * planeOffset;
    
        Hashtable tilePlane = new Hashtable();
    
        private void Start()
        {
            startPos = Cube.transform.position;
        }
    
        class Helper
        {
            public GameObject tile;
            public GameObject wall;
        }
    
        void Update()
        {
            generateWorld();
        }
    
        private void generateWorld()
        {
            if (!startAreaGenerated)
            {
                for (int x = -radius; x < radius; x++)
                {
                    for (int z = -radius; z < radius; z++)
                    {
                        Vector3 pos = new Vector3((x * planeOffset + XPlayerLocation), 0, (z * planeOffset + ZPlayerLocation));
    
                        if (!tilePlane.Contains(pos))
                        {
    
                            GameObject tile = Instantiate(WorldPlane, pos, Quaternion.identity);
                            Helper helper = new Helper();
                            helper.tile = tile;
                            tilePlane.Add(pos, helper);
                        }
                    }
                }
                startAreaGenerated = true;
            }
            if (hasPlayerMoved(XPlayerMove, ZPlayerMove))
            {
                for (int x = -radius; x < radius; x++)
                {
                    for (int z = -radius; z < radius; z++)
                    {
                        Vector3 pos = new Vector3((x * planeOffset + XPlayerLocation),0, (z * planeOffset + ZPlayerLocation));
    
                        if (!tilePlane.Contains(pos))
                        {
                            int random0to3 = Random.Range(0, 4); // 0, 1, 2 or 3.
                            Quaternion randomRotation = Quaternion.Euler(0, random0to3 * 90, 0); // rotated 0°, 90°, 180° or 270° around Y-Axis.
                            GameObject Wall = Instantiate(walls, pos, randomRotation);
                            GameObject tile = Instantiate(WorldPlane, pos, Quaternion.identity);
    
                            Helper helper = new Helper();
                            helper.tile = tile;
                            helper.wall = Wall;
                            tilePlane.Add(pos, helper);
                        }
                    }
                }
            }
        }
    
        private bool hasPlayerMoved(int playerX, int playerZ)
        {
            if (Mathf.Abs(XPlayerMove) >= planeOffset || Mathf.Abs(ZPlayerMove) >= planeOffset)
            {
                return true;
            }
            return false;
        }
    }