Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411283 Posts in 69325 Topics- by 58380 Members - Latest Member: bob1029

March 29, 2024, 02:55:28 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Help: Unity C# Procedural World Generation Cant Handle Big Maps
Pages: [1]
Print
Author Topic: Help: Unity C# Procedural World Generation Cant Handle Big Maps  (Read 1402 times)
__personGuy
TIGBaby
*


View Profile
« on: March 08, 2020, 05:06:52 PM »

so, I'm making a terraria-like game in unity using c# which has procedurally generated tilemaps and i have this script attached to a grid:

Code:
using UnityEngine;
using AccidentalNoise;
using System.Collections.Generic;
using UnityEngine.Tilemaps;
using System;

public class CompileTerrain : MonoBehaviour
{

    public TileBase dirtTile;
    public TileBase grassTile;
    public TileBase stoneTile;

    public List<GameObject> fractalLayers = new List<GameObject>();

    public Tilemap grid;
    public int width;
    public int height;

    public float seed;
    public int caveSmoothness = 2;

    void Start()
    {
        grid.ClearAllTiles();

        int touchCount = 0;
        Vector3Int newPos;
        double nx, ny;

        ModuleBase combinedTerrain = CavesAndMountains((uint)seed);
        List<Vector3Int> terrainCoords = new List<Vector3Int>();
        SMappingRanges ranges = new SMappingRanges();

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                nx = (ranges.mapx0 + ((double)x / (double)width) * (ranges.mapx1 - ranges.mapx0)) * 3;
                ny = (ranges.mapy0 + ((double)y / (double)height) * (ranges.mapy1 - ranges.mapy0)) * 3;

                if (combinedTerrain.Get(nx, ny) > 0f)
                {
                    terrainCoords.Add(new Vector3Int(x, height - y, 0));
                }
            }
        }

        List<Tuple<int, int>> neighbors = new List<Tuple<int, int>>() {Tuple.Create(1, 1), Tuple.Create(-1, -1),
                                                                       Tuple.Create(0, 1), Tuple.Create(1, 0),
                                                                       Tuple.Create(0, -1), Tuple.Create(-1, 0),
                                                                       Tuple.Create(-1, 1), Tuple.Create(1, -1)};

        for (int index = 0; index < terrainCoords.Count; index++)
        {
            if (index == terrainCoords.Count)
            {
                break;
            }

            touchCount = 0;

            for (int posAdd = 0; posAdd < neighbors.Count; posAdd++)
            {
                newPos = new Vector3Int(terrainCoords[index].x + neighbors[posAdd].Item1, terrainCoords[index].y + neighbors[posAdd].Item2, 0);
                touchCount += terrainCoords.Contains(newPos) ? 1 : 0;
            }

            if (touchCount < 2)
            {
                terrainCoords.Remove(terrainCoords[index]);
            }

        }

        for (int j = 0; j < caveSmoothness; j++)
        {
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    if (!terrainCoords.Contains(new Vector3Int(x, y, 0)))
                    {
                        touchCount = 0;

                        for (int posAdd = 0; posAdd < neighbors.Count; posAdd++)
                        {
                            newPos = new Vector3Int(x + neighbors[posAdd].Item1, y + neighbors[posAdd].Item2, 0);
                            touchCount += terrainCoords.Contains(newPos) ? 1 : -1;
                        }

                        if (touchCount > 1)
                        {
                            terrainCoords.Add(new Vector3Int(x, y, 0));
                        }

                    }

                }

            }
        }

        foreach (Vector3Int blck in terrainCoords)
        {
            grid.SetTile(blck, stoneTile);
        }

        terrainCoords.Sort((x, y) => x.x == y.x ? x.y.CompareTo(y.y) : x.x.CompareTo(y.x));
        terrainCoords.Reverse();

        TileBase selectedTile;
        int depth = 0;
        int lastx = 0;
        int lasty = terrainCoords[0].y + 1;

        foreach (Vector3Int blck in terrainCoords)
        {
            depth = blck.x != lastx ? 0 : depth;
            lasty = blck.x != lastx ? blck.y + 1 : lasty;

            selectedTile = depth < 4 ? grassTile : stoneTile;
            selectedTile = 3 < depth && depth < 30 ? dirtTile : selectedTile;

            grid.SetTile(blck, selectedTile);

            lastx = blck.x;
            depth += lasty - blck.y;
            lasty = blck.y;
        }

        int layerNum = 1;
        List<Vector3Int> posList = new List<Vector3Int>();

        foreach (GameObject layer in fractalLayers)
        {
            GetPerlinLayer component = layer.GetComponent<GetPerlinLayer>();

            for (int k = 0; k < component.populateCount; k++)
            {
                layerNum++;
                foreach (Vector3Int pos in component.GetFractalCoords(width, height, (uint)(seed * layerNum)))
                    if (grid.GetTile(pos) != null && grid.GetTile(pos) != grassTile)
                    {
                        grid.SetTile(pos, component.defaultTile);
                    }
            }
        }
    }

    public static ModuleBase CavesAndMountains(uint seed)
    {
        AccidentalNoise.Gradient ground_gradient = new AccidentalNoise.Gradient(0, 0, 0, 1);

        // lowlands
        Fractal lowland_shape_fractal = new Fractal(FractalType.BILLOW, BasisTypes.GRADIENT, InterpTypes.QUINTIC, 2, 0.25, seed);
        AutoCorrect lowland_autocorrect = new AutoCorrect(lowland_shape_fractal, 0, 1);
        ScaleOffset lowland_scale = new ScaleOffset(0.125, -0.45, lowland_autocorrect);
        ScaleDomain lowland_y_scale = new ScaleDomain(lowland_scale, null, 0);
        TranslatedDomain lowland_terrain = new TranslatedDomain(ground_gradient, null, lowland_y_scale);

        // highlands
        Fractal highland_shape_fractal = new Fractal(FractalType.FBM, BasisTypes.GRADIENT, InterpTypes.QUINTIC, 4, 2, seed);
        AutoCorrect highland_autocorrect = new AutoCorrect(highland_shape_fractal, -1, 1);
        ScaleOffset highland_scale = new ScaleOffset(0.25, 0, highland_autocorrect);
        ScaleDomain highland_y_scale = new ScaleDomain(highland_scale, null, 0);
        TranslatedDomain highland_terrain = new TranslatedDomain(ground_gradient, null, highland_y_scale);

        // mountains
        Fractal mountain_shape_fractal = new Fractal(FractalType.RIDGEDMULTI, BasisTypes.GRADIENT, InterpTypes.QUINTIC, 8, 1, seed);
        AutoCorrect mountain_autocorrect = new AutoCorrect(mountain_shape_fractal, -1, 1);
        ScaleOffset mountain_scale = new ScaleOffset(0.3, 0.15, mountain_autocorrect);
        ScaleDomain mountain_y_scale = new ScaleDomain(mountain_scale, null, 0.15);
        TranslatedDomain mountain_terrain = new TranslatedDomain(ground_gradient, null, mountain_y_scale);

        // terrain
        Fractal terrain_type_fractal = new Fractal(FractalType.FBM, BasisTypes.GRADIENT, InterpTypes.QUINTIC, 3, 0.125, seed);
        AutoCorrect terrain_autocorrect = new AutoCorrect(terrain_type_fractal, 0, 1);
        ScaleDomain terrain_type_y_scale = new ScaleDomain(terrain_autocorrect, null, 0);
        AccidentalNoise.Cache terrain_type_cache = new AccidentalNoise.Cache(terrain_type_y_scale);
        Select highland_mountain_select = new Select(terrain_type_cache, highland_terrain, mountain_terrain, 0.55, 0.2);
        Select highland_lowland_select = new Select(terrain_type_cache, lowland_terrain, highland_mountain_select, 0.25, 0.15);
        AccidentalNoise.Cache highland_lowland_select_cache = new AccidentalNoise.Cache(highland_lowland_select);
        Select ground_select = new Select(highland_lowland_select_cache, 0, 1, 0.5, null);

        // caves
        Fractal cave_shape = new Fractal(FractalType.RIDGEDMULTI, BasisTypes.GRADIENT, InterpTypes.QUINTIC, 1, 4, seed);
        Bias cave_attenuate_bias = new Bias(highland_lowland_select_cache, 0.65);
        Combiner cave_shape_attenuate = new Combiner(CombinerTypes.MULT, cave_shape, cave_attenuate_bias);
        Fractal cave_perturb_fractal = new Fractal(FractalType.FBM, BasisTypes.GRADIENT, InterpTypes.QUINTIC, 6, 3, seed);
        ScaleOffset cave_perturb_scale = new ScaleOffset(0.5, 0, cave_perturb_fractal);
        TranslatedDomain cave_perturb = new TranslatedDomain(cave_shape_attenuate, cave_perturb_scale, null);
        Select cave_select = new Select(cave_perturb, 1, 0, 0.75, 0);

        return new Combiner(CombinerTypes.MULT, cave_select, ground_select) as ModuleBase;
    }
}

which i have so graciously borrowed and modified from the fine folks at accidental noise, and i made an empty gameobject which i attached this script to:

Code:
using UnityEngine;
using AccidentalNoise;
using System.Collections.Generic;
using UnityEngine.Tilemaps;

public class GetPerlinLayer : MonoBehaviour
{

    public TileBase defaultTile;
    public float threshold = 0.5f;
    public int populateCount = 5;

    public List<Vector3Int> GetFractalCoords(int width, int height, uint seed)
    {
        double nx, ny;

        ModuleBase combinedTerrain = new Fractal(FractalType.FBM, BasisTypes.GRADIENT, InterpTypes.QUINTIC, 6, 2, seed);
        List<Vector3Int> fractalCoords = new List<Vector3Int>();
        SMappingRanges ranges = new SMappingRanges();

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                nx = (ranges.mapx0 + ((double)x / (double)width) * (ranges.mapx1 - ranges.mapx0)) * 3;
                ny = (ranges.mapy0 + ((double)y / (double)height) * (ranges.mapy1 - ranges.mapy0)) * 3;

                if (combinedTerrain.Get(nx, ny) > threshold)
                {
                    fractalCoords.Add(new Vector3Int(x, height - y, 0));
                }
            }
        }

        return fractalCoords;
    }
}

and i attached different colored square sprites for each of those gameobjects, and saved them as a prefab. Once i had that prefab, i attached that to the fractalLayers list in my previous script to generate ores. And although it runs fine on a lower scale, I cant run it on a larger scale. And since there's no cure-all for making code run faster (aside from refactoring, which i don't know how to do), and i probably could've made parts of my code more efficient since I'm a novice, i would really like some insight from the eyes of a professional on how to make my code run better. I know i didn't explain everything about my project but its really just a bare-bones project those are the only scripts and unique parts about it, you can just infer what i did and fill in the blanks. Any help is appreciated. And if you could give me the extra push along with some information on the subject, I would love to have some videos recommended along with your insight to guide me along this process, since i am more of a visual learner. Thank you! =)
Logged
dr.crow
Level 1
*



View Profile
« Reply #1 on: April 28, 2020, 06:38:22 AM »

This is a bit old, so I guess you might have moved on or found a solution by now?

Anyway. A couple of suggestions:

- Don't generate what you don't need. Divide your world into chunks and generate only what the player can interact with.
- Once generated, consider continuously deallocating chunks of the world the player can't see or interact with. If you need persistency, you can use difference lists to store what changed and regenerate and reapply those changes when the player comes back.
- Look in the profiler which part of the algorithm is taking the longest and focus on that first.
- Use burst to speed up the unity c#
- Use parallel jobs for the parallel parts of your algorithm (the noise part is a good candidate for this)
- Or if you need to generate insane amounts of data very fast, and your algorithm is easily parallelizable, you can use compute shaders on the GPU and get an insane speedup at the cost of annoying to write code and some overhead when starting and reading from gpu tasks. I haven't done this in unity, though, but I did a project back in 2014 where I compared a C++ version against a GPU version and for a 8192x8192 patch it was around half a second on the CPU and 1ms on the GPU. But as I said, some things are hard to program on the GPU, though.

My thesis and project are here in case you're interested: https://ntnuopen.ntnu.no/ntnu-xmlui/handle/11250/2352231 and noisemodeler.org
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic