Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
vJass
Тип:
Алгоритм
Версия Warcraft:
1.35

Генератор ландшафта-шума

Введение

Вашему вниманию предлагается алгоритм генератора случайного ландшафта похожего на белый шум. Алгоритм нацелен на предварительную генерацию до начала основных действий игры. Описание алгоритма состоит из трех частей. Генерация текстур рельефа, генерация деформаций рельефа, генерация декораций. Алгоритм не содержит никаких публичных функций кроме инициализации. В целом готов к копированию в любую карту без изменений, возникающие проблемы легко решаются изменением малого числа констант. Результаты алгоритма можно видеть на рисунках.
Рис. Демонстрационные изображения результата: 1 - из примера, 2 - при вдвое увеличенных кистях

Основная часть

Генератор текстур ландшафта

В предложенной реализации добавлено четыре набора (или типа) текстур ландшафта летнего Лордерона, однако их число легко расширить. Каждый тип текстур, для отрисовки, нуждается в определении трех параметров: номер типа, вес вероятности отрисовки, размер кисти.
//Задание типа/набора текстур ланшафта
set loc_TerrianType[0] = 'Lgrs'
//Вероятностные веса для каждого из типов текстур
set loc_TerrianType_W[0] = 4 
//Задание размера кистей для каждого типа текстуры
set loc_TerrianType_Size[0] = 2
Вероятностные веса определяют вероятность того, что именно этот конкретный тип будет отрисован, по формуле: (вероятность отрисовки) = (вес типа)/(сумма всех весов). Соответственно, чем больше вес тем больше вероятность отрисовки. Чем больше кисть тем бОльшие свЯзные участки данного типа ландшафта вы получите. Соответственно, чем больше вес тем больше участков территории будет покрыто данном типом ландшафта. Регулировку итераций отрисовки можно варьировать двумя способами. Первый вариант это просто изменять максимальное число в цикле отрисовки.
code vJass
//Цикл отрисовки текстур
    set i = 0
    loop
    //Выход при достижении примерного максимума команд потока
    exitwhen(i == 20000) //максимум 32k-40k
        loop
            set loc_rintx = GetRandomInt(0,loc_TerrianTypeCount-1)
            set loc_rinty = GetRandomInt(1,loc_TerrianType_WM)
            exitwhen( loc_TerrianType_W[loc_rintx] <= loc_rinty)
        endloop
        set loc_X = GetRandomReal(loc_minX, loc_maxX)
        set loc_Y = GetRandomReal(loc_minY, loc_maxY)
        call SetTerrainType(loc_X, loc_Y, loc_TerrianType[loc_rintx], -1, loc_TerrianType_Size[loc_rintx], 0)
    set i = i + 1
    endloop
Однако, так как варкрафт имеет лимит на операций в одном потоке исполнения триггера, для очень больших карт этого может отказаться недостаточно. Поэтому можно добавлять дополнительные потоки, то есть создавать новые потоки через новые триггеры, а затем уничтожать триггер при завершении операции.
code vJass
    set i = 0
    loop 
        //Число потоков
        exitwhen(i == 5)
        set loc_trg_LandscapeGen = CreateTrigger()
        call TriggerRegisterTimerEventSingle( loc_trg_LandscapeGen, 0.01 )
        call TriggerAddAction( loc_trg_LandscapeGen, function LandscapeGen )
        set i = i + 1
    endloop

Генератор деформаций ландшафта

Генератор деформаций ландшафта использует три числа, это максимальная высота холмов и максимальная глубина впадин при деформации, а также радиус деформации.
local integer loc_MinH =  10 //Максимальная высота
local integer loc_MaxH =  10 //Максимальная глубина
local real loc_R = 512       //Радиус деформации
Замечу, что, максимальные размеры холмов и впадин не отражают максимального их итогового размера, так как процесс вероятностный, разные холмы и впадины могут накладываться друг на друга, поэтому увеличение числа итераций генерации деформаций, при сохранении размера карты, будет вызывать увеличение средней высоты гор и впадин. Поэтому балансировку следует осуществлять как для числа итераций, так для и высот впадин и холмов.
Как в случае с отрисовкой текстур, увеличение числа итераций реализовано двумя способами. Как прямым увеличением итераций, так и увеличением потоков.

Генератор декораций ландшафта/окружения

В данном алгоритме все декорации реализованы через спецэффект. Однако другой популярный способ через юнита также не лишон смысла, так как в этом случае при повторном искажении ландшафта декорации не будут утопать в ландшафте. Однако тут спецэффекты выбраны именно из-за простоты внедрения в различные карты и, как следствие, субъективной элегантности исполнения.
Для работы алгоритму необходимо указать только ссылку на модель окружения, например
set ModelPathArray[0] = "Doodads\\Ruins\\Plants\\Ruins_Shrub\\Ruins_Shrub0.mdl"
Число декораций регулируется числом итераций, которые также реализовано двумя способами. Как прямым увеличением итераций, так и увеличением потоков.

Весь алгоритм

code vJass

library LandscapeLib


// ===== Алгоритм генерации текстур ландшафта ======
private function LandscapeGen takes nothing returns nothing
    local real loc_minX = GetRectMinX(GetEntireMapRect())
    local real loc_minY = GetRectMinY(GetEntireMapRect())
    local real loc_maxX = GetRectMaxX(GetEntireMapRect())
    local real loc_maxY = GetRectMaxY(GetEntireMapRect())
    local integer array loc_TerrianType
    local integer array loc_TerrianType_W
    local integer array loc_TerrianType_Size
    local integer loc_TerrianType_WM = 0
    local integer loc_TerrianTypeCount
    local integer loc_rintx
    local integer loc_rinty
    local integer i
    local real loc_X
    local real loc_Y
    //
    //Задание типа/набора текстур ланшафта
    set loc_TerrianType[0] = 'Lgrs'
    set loc_TerrianType[1] = 'Lgrd'
    set loc_TerrianType[2] = 'Ldro'
    set loc_TerrianType[3] = 'Ldrt'
    //
    //Вероятностные веса для каждого из типов текстур
    set loc_TerrianType_W[0] = 4 
    set loc_TerrianType_W[1] = 4
    set loc_TerrianType_W[2] = 1
    set loc_TerrianType_W[3] = 1
    //
    //Задание размера кистей для каждого типа текстуры
    set loc_TerrianType_Size[0] = 2
    set loc_TerrianType_Size[1] = 2
    set loc_TerrianType_Size[2] = 1
    set loc_TerrianType_Size[3] = 1
    //
    //Количество типов текстур
    set loc_TerrianTypeCount = 4
    //
    set i = 0
    loop 
    exitwhen (i == loc_TerrianTypeCount)
        if(loc_TerrianType_WM < loc_TerrianType_W[i])then
            set loc_TerrianType_WM = loc_TerrianType_W[i]
        endif
        set i = i + 1
    endloop
    //
    //Цикл отрисовки текстур
    set i = 0
    loop
    //Выход при достижении примерного максимума команд потока
    exitwhen(i == 20000) //максимум 32k-40k
        loop
            set loc_rintx = GetRandomInt(0,loc_TerrianTypeCount-1)
            set loc_rinty = GetRandomInt(1,loc_TerrianType_WM)
            exitwhen( loc_TerrianType_W[loc_rintx] <= loc_rinty)
        endloop
        set loc_X = GetRandomReal(loc_minX, loc_maxX)
        set loc_Y = GetRandomReal(loc_minY, loc_maxY)
        call SetTerrainType(loc_X, loc_Y, loc_TerrianType[loc_rintx], -1, loc_TerrianType_Size[loc_rintx], 0)
    set i = i + 1
    endloop
    call BJDebugMsg("Генератор ланшафта прошли все: " + I2S(i))
    call DestroyTrigger(GetTriggeringTrigger())
endfunction
//
// ===== Алгоритм генерации деформаций ландшафта ======
private function TerrianDeformGen takes nothing returns nothing
    local real loc_minX = GetRectMinX(GetEntireMapRect())
    local real loc_minY = GetRectMinY(GetEntireMapRect())
    local real loc_maxX = GetRectMaxX(GetEntireMapRect())
    local real loc_maxY = GetRectMaxY(GetEntireMapRect())
    local real loc_X
    local real loc_Y
    local integer loc_MaxH = 10 // Максимальная глубина деформации
    local integer loc_MinH = 10 // Максимальная высота деформации
    local real loc_R = 512      // Радиус деформации
    local integer i
    set i = 0
    loop
        set loc_X = GetRandomReal(loc_minX, loc_maxX)
        set loc_Y = GetRandomReal(loc_minY, loc_maxY)
        call TerrainDeformCrater(loc_X, loc_Y, loc_R, GetRandomInt(-loc_MinH, loc_MaxH) * 1, 1, true)
        set i = i + 1
        exitwhen(i == 2000)
    endloop
    call BJDebugMsg("Генератор деформации прошли все: " + I2S(i))
endfunction

// ===== Алгоритм генерации окружения ======
private function CreateEnviroment takes nothing returns nothing
    local real loc_minX = GetRectMinX(GetPlayableMapRect())
    local real loc_minY = GetRectMinY(GetPlayableMapRect())
    local real loc_maxX = GetRectMaxX(GetPlayableMapRect())
    local real loc_maxY = GetRectMaxY(GetPlayableMapRect())
    local integer i
    local string array ModelPathArray
    local integer ModelPathArrayCount
    local effect loc_effect
    local real loc_X
    local real loc_Y
    set ModelPathArray[0] = "Doodads\\Ruins\\Plants\\Ruins_Shrub\\Ruins_Shrub0.mdl"
    set ModelPathArray[1] = "Doodads\\Ruins\\Plants\\Ruins_Shrub\\Ruins_Shrub1.mdl"
    set ModelPathArray[2] = "Doodads\\Ruins\\Plants\\Ruins_Shrub\\Ruins_Shrub2.mdl"
    set ModelPathArray[3] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower0.mdl"
    set ModelPathArray[4] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower1.mdl"
    set ModelPathArray[5] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower2.mdl"
    set ModelPathArray[6] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower3.mdl"
    set ModelPathArray[7] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower4.mdl"
    set ModelPathArrayCount = 8
    set i = 0
    loop
        set loc_X = GetRandomReal(loc_minX, loc_maxX)
        set loc_Y = GetRandomReal(loc_minY, loc_maxY)
        set loc_effect = AddSpecialEffect(ModelPathArray[GetRandomInt(0,ModelPathArrayCount-1)], loc_X, loc_Y)
        call BlzSetSpecialEffectYaw(loc_effect,GetRandomReal(0, 2*bj_PI))
        call BlzSetSpecialEffectScale(loc_effect,GetRandomReal(0.8, 1.2))
        set i = i + 1
        exitwhen(i == 200)
    endloop
endfunction


// ===== Инициализция =====
function InitTrig_LandscapeLib takes nothing returns nothing
    local trigger loc_trg_LandscapeGen
    local integer i
    //Потоки текстур рельефа
    set i = 0
    loop 
        //Число потоков
        exitwhen(i == 5)
        set loc_trg_LandscapeGen = CreateTrigger()
        call TriggerRegisterTimerEventSingle( loc_trg_LandscapeGen, 0.01 )
        call TriggerAddAction( loc_trg_LandscapeGen, function LandscapeGen )
        set i = i + 1
    endloop
    //Потоки деформации рельефа
    set i = 0
    loop 
        //Число потоков
        exitwhen(i == 5)
        set loc_trg_LandscapeGen = CreateTrigger()
        call TriggerRegisterTimerEventSingle( loc_trg_LandscapeGen, 0.01 )
        call TriggerAddAction( loc_trg_LandscapeGen, function TerrianDeformGen )
        set i = i + 1
    endloop
    //Потоки создания окружения
    set i = 0
    loop
        //Число потоков
        exitwhen(i == 1)
        set loc_trg_LandscapeGen = CreateTrigger()
        call TriggerRegisterTimerEventSingle( loc_trg_LandscapeGen, 0.50 )
        call TriggerAddAction( loc_trg_LandscapeGen, function CreateEnviroment )
        set i = i + 1
    endloop
endfunction

endlibrary

Заключение

В целом алгоритм закончен, однако можно вносить ряд улучшений на вкус каждого автора. Выражаю благодарность своему соавтору Storsmwind за возникновение подобной задачи. Буду рад, если кто-то знает похожие реализации, ими поделиться в комментариях. Также при возникновении вопросов или замечаний прошу в комментарии. Тестовую карту можно скачать из вложения.
`
ОЖИДАНИЕ РЕКЛАМЫ...
2
29
1 год назад
2
Неплохо бы посмотреть на скрины результата генерации.
0
9
1 год назад
0
Неплохо бы посмотреть на скрины результата генерации.
Добавил картинки, хорошее замечание.
0
29
1 год назад
0
Koladik, чем-то на Noise похож.
Загруженные файлы
0
9
1 год назад
0
Koladik, чем-то на Noise похож
Спасибо! Посмотрю детально эту реализацию. Я решил не называть свой алгоритм шумом Перлина, как тут, ибо всё-таки генерация отличается от классики. Но, скорее всего, тут больше сходств чем отличий по части генерации текстур с этим алгоритмом. Жаль тут разработчик не дал детального описания нюансов.
0
29
1 год назад
0
Koladik, как это не дал? Там есть ссылка на блог Перлина.
1
23
1 год назад
1
Хорошая система, взял и адаптировал для своего проекта, большое спасибо!
0
27
1 месяц назад
0
Классная наработка 😍
Чтобы оставить комментарий, пожалуйста, войдите на сайт.