1

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.

1 Answers1

1

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;
    }
}
KYL3R
  • 3,877
  • 1
  • 12
  • 26
  • Thank you for this, really hard to dissolve this, well so according to the code are the walls being instantiated at the start or will they be placed according to player movement, in a procedural way? Sorry not have much experience in this, also could you show me where I need to place these in code, and is there a way to rotate the prefabs about 90 degrees so that I can look like a small maze? Many thanks in advance. – Joice Philip Thomas Aug 30 '22 at 12:37
  • This is really helpful, @KYL3R you are a lifesaver, don't know if it is too much to ask but now we have the prefabs getting generated as per the player movement is it possible to make a small maze like the same at the start of the plane generation, when the game starts. – Joice Philip Thomas Aug 30 '22 at 15:35
  • yes just remove the whole block of `if (!startAreaGenerated)` and in the 2nd clause change `if (hasPlayerMoved(XPlayerMove, ZPlayerMove))` to `if (hasPlayerMoved(XPlayerMove, ZPlayerMove) || !startAreaGenerated)` and set `startAreaGenerated = true` somewhere in that block. – KYL3R Aug 30 '22 at 19:17