Ono, veľa hier predtým aj potom boli koncipované tak, že vám túto prácu akoby uľahčovali. Mohli ste upravovať terén "brush, štetcom", kedy ste upravovali nie 1 tile, štvorec na mape, ale skupinu napr. 5x5 tilov, štvorčekov na mape. Nebolo to zlé, ale problém bol v tom, že už to stratilo akúsi "poctivosť". To je to správne slovo. Tie počítačové hry po roku 1997 s nástupom DirectX už akoby stratili "poctivosť". Píšem to v úvodzovkách pretože, tie modernejšie hry boli určite poctivé, čo sa týka grafického spracovania a niektoré aj herného spracovania, ale nedosahovali akúsi hernú poctivosť. Neboli to úplne všetky, samozrejme, napr. hra Settlers, Age of Empires, SimCity, Civilization, ale aj Theme Hospital, The Sims a podobne, mali svoje herné vymoženosti, ale hra Transport Tycoon bola špeciálna.
A tak sa stalo to, že hráč paradoxne nepotreboval realizovať nejaké terénne úpravy, resp. minimálne. Lenže v hre Transport Tycoon potrebujete realizovať priame úpravy terénu, pretože terén je realizovaný akoby "jednotkovo", tiež atomicky. Na určitý typ prevýšenia nie je možné postaviť nejaký herný objekt, budovu, to znamená, že je nutné revidovať takýto terén.
Teda, zaujímavým prvkom hry bola teda možnosť realizovať výstavbu terénu. Takže obsahovala možnosť editácie terénu, mapy. Nie je to nič výnimočné, ale na vtedajšie hry nebolo tak veľa hier, ktoré by ponúkali možnosť upravovať terén počas hrania samotnej hry. Na druhej strane, to, že mnohé hry ponúkali možnosť upravovať terén počas hrania hry, nebolo natoľko zaujímavé, pretože úprava terénu vo väčšom merítku než 1x1 už nemala taký zmysel. Pretože jednotlivé prevýšenia medzi dvoma úrovňami terénu neboli natoľko nežiadúce, aby ich hráč musel redizajnovať, meniť a to z toho dôvodu, že nejaké herné objekty bolo možné aplikovať aj na terén, ktorý nemal veľké prevýšenie.
Jednotlivé "štvorčeky, tile" sú dimenzované špecificky. Ako vidieť na obázku, každý typ štvorčeka má svoj význam, ale určitý typ štvorčeka nie je možné postaviť nejaký herný objekt. Lenže, ak by ste mali terén, kde by bola skupina štvorčekov rovnakého charakteru, tak nemá zmysel upravovať dané plôšky. Len tie, ktoré by mali veľmi veľké terénne prevýšenie.
Otázka je, ako naprogramovať takýto typ terénu. Ono, mohlo by sa zdať, že samotný autor hry Transport Tycoon realizoval hru v 3D prevedení. Avšak nie je to pravda. Samotná hra bola realizovaná v 2D prevedení s tým, že jednotlivé štvorčeky boli koncipované ako 3D a teda navonok to vyzeralo ako 3D izometrická hra. Áno, vyzerá to ako šialenstvo, generovať toľko 2D štvorčekov, ale v skutočnosti to takto bolo realizované. Programovať to takýmto spôsobom v dnešnej modernej dobe je tzv. "obsolete", zastarané. Aj keď pri dnešných "javascriptových" jazykoch by to nemuselo byť zlé riešenie, v každom prípade nie je až také veľmi náročné to realizovať v 3D prevedení.
Od čias, keď som objavil hru Transport Tycoon som chcel naprogramovať vlastnú hru, typologicky podobnú tejto hre. Skúšal som viackrát naprogramovať základný terén v C/C++ OpenGL, neskôr v C/C++ DirectX, ale zakaždým som narazil na rovnaký problém. Nevedel som realizovať prepočet trojuholníkov tak, aby jednotlivé trojuholníky boli koncipované "dobre, správne". Ono, bolo by možno zadefinovať exaktne, matematicky, čo je to "správne", ale vysvetlím to na jednoduchom príklade.
Čierna sieť reprezentuje klasickú 3D polygonálnu sieť, kde je možné vidieť, že jednotlivé trojuholníky sú dimenzované všetky v jednom smere. Mohli by byť aj v opačnom smere, ale tiež všetky jednostranne. Ak by ste na takomto teréne klikli myšou na vertex a vyvýšili by ste jeho polohu o nejakú vzdialenosť (v zvislom smere, kolmom na všeobecnú rovinu, nie na danú aktuálnu plôšku), tak jednotlivé okolité plôšky by sa modifikovali určitým spôsobom, ako je zobrazené na obrázku. Teda, neboli by realizované akýmsi "uzavretým" spôsobom, teda takým ako je možné vidieť na priložených obrázkoch priamo z hry Transport Tycoon. Takže je nutné vykonať zmenu v polohe uhlopriečky daných štvorcov.
Zelenou farbou je vyznačená zmena v polohovaní uhlopriečok tak, že každá uhlopriečka je kolmá na daný kliknutý vertex. Podobný efekt je možné realizovať tak, že uhlopriečka bude dimenzovaná v 45 stupňovom smere oproti vertexu, na ktorý sme klikli myšou. Transport Tycoon používa spôsob vykreslenia, ktorá je indikovaná zelenou farbou, ale napr. hra SimCity používa spôsob vykreslenia modrou farbou.
Ako to naprogramovať ?
Osobne som dlho nad tým premýšľal od roku 1997, aj keď nie každý deň, napadli ma rôzne riešenia, ale vo výsledku som nevedel ako to realizovať v prostrední DirectX/OpenGL, teda takom grafickom prostredí, kde musíte uvažovať nad 1 štvorcom tak, že tento štvorec (Face) je dimenzovaný ako 2 trojuholníky. Toto je prapodstata podstaty moderných grafických vykresľovacích štandardov. Zo začiatku som si myslel, že riešenie je úplne jednoduché v tom duchu a zmysle, že nemusíme uvažovať nad uhlopriečkami. Že teda, grafická reprezentácia 1 face bude korelovať "tak ako chceme" a zobrazí sa terén v "správnom rozpoložení".
Prečo som si to dlho myslel ?
Pretože DirectX/OpenGL poskytoval na vykreslenie grafického objektu v 3D scéne možnosť tvorby pomocou vertex a vertex buffera. Primárne poskytoval takýto prístup, ale sekundárne poskytoval aj prístup pomocou tzv. indices, alebo tiež index buffer, teda "tie uhlopriečky". Na to, aby ste vykreslili nejaký model v 3D scéne, nemusíte tento model realizovať na báze "indices". Grafický systém dokáže "pospájať" tieto vertexy, vrcholy nejakým určeným spôsobom, ktorý obvykle používa asi 7 typov spojenia (TriangleList, TriangleStrip, LineStrip, a podobne). Každý z týchto spojení je nejakým spôsobom špecifický, ale určite nerealizuje to, že to akoby "vykreslí správne". Je otázka, čo je správne a nesprávne, ale v každom prípade, dané pospájanie nebude korelovať s "tycoon terrain" spojením. Preto som si myslel, že stačí len definovať vertexy a ono sa to nejako samo od seba pospája správne. Skúšal som naprogramovať aj toto, teda len vyvýšenie vertexu, ale vzhľadom na vtedajšie nižšie programátorské znalosti sa mi to nepodarilo naprogramovať. Nie, že by to bolo ťažké, ale nepristupoval som k tomu správne. Predpokladal som, že grafický systém DirectX/OpenGL vykonáva určité grafické operácie automaticky. Konkrétne mám na mysli, update posunutého vertexu. Myslel som si, že ak zmením pozíciu vertexu, tak grafický systém danú zmenu vykoná bez toho, aby som to musel ešte dodatočne explicitne dodefinovať. Lenže štandardne sa takéto operácie nevykonávajú implicitne.
Nejaký čas som to nechal tak, až napokon sa objavil grafický editor Unity3D. V tomto to bolo možné naprogramovať jednoduchšie, resp. mali ste bližší kontakt s Editorom, takže programovanie nebolo také ťažkopádne. Takže som si mohol vyskúšať v reálnom čase aj rôzne chybové stavy v dohľadnom čase.
Stále som nevedel prísť na to, ako dispozične otočiť uhlopriečku tak, aby to korešpondovalo s okolitým prostredním. Napadlo ma, rozdeliť mapu na štvorce s diagonálnymi uhlopriečkami. Vyzeralo to ako správne riešenie, avšak nefungovalo to v každom smere, samozrejme. Vyskúšal som rozdeliť mapu na iné diagonálne rozpoloženie, pootočené o 45 stupňov, ale ani toto nefungovalo správne. Vždy to bolo o tom, že ste museli "ešte doupraviť" určité uhlopriečky, aby to korešpondovalo. Takže vás napadlo, že teda musíte naprogramovať algoritmus taký, ktorý by "to kontroloval", či je to správne. A napísať ten kontrolný mechanizmus bolo relatívne komplikované.
Autori to riešia tak, že kontrolujú dané "správne" pootočenie uhlopriečky. Rozumiem prečo to vykonávajú, ale časom som zistil, že takáto kontrola nie je nutná. Ako ma napadlo vykonať to, že z každého vertexu budeme smerovať práve jednu hranu do každého protiľahlého vertexu ?
Povedal som si, že vždy, keď sa zdvihne terén pomocou vertexu, tak vždy musí byť zabezpečený na vôkol tohto vertexu určité rozpoloženie také, ktoré bude v okolí vertexu sústredené okolo daného vertexu centrovaným spôsobom, alebo tiež veárovitým spôsobom. To, aký dôsledok bude po stranách nebude podstatné, ale dôležité bude, že v danom okolí to VŽDY bude musieť byť v takejto konštelácii. Povedal som si, že OK, ale určite to nebude korešpodovať s okolitými vertexmi a teda som na začiatku túto vísiu zavrhol. Až potom som si povedal, pri obedovaní obedu, kde som videl obrus so štvorčekovým vzorom, že predsa by to mohlo byť správne riešenie. Predstavil som si vo vzore na obruse ako som zdvihol 1 vertex a dispozične upravil uhlopriečky do hviezdy. A potom som si predstavil, čo sa stane, ak zdvihnem druhý vertex vedľa tohto a vykonám rovnakú dispozičnú zmenu, že teda z daného vertexu vyšlem 4 hrany do protiľahlých vertexov. Tam som si všimol prvýkrát, že na 2 face, spoločných pre tento vzniknutý terén, sa stane to, že zmenou uhlopriečky sa nič nestane !
Toto bol môj objav, heuréka !
Zaujímavé je to, že to bude fungovať v každom prípade. Že tým, že sa síce zmení uhlopriečka aj tam, kde "by sa nemala", sa nič nestane, pretože daná plôška bude mať stále platný vykresliteľný face.
Takže, keď som to realizoval v Unity3D, bez tejto úpravy, tak ako je možné vidieť, daný terén je v takom rozpoložení, že krajné uhlopriečky sú nesprávne situované. Programový kód je nasledovný:
Ak som následne vykonal zmenu uhlopriečok, stalo sa to, že daný terén sa vykreslil správne. mesh = GetComponent<MeshFilter>().mesh;
vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 0, 2), new Vector3(1, 0, 0),
new Vector3(1, 0.5f, 1), new Vector3(1, 0, 2), new Vector3(2, 0, 0), new Vector3(2, 0.5f, 1),
new Vector3(2, 0, 2), new Vector3(3, 0, 0), new Vector3(3, 0, 1), new Vector3(3, 0, 2) };
triangles = new int[] { 0,1,4,3,0,4,1,2,5,4,1,5,3,4,7,6,3,7,4,5,8,7,4,8,6,7,10,9,6,10,7,8,11,10,7,11 };
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
A daný programový kód: (GIT: https://github.com/indicatrix-git/tycoon-terrain-enhanced/blob/main/MyPrincipleTycoonTerrain.cs)
mesh = GetComponent<MeshFilter>().mesh;
vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 0, 2), new Vector3(1, 0, 0),
new Vector3(1, 0.5f, 1), new Vector3(1, 0, 2), new Vector3(2, 0, 0), new Vector3(2, 0.5f, 1),
new Vector3(2, 0, 2), new Vector3(3, 0, 0), new Vector3(3, 0, 1), new Vector3(3, 0, 2) };
{
int v = 4;
int v0 = v; //ten vertex, na ktory sme klikli mysou
int v1 = v + 1;
int v2 = v - 1;
int v3 = v + 3;
int v4 = v - 3;
int v5 = v + 4;
int v6 = v + 2;
int v7 = v - 4;
int v8 = v - 2;
triangles01 = new int[] { v0, v1, v5, v0, v5, v3, v0, v3, v6, v0, v6, v2, v0, v2, v7, v0, v7, v4, v0, v4, v8, v0, v8, v1 };
}
{
int v = 7;
int v0 = v; //ten vertex, na ktory sme klikli mysou
int v1 = v + 1;
int v2 = v - 1;
int v3 = v + 3;
int v4 = v - 3;
int v5 = v + 4;
int v6 = v + 2;
int v7 = v - 4;
int v8 = v - 2;
triangles02 = new int[] { v0, v1, v5, v0, v5, v3, v0, v3, v6, v0, v6, v2, v0, v2, v7, v0, v7, v4, v0, v4, v8, v0, v8, v1 };
}
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles01.Concat(triangles02).ToArray();
Virtuálne som predpokladal akože "kliknutie" na daný vertex, teda vertex číslo 4 a 7. Kvázi algoritmus nie je nijako špeciálny. Dané prírastky pre vertexy znamenajú to, že indikujú danú uhlopriečku. Index v0 je vždy ten, na ktorý sme klikli myšou, teda prvý index. V kóde je to napísané 2x, ale v skutočnosti je to pre každé kliknutie rovnaké, len s iným indexom vertexu.Donedávna som nevedel vyriešiť to, že medzi plôškami na obrázku s indexami vertexov 3, 6, 4, 7, 5, 8 sa dané hrany objavia 2x, teda ešte z predchádzajúcej úpravy vertexov. Daný grafický systém to beztak zobrazí správne, pretože na vykreslenie 1 face postačuje 1 platná uhlopriečka a každá ďalšia navyše sa síce vykreslí, ale na vykreslenie samotného Face to nemá nejaký negatívny vplyv.
Podarilo sa mi vyriešiť aj tento problém nadbytočnej hrany. Nemám to síce už naimplementované, pretože by to bolo analogické a vzhľadom na danú programovú korektnosť by to už bolo funkčné. Jedná sa o to, že danú hranu, alebo hrany by bolo možné zmazať tak, že z daného indices zoznamu by sa vyhľadala vždy najskôr tá hrana, ktorá by mala byť pridaná. Takže, algoritmus by fungoval tak, že najskôr by vymyzal hrany z daných 4 Face, okolo toho kliknutého v0 vertexu a potom by realizoval to, čo je popísané vyššie, že by teda vygeneroval 4 hrany do protiľahlých vertexov.
Ako zmazať nadbytočné hrany z indices ?
Ako je možné vidieť v programe, indices, alebo tiež triangle list spájam s ďalším zoznamom indices. Napriek tomu, že vykonávam túto operáciu, nemá to vplyv na samotné vykreslenie trojuholníkov. Z toho vyplýva, že nezáleží na poradí jednotlivých vertexov v rámci triangle zoznamu. Je nutné zachovať poradie generovania trojuholníkov v rámci súslednosti v smere hodinových ručičiek, ale potom už nezáleží na tom, či daný zoznam triangle bude alokovaný na začiatku celkového triangle zoznamu, alebo na jeho konci (alebo dokonca aj kdesi uprostred zoznamu, resp. v ľubovoľnom prvku v zozname). Keďže je toto takto definované, tak to znamená, že určite bude možné nájsť 1 triádu napr. (4, 5, 8) v celkovom zozname indices, triangle list. Myslel som si, že bude nemožné to násjť v zozname, pretože pre index 4 existuje minimálne 8 triád s číslom 4, ak zabezpečíme, že dané triády budeme generovať rovnakým spôsobom. Jedná sa o to, že dané triády bude vždy generované rovnakým spôsobom, podľa akejsi vnútornej dohody. Ak to takto bude, tak potom vždy nájdeme dané triády. Nikdy sa nestane, že ak bude nejaká triáda obsahovať číslo s indexom napr. 4, že to bude taká triáda, ktorú tam nenájdeme v zozname triangle list. Ak toto bude zachované, tak potom sa stane to, že po kliknutí na vertex v0, nájdeme 8 triád z jednej strany a 8 triád z druhej strany, pretože trojuholník može byť situovaný s uhlopriečkou v dvojakom rozpoložení. Tieto triády zmažeme a vygenerujeme 8 triád podľa "správneho" uváženia, teda, že z 1 vertexu bude vychádzať 1 hrana do vertexu protiľahlého vrchola.
Tento problém je možné naprogramovať aj v DirectX, čo som teda podľa tutoriálu naprogramoval s rovnakým výsledkom.
Programový kód pre DirectX je komplikovanejší v rámci toho, že musíte napísať obslúžne programové kódy pred samotným generovaním vertexov a indexov, teda vykreslením grafiky na frame buffer. Neuvediem úplne celý programový kód, pretože obslúžny programový kód je defacto takmer nemenný a striktne daný. Takže nasleduje len programový kód pre samotné vykreslenie vertexov a indexov.
std::array<Vertex, 12> vertices =
{
Vertex({ XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(0.0f, 0.0f, 1.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(0.0f, 0.0f, 2.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(1.0f, 0.0f, 0.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(1.0f, 0.5f, 1.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(1.0f, 0.0f, 2.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(2.0f, 0.0f, 0.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(2.0f, 0.5f, 1.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(2.0f, 0.0f, 2.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(3.0f, 0.0f, 0.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(3.0f, 0.0f, 1.0f), XMFLOAT4(Colors::Black) }),
Vertex({ XMFLOAT3(3.0f, 0.0f, 2.0f), XMFLOAT4(Colors::Black) })
};
std::array<std::uint16_t, 48> indices;
std::array<std::uint16_t, 24> indices01;
std::array<std::uint16_t, 24> indices02;
{
uint16_t v = 4;
uint16_t v0 = v; //ten vertex, na ktory sme klikli mysou
uint16_t v1 = v + 1;
uint16_t v2 = v - 1;
uint16_t v3 = v + 3;
uint16_t v4 = v - 3;
uint16_t v5 = v + 4;
uint16_t v6 = v + 2;
uint16_t v7 = v - 4;
uint16_t v8 = v - 2;
indices01 =
{
v0, v1, v5, v0, v5, v3, v0, v3, v6, v0, v6, v2, v0, v2, v7, v0, v7, v4, v0, v4, v8, v0, v8, v1
};
}
{
uint16_t v = 7;
uint16_t v0 = v; //ten vertex, na ktory sme klikli mysou
uint16_t v1 = v + 1;
uint16_t v2 = v - 1;
uint16_t v3 = v + 3;
uint16_t v4 = v - 3;
uint16_t v5 = v + 4;
uint16_t v6 = v + 2;
uint16_t v7 = v - 4;
uint16_t v8 = v - 2;
indices02 =
{
v0, v1, v5, v0, v5, v3, v0, v3, v6, v0, v6, v2, v0, v2, v7, v0, v7, v4, v0, v4, v8, v0, v8, v1
};
}
for (int i = 0; i < 24; i++)
{
indices[i] = indices01[i];
}
for (int i = 0; i < 24; i++)
{
indices[24 + i] = indices02[i];
}
const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
mBoxGeo = std::make_unique<MeshGeometry>();
mBoxGeo->Name = "boxGeo";
ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));
CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
mBoxGeo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);
mBoxGeo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);
mBoxGeo->VertexByteStride = sizeof(Vertex);
mBoxGeo->VertexBufferByteSize = vbByteSize;
mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
mBoxGeo->IndexBufferByteSize = ibByteSize;
SubmeshGeometry submesh;
submesh.IndexCount = (UINT)indices.size();
submesh.StartIndexLocation = 0;
submesh.BaseVertexLocation = 0;
mBoxGeo->DrawArgs["box"] = submesh;
Bol to upravený kód pre vykreslenie Kocky (Box). Ale ako je možné vidieť, programový kód pre vykreslenie samotného terénu je veľmi podobný ako pre Unity.
Ako tento problém riešia iní autori ?
Predtým, než som toto vymyslel som nemal žiadne indície od niekoho. Síce dané algoritmy existovali a aj som si niektoré programové kódy pozrel, ale nerozumel som časti pre "kontrolu uhlopriečok". Z toho dôvodu som z daných programových kódov nemal prakticky žiadnu výpovednú hodnotu. Algoritmus tohto problému som nevedel a ani neviem nájsť, ale koreluje s už spomínaným algoritmom prezentovaným mnou na tomto blogu v rámci tzv. Diamond-Square algoritmom. Avšak nie je to tento algoritmus. Vyzerá to síce podobne, je tam "nejaká hviezda a štvorec", ale tento algoritmus nemá za cieľ generovať terén takýmto spôsobom, ale len v rámci daného Face rozložiť daný Face na typologicky Diamond-Square spôsob. Nič viac, nič menej. Predtým, než som vymyslel tento algoritmus, som nepoznal tento algoritmus. A aj keby som ho poznal, tak idea hviezdy v rámci Face by mi nepomohla, pretože jednak som to už dávnejšie vedel a jednak som to aj zavrhol ako "nesprávne riešenie".
Iní autori to riešia tak, že generujú tzv. height mapu, s určitými číslami a nad touto mapou aplikujú algoritmus, ktorý "pospája" dané uhlopriečky požadovaným spôsobom v tycoon štýle. Nie je na tom nič zlé, len je tam ten problém, že autori tohto algoritmu musia KONTROLOVAŤ vertexy a uhlopriečky, "či sú v správnom rozpoložení". Oni to napokon tiež majú realizované tak, že z daného vertexu bude vychádzať práve 1 hrana. Jedná sa o to, že kontrolujú úplne všetky vertexy a ešte nad to kontrolujú tie, ktoré sú náležite hodné.
Samozrejme môj algoritmus predpokladá, že daný terén už je daný a je na začiatku v správnom uhlopriečkovom rozpoložení. Ale to je jediná vstupná požiadavka. Potom sa vykonáva zmena vždy len po kliknutí na daný 1 vertex, bez akejsi ďalšej kontroly.
Programový kód od jedného z autorov je nasledovný (GIT: (https://github.com/mtt-git/tycoon-terrain)):
TerrainManager.cs
using UnityEngine;
public class terrainManager : MonoBehaviour {
public static terrainManager instance;
public int terrainWidth, elementWidth;
public terrainElement terrainPrefab;
[HideInInspector]
public Vector3[] coords;
[HideInInspector]
public terrainElement[] terrainElements;
// void OnDrawGizmosSelected()
// {
// foreach(Vector3 vec3 in coords)
// {
// Gizmos.color = Color.red;
// Gizmos.DrawSphere(vec3, .1f);
// }
// }
void Start()
{
instance = this;
CreateCoords();
CreateTerrainElements();
}
private void CreateCoords()
{
coords = new Vector3[(terrainWidth + 1) * (terrainWidth + 1)];
/*
every side needs to be 1 unity longer
*/
for (int i = 0, z = 0; z <= terrainWidth; z++)
{
//outer loop, z-axis
for (int x = 0; x <= terrainWidth; x++, i++)
{
/*
inner loop, x-axis
setting height value based on perlin noise
needs to be optimized low, mid and high frequency noise
*/
float y = Mathf.PerlinNoise((float)x / 20, (float)z / 20) *10;
y = Mathf.Floor(y) / 2;
coords[i] = new Vector3(x, y, z);
}
}
}
private void CreateTerrainElements()
{
int tilesPerSide = terrainWidth / elementWidth;
terrainElements = new terrainElement[tilesPerSide*tilesPerSide];
for (int i = 0, z = 0; z < tilesPerSide; z++)
{
//outer loop, z-axis
for (int x = 0; x < tilesPerSide; x++, i++)
{
terrainElement elementInstance = Instantiate(terrainPrefab, this.transform);
elementInstance.Initialize(x,z);
terrainElements[i] = elementInstance;
}
}
}
}
TerrainElement.cs
using UnityEngine;
public class terrainManager : MonoBehaviour {
public static terrainManager instance;
public int terrainWidth, elementWidth;
public terrainElement terrainPrefab;
[HideInInspector]
public Vector3[] coords;
[HideInInspector]
public terrainElement[] terrainElements;
// void OnDrawGizmosSelected()
// {
// foreach(Vector3 vec3 in coords)
// {
// Gizmos.color = Color.red;
// Gizmos.DrawSphere(vec3, .1f);
// }
// }
void Start()
{
instance = this;
CreateCoords();
CreateTerrainElements();
}
private void CreateCoords()
{
coords = new Vector3[(terrainWidth + 1) * (terrainWidth + 1)];
/*
every side needs to be 1 unity longer
*/
for (int i = 0, z = 0; z <= terrainWidth; z++)
{
//outer loop, z-axis
for (int x = 0; x <= terrainWidth; x++, i++)
{
/*
inner loop, x-axis
setting height value based on perlin noise
needs to be optimized low, mid and high frequency noise
*/
float y = Mathf.PerlinNoise((float)x / 20, (float)z / 20) *10;
y = Mathf.Floor(y) / 2;
coords[i] = new Vector3(x, y, z);
}
}
}
private void CreateTerrainElements()
{
int tilesPerSide = terrainWidth / elementWidth;
terrainElements = new terrainElement[tilesPerSide*tilesPerSide];
for (int i = 0, z = 0; z < tilesPerSide; z++)
{
//outer loop, z-axis
for (int x = 0; x < tilesPerSide; x++, i++)
{
terrainElement elementInstance = Instantiate(terrainPrefab, this.transform);
elementInstance.Initialize(x,z);
terrainElements[i] = elementInstance;
}
}
}
}
Následne vykreslenie danej scény pre tento programový kód v Unity bude vyzerať nasledovne.
Ako je možné vidieť výsledok je rovnaký. Takže takýmto spôsobom je možné realizovať vykreslenie terénu v štýle hry Transport Tycoon. Osobne som sa o to pokúšal veľmi dlhý čas, niečo málo cez 25 rokov a nikdy sa mi to nepodarilo v adekvátnom grafickom rozpoložení. Nikdy by som ani nebol povedal, že na to prídem v podstate úplne sám a defacto úplnou náhodou v tom zmysle, že revidujem to, čo ma kedysi už napadlo.
Až na tomto algoritme som si uvedomil, aké je dôležité veriť v svoju intuíciu a v sile myšlienky. Znie to ako klišé, tak lacno a jednoducho, ale skutočne až v tomto okamihu som si uvedomil, že veci nie sú až také zložité ako vyzerajú a že aj tie najťažšie problémy sú riešiteľné pomerne jednoduchým spôsobom :)
V nasledujúcom videu to popisujem podrobnejšie, alebo minimálne vizuálne prívetivejšie:











0 comments:
Post a Comment