Creating A World Generator

  1. Procedural Generation Heatmaps
  2. Creating A World Generator
  3. Graphical Programming Weirdness
  4. Project: Susminer

When developing Susminer, a small 2d building/mining survival game, I needed to create a procedural world generator. And so I jumped head first by making a tile system, and then a basic bit of code to generate a small world, which gave me something like this.

So, then with this code working a bit I decided to make it expand out a bit horizontally. And then I also threw in some code to give it some extra variation on the bottom of the island.

Then, I made it start with nearly no height, and tried to make it generate in a near semi-circle design. But it’s far from perfect.

With some extra work, I got some more variety and tuned it to something a bit better. Procedural generation is really hard to get perfect, but “pretty good” is a fine metric for something like this.

Oh, and I also eventually added ore clusters which just replaces some blocks with an ore block.

Also, here’s the mess of the code I had for this at the end. Don’t worry, it got cleaned up later.

    ///C#, as it is in this final image
void GenerateMap() 
    {
        Debug.Log("Generating Map");
        Vector2 center = new Vector2(0, 0);
        BlockData grass = (BlockData) blocks[1];
        BlockData dirt = (BlockData)blocks[0];
        BlockData stone = (BlockData)blocks[3];
        BlockData ironOre = (BlockData)blocks[4];

        int maxWidth = 80;
        int maxHeight = 40;

        int minWidth = 40;
        int minHeight = 20;

        int heightDiff = maxHeight - minHeight;
        int widthDiff = maxWidth - minWidth;

        int height = ((int)(Random.value * heightDiff) + minHeight);
        int width = ((int)(Random.value * widthDiff) + minWidth);

        int coreWidth = (int) (width / 3);
        int coreHeight = (int)(height);
        int baseHeight = 2;

        int prevHeightVar = (int) (Random.value * 4);
        int curHeight = baseHeight;

        int maxPosYVar = 5;
        int maxNegYVar = 5;

        int posYVar = 1;
        int negYVar = 1;

        Debug.Log($"Height: {height}, Width: {width} ");
        
        for (int x = (width/2) * -1; x < width/2; x++) 
        {
            int calcX = x + width / 2;
            int coreHeightDiff = coreHeight - curHeight;
            int coreWidthDiff = coreWidth - calcX;
            int coreWidthDiffLate = (coreWidth * 2);
            float dist =0;
            if (coreHeightDiff > 0 && coreWidthDiff > 0)
            {
                dist = (coreHeightDiff / coreWidthDiff);
                Debug.Log($" coreWidthDiff: {coreWidthDiff} , coreHeightDiff: {coreHeightDiff} DIST: {dist}");
            }
            else 
            {
                dist = ((0 - curHeight) / (width - calcX) * 2);
            }

            
            

            if (coreWidthDiff == 0) 
            {
                Debug.Log($"Core Width Diff equals zero");
                coreWidthDiff = 1;
            }

            if (calcX < coreWidth)
            {
                if (curHeight < coreHeight)
                {
                    int temp = (int)(Random.value * (dist) );
                    curHeight += temp;
                    Debug.Log($"CurHeight added to by {temp}");
                }
                else 
                {
                    curHeight -= (int)(Random.value * 3);
                }
            } else if (calcX >= coreWidth && calcX < (coreWidth * 2))
            {
                curHeight = coreHeight;
                Debug.Log("Is Centered");
            }
            else 
            {
                Debug.Log($"CurHeight added to by {dist}");
                curHeight += (int)(Random.value * (dist));
                
            }


            if (curHeight <= 0) { curHeight = 1; }

            if (posYVar < maxPosYVar)
            {
                int coinflip = (int)(Random.value * 5);
                if (coinflip > 0 && coinflip <= 2 )
                {
                    posYVar += (int)(Random.value * 2);
                }
                else if (coinflip == 0)
                {
                    posYVar -= (int)(Random.value * 2);
                }
            }
            else 
            {
                posYVar -= (int)(Random.value * 2);
            }

            if (posYVar < 0) { posYVar = 2; }

            if (negYVar < maxNegYVar)
            {
                int coinflip = (int)(Random.value * 2);
                if (coinflip > 0)
                {
                    negYVar += (int)(Random.value * 4);
                }
                else
                {
                    negYVar -= (int)(Random.value * 2);
                }
            }
            else
            {
                negYVar -= (int)(Random.value * 4);
            }

            if (negYVar <= 0) { negYVar = 1; }

            int startHeight = posYVar;
            int endHeight =  (curHeight + negYVar) * -1;
            Debug.Log($"StartHeight: {startHeight} End Height: {endHeight}");
            for (int y = startHeight; y >  endHeight; y--) 
            {
                if (y == startHeight)
                {
                    map.Add(new BlockInstance(grass,(short) x, (short)y));
                }
                else if (y < startHeight && y >= startHeight - 2)
                {
                    map.Add(new BlockInstance(dirt, (short)x, (short)y));
                }
                else 
                {
                    map.Add(new BlockInstance(stone, (short)x, (short)y));
                }
            }
        }

        //Adding Ore Clusters
        int clusterSizeMin = 4;
        int clusterSizeMax = 16;

        int maxClusters = 12;
        int minClusters = 4;

        int clusters = minClusters + ((int) (Random.value * (maxClusters - minClusters)));
        Debug.Log($"Clusters to be spawned: {clusters}");

        for (int i = 0; i < clusters; i++) 
        {

            int clusterSize = clusterSizeMin + ((int) (Random.value * (clusterSizeMax - clusterSizeMin)));
            Vector2 [] cPos = new Vector2 [clusterSize];

            int clusterX = 0;
            int clusterY = 0;
            cPos[0] = new Vector2(0, 0);

            for (int ii = 1; ii < clusterSize; ii++) 
            {
                int die = (int)(Random.value * 4);
                if (die == 1) //drawing a square for remainder on one
                {
                    int diff = clusterSize - i;
                    int count = 0;
                    if (diff % 2 == 0) 
                    {
                        for (int x = 0; x < diff / 2; x++) 
                        {
                            for (int y = 0; y < diff / 2; y++) 
                            {
                                if (ii + count >= clusterSize) { break; }
                                cPos[ii + count] = 
                                    new Vector2(cPos[ii - 1].x + 
                                    x, cPos[ii-1].y + y);
                                count++;
                            }
                        }
                        break;
                    }
                    else 
                    {
                        for (int x = 0; x <( diff / 2) -1; x++)
                        {
                            for (int y = 0; y < diff / 2; y++)
                            {
                                if (ii + count >= clusterSize) { break; }
                                cPos[ii + count] = new Vector2(cPos[ii - 1].x + x, cPos[ii - 1].y + y);
                            }
                        }
                        break;
                    }
                }
                else if (die ==2) // going down
                {
                    cPos[ii] = new Vector2(cPos[ii].x, cPos[ii].y + 1);
                }
                else if (die == 3) // going down
                {
                    cPos[ii] = new Vector2(cPos[ii].x + 1, cPos[ii].y);
                }
            }

            BlockInstance start = (BlockInstance) map[ (int) (Random.value * map.Count) ];
            while (start.blockData != stone) 
            {
                start = (BlockInstance) map[(int)(Random.value * map.Count)];
            }

            short startX = start.x;
            short startY = start.y;

            for (short s = 0; s < clusterSize; s++) 
            {
                BlockInstance block = findBlock(startX + (short) cPos[s].x, startY + (short)cPos[s].y);
                block.blockData = ironOre;
            
            }
        }
    }