Процедурная генерация мира не справляется с большими картами

Итак, я делаю игру, подобную террарии, в Unity 2019 на Windws 10, используя С#, который имеет процедурно сгенерированные тайловые карты, и у меня есть этот скрипт, прикрепленный к сетке:

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;
    }
}

который я так любезно позаимствовал и модифицировал у хороших людей при случайном шуме, и я сделал пустой игровой объект, к которому я прикрепил этот скрипт:

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;
    }
}

и я прикрепил квадратные спрайты разного цвета к каждому из этих игровых объектов и сохранил их как префаб. Как только у меня был этот префаб, я прикрепил его к списку fractalLayers в моем предыдущем скрипте, чтобы генерировать руды. И хотя он отлично работает в более низком масштабе, я не могу запустить его в более крупном масштабе. А так как не существует панацеи для ускорения работы кода (кроме рефакторинга, который я не знаю, как сделать), и я, вероятно, мог бы сделать части своего кода более эффективными, так как я новичок, я бы очень хотелось бы увидеть глазами профессионала, как сделать мой код лучше. Я знаю, что не все объяснил о своем проекте, но на самом деле это всего лишь проект с голыми костями, это единственные сценарии и уникальные части, вы можете просто сделать вывод, что я сделал, и заполнить пробелы. Любая помощь приветствуется. И если бы вы могли дать мне дополнительный толчок вместе с некоторой информацией по этому вопросу, я бы хотел, чтобы мне порекомендовали несколько видео вместе с вашим пониманием, которые помогут мне в этом процессе, поскольку я больше визуальный ученик. Спасибо! знак равно

(Для справки, мне потребовалось около 4 минут, чтобы построить это с показанными здесь настройками.)< /а>


person TryingMyBest    schedule 09.03.2020    source источник
comment
Есть множество способов, которыми я мог бы оптимизировать это, просто взглянув на него. Тем не менее, я очень рекомендую использовать профилировщики производительности и памяти. Это покажет вам фактические показатели, и оттуда вы сможете получить более конкретные решения и вопросы.   -  person Zer0    schedule 09.03.2020
comment
Минимальный воспроизводимый пример сделает ваш вопрос более понятным. Особенно там куча кода. Я предлагаю профилировать ваше время затрат на StopWatch или, если вам нужна точность менее 15 мс, используйте QueryPerformanceCounter   -  person Louis Go    schedule 09.03.2020
comment
Кажется, что минимально воспроизводимые примеры предназначены для воссоздания проблемы с совершенно другим набором кода. Я вижу, как это можно использовать, и я хотел бы попытаться сделать его для вас, но, к сожалению, я не могу сказать вам, что именно делает его таким медленным. Я тоже удивляюсь, почему. Если бы вы могли дать мне идею, я бы хотел сделать то, о чем вы просите, хотя это означает больше вклада!   -  person TryingMyBest    schedule 09.03.2020
comment
@TryingMyBest Вы можете начать с изучения того, как использовать StopWatch. Проверьте стоимость каждого цикла for. Пока вы не обнаружите, что циклы требуют времени, вы можете опубликовать Минимальные воспроизводимые примеры для вашего цикла.   -  person Louis Go    schedule 09.03.2020
comment
И если мне удалось укоротить один из моих шлейфов, как вы думаете, это исключительно мои шлейфы являются причиной медлительности или есть что-то заложенное в методе генерации шума, который я использовал? Если нет, существуют ли другие методы, которые могут еще больше повысить производительность кода, например рефакторинг?   -  person TryingMyBest    schedule 09.03.2020


Ответы (1)


Итак, очевидно, мой метод генерации слов был в порядке, просто цикл, который я использовал для сглаживания рельефа, занимал слишком много времени.

person TryingMyBest    schedule 13.03.2020