Как создать определенный процент сущностей с помощью шума Перлина С#?

Рейтинг: 0Ответов: 1Опубликовано: 12.07.2023

У меня есть карта местности в виде двумерного массива. Я на ней генерирую деревья (1) с помощью шума Перлина.

0 0 0 0 0      0 1 1 1 1
0 0 0 0 0      1 1 1 0 0
0 0 0 0 0  ->  0 1 0 1 1
0 0 0 0 0      0 1 0 0 0
0 0 0 0 0      1 1 1 0 0

При генерации я указываю сколько процентов деревьев я хочу создать.

for (var x = sector.top; x < sector.topOffset; x++)
for (var y = sector.left; y < sector.leftOffset; y++)
{
    var rawNoise = Mathf.PerlinNoise((x - noiseOffsetX) / noiseScale, (y - noisnoiseScale);
    var clampedNoise = Mathf.Clamp(rawNoise, 0, 1);
    if (clampedNoise < percent)
    {
        _map[x, y] = 1;
    }
}

Но теперь я хочу создать камни (2) так, чтобы они появлялись только на пустых полях (0), сохранив при этом выбранный мной процент. И как это можно сделать (если вообще возможно) не могу понять.

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

Думал еще над вариантом использовать обычный рандом. Тут получится сохранить процент циклом: пока не заспавниться N штук. Но тут уже проблема с тем, что они буду слишком рандомно расположены, а не в "кучках" как от шума Перлина. Примерно так:

0 1 1 1 1
1 2 0 2 1
2 0 0 0 1
0 1 0 2 0
2 1 1 0 2

Еще был вариант передавать сразу все сущности для спавна в метод генерации, чтобы сделать несколько проверок:

for (var x = sector.top; x < sector.topOffset; x++)
for (var y = sector.left; y < sector.leftOffset; y++)
{
    var rawNoise = Mathf.PerlinNoise((x - noiseOffsetX) / noiseScale, (y - noisnoiseScale);
    var clampedNoise = Mathf.Clamp(rawNoise, 0, 1);
    if (clampedNoise < wood)
    {
        _map[x, y] = 1;
    }
    else if (clampedNoise < stone)
    {
        _map[x, y] = 2;
    }
}

Но тогда камни будут всегда только окружать деревья, а нужно больше рандомности:

2 1 1 1 1
1 2 2 2 1
2 0 0 2 1
1 0 2 0 2
2 1 1 2 0

Вот так сейчас происходит наполнение самой карты:

var terrainMaskBuilder = new TerrainMaskBuilder(mapSize);       
terrainMaskBuilder.AddPerlinTerrain(Size.FullMap, entity: 1, percent: 0.6, noiseScale: 5);
terrainMaskBuilder.AddPerlinTerrain(Size.FullMap, entity: 2, percent: 0.2, noiseScale: 5);
var result = terrainMaskBuilder.Get();

UPD1: Появился вариант создавать маски с сущностями с помощью шума, накладывать их на карту и проверять есть ли их N процентов от всех. Если нет, то создавать новую маску. И так в while цикле. Но насколько такой подход оптимален?

Ответы

▲ 2Принят

Для всех свободных клеток вычислите значение шума Перлина. Соберите клетки в массив (значение шума, x, y), отсортируйте по убыванию шума. Отберите нужное количество клеток из начала массива. Расставьте камни в этих клетках.