• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Dynamiczne renderowanie świata 2D - Projektowanie gier.

Object Storage Arubacloud
+2 głosów
826 wizyt
pytanie zadane 6 stycznia 2016 w C i C++ przez arek01996 Stary wyjadacz (12,100 p.)

Witam, Tworze sobie grę dokładnie Minecraft 2D próbuje zastosować tam techniki które użyję w przyszłości do rozwijanego wciąż silniczka na SFML. :) Ale Zmagam się z problemem generowania mapy w locie. Gdyż mapa będzie naprawdę olbrzymia generowana po kluczu SEED. To już zrobiłem ale teraz kwestia kolizji oraz rysowania.

Powiem wam jak to jak na razie u mnie wyglgąda.

Mam dwa kontenery w jednym wszystkie obiekty gry razem w drugim również wszyskie obiekty ale podzielone w  quadTree do kolizji, które swoją drogą nie działają najlepiej na granicach jednego drzewka z drugim, ale mniejsza o to.  Wiadomo marnotrawienie pamięci ale nic lepszego nie wymyśliłem.

Algorytmem PerlinNoice generuję jakąś wysokość i do wysokości minimalnej dodaję wygenerowaną z klucza wysokość daje to ładny efekt. I fajne mapy się robią
Następnie robię pętlę i,j i dodaję kolejno do obu kontenerów każdy obiekt.

Wygląda to mniej więcej tak:

PerlinNoise * noise = new PerlinNoise( SEED, CHUNK );

p = new Quadtree( 0.0f, 0.0f,(( worldSizeMin.x + worldSizeMax.x ) * 32 ),(( worldSizeMin.y + worldSizeMax.y ) * 32 ), 0, 3 );

float width = 32;
float height = 32;
long long int summator = 0;

for( int i = 0; i < worldSizeMax.x + worldSizeMin.x; i++ ) //columns (x values
{
    int columnHeight = 2 + noise->getNoise( i - worldSizeMin.x, worldSizeMax.y - worldSizeMin.y - 2 );
   
    mapIndex.push_back( sf::Vector3f( i,( worldSizeMin.y + columnHeight ), summator ) );
    summator = summator + worldSizeMin.y + columnHeight;
   
    for( int j = 0; j < worldSizeMin.y + columnHeight; j++ ) //rows (y values)
   
    {
        sf::Vector2f currentPosition( i * width,(( worldSizeMax.y + worldSizeMin.y ) - j ) * height );
       
        DirtGrass * dirtGrass = new DirtGrass( currentPosition, sf::Vector2f( width, height ) );
        p->AddObject( dirtGrass );
        worldContainer.push_back( dirtGrass );
    }
}

 


Jak widać mam jeszcze kontener o nazwie mapIndex dzięki któremu mogę sobie ograniczyć generowany obszar do rysowania:

Jak widać summator dodawany do mapIndex to ilość klocków które znajdują się przed elementem. jak dla x=0 tworzymy 7 klocków to dla x=1 sumator=7 ... a jeśli w x1 zrobimy już 10 kolejnych klocków to dla x2 sumator = 17 i tak dalej. tak więc wiemy że jak mamy rysować od np x1 do x10 to robimy zwykłą petlę w której od i=0 dodajemy summator gdyż wiadomo, że w kontenerze od i = 7 są już klocki z x1 (przynajmniej zakładamy że od 7 aż do końca wysokości zapisanej w mapIndex będą klocki z pozycją odpowiadającą x1).

Rysowanie wygląda jak na razie tak:

for( int i = cam.mX /* Okreslana wielkość ekranu względem kamery, gracz na pozycji np 1024 to wiadomo że trzeba rysować dla x=32 bo jeden klocek ma 32 px czyli 32*32 to 1024 */; i < cam.screenSizeX + cam.mX /* do początku dodajemy rozdzielczość ekranu dzielona przez 32 czyli jeden klocek*/; i++ )
{
    for( long long int j = abs( cam.mY ) - int( cam.screenSizeY ); j < abs( cam.mY ) + int( cam.screenSizeY ) / 2; j++ ) // Robimy to samo co wyżej ale określamy wartość rysowania w pionie.
    {
        WindowApp->draw( w1->cont[ w1->mapIndex[ i ].z + j ]->drawableObject );
    }
}

Mam nadzieję że kod jest dość jasny i że da się go ogarnąć. Tylko takie rozwiązanie ma jednak wadę. Jak będziemy chcieli usunąć albo dodać jakiś klocek to będziemy musieli znaleźć go w mapIndex i potem dla każdego x zmniejszyć wartość summatora o jeden gdyż liczba klocków zmniejszyła się.
O usuwaniu z quadTree i z vektora przechowującego wszystkie obiekty ze wszystkimi klockami nie wspomnę.
Dodawanie byłoby jeszcze gorsze gdyż trzeba by go dodać w odpowiednim miejscu w kontenerze aby się nie posypało. I zwiększyć każdy summator w przód.

Druga sprawa jak chce mieć "nieskończony" świat to przecież nie mogę wszystkiego trzymać w kontenerze myślałem żeby generować tylko trochę ponad rozdzielczość ekranu i na bieżąco usuwać (jak gracz idzie w prawo) obiekty po lewej i generować nowe po prawej i analogicznie odwrotnie jakby gracz szedł w drugą stronę. i zrobić dodatkowy kontener dla zmian jaki gracz wprowadził czyli np idę w lewo generuję np 10 klocków ale w kontenerze ze zmianami jest zapisane że na pozycji x,y klocek został usunięty więc go pomijam przy generowaniu... Fajna opcja do zapisu gry by to była.

Czy to co napisałem ma jakikolwiek sens?

3 odpowiedzi

+2 głosów
odpowiedź 6 stycznia 2016 przez niezalogowany
wybrane 7 stycznia 2016 przez arek01996
 
Najlepsza
O ile rozumiem to generujesz świat za każdym razem przy użyciu seeda (przy każdym poruszeniu itd, tak samo przy ponownym wczytaniu mapy). W Minecrafcie (i ogólnie przy takim generowaniu mapy) dany fragment mapy generowany jest tylko raz i później zapisywany do pliku (level podzielony jest na chunki).

Do samej pamięci są wczytywane tylko potrzebne chunki (tzn chunk gracza + te blisko gracza, jeżeli gracz zbliża się do miejsca gdzie nie ma wygenerowanego chunka to gra dopiero teraz go generuje na podstawie seeda), reszta czeka sobie w pliku (odtwarzanie mapy z seeda + nanoszenie zmian zrobionych przez gracza zużywa więcej zasobów niż wczytanie odpowiedniego fragmentu mapy z pliku).

Odsyłam cię na forum warsztat.gd gdzie swego czasu była prowadzona dyskusja na dokładnie te temat.

(nie jestem pewien czy to dokładnie ten link o który mi chodziło) http://forum.warsztat.gd/index.php?topic=29857.0
komentarz 8 stycznia 2016 przez niezalogowany

Z chęcią zobaczę efekty, wiem że kiedyś zrobię z tego konceptu kolizji poradnik ( http://szymonsiarkiewicz.pl/ ), skoro już się pobawiłem żeby takie coś wymyślić (na serio, uwielbiam tego typu problemu). Mail: kontakt@szymonsiarkiewicz.pl

Jak słusznie zauważyłeś, to jest głównie teoria i jej siła opiera się na tym jak dobry będzie algorytm to wykrywania bloków sąsiadujących z blokami bez kolizji (czy to powietrze, czy jakieś pochodnie etc), z racji że mapa jest generowana runtime to nie jesteś w stanie wcześniej 'upiec' poziomu (pomyślę nad tym, bo sam problem jest ciekawy, czuję że można jakoś użyć w tym celu speparowany DFS, ale nie wiem czy to ma sens).

Co do samych chunków, to nie mam pojęcia, nie zdarzyło mi się pisać gry tego typu. Zauważ, że nie musisz renderować całych chunków, możesz renderować tylko ich fragmenty.

Wracając do tematu kolizji, to kolizje nie o tyle co są "na powierzchni", ale wszystkie te które: sąsiadują z blokami powietrza i innymi niekolidującymi + koło bloków dynamicznych, co powinno załatwić sprawę także pod ziemią (łącznie z sytuacjami gdybyś miał źródło wody otoczone skałą (woda to obiekt dynamiczny, więc tam generowana by była kolizja).

W sumie jak się teraz zastanowić to możesz nieco odwrócić kolejność w przedstawionym przeze mnie procesie i zacząć od ustalenia przy jakich blokach znajdują się obiekty dynamiczne (te magiczne aury powinny być nieco większe niż na rysunku, tzn promień to z kilka bloków), a na podstawie tylko tych bloków możesz wygenerować odpowiednie drzewo quad-tree (później jeżeli nie zapomnę wstawię rysunek).

komentarz 8 stycznia 2016 przez arek01996 Stary wyjadacz (12,100 p.)
Hmm a czy jest sens tworzenie drzewek quadTree dla ok 50 elementów ?
komentarz 9 stycznia 2016 przez niezalogowany
Jak mówiłem: sposób jest czysto teoretyczny w dodatku niemalże pisany "na kolanie", drzewo będzie musiało być większe niż przedstawiałem to tutaj (jakby nie było, inaczej by wymagało zbyt częstego edytowania go), koncepcja jedynie pokazuje jak bardzo można zmniejszyć wielkość drzewa w najbardziej ekstremalny sposób.

O ile pierwsza część algorytmu ma sens (o zaniedbaniu colliderów obiektów znajdujących się pod innymi colliderami, tzn żaden inny obiekt dynamiczny nie może i tak wywołać z nimi kolizji), to część dotycząca generowania drzewa tylko w promieniu dynamicznych obiektów już ma dużo mniejszy. Ale spróbujmy podejść do problemu jeszcze inaczej,

Materia jest dość złożona (i nie ukrywam że wymaga jeszcze sporego przemyślenia, nie jestem ekspertem w tej dziedzinie) więc stosując spore uproszczenia, załóżmy że pod uwagę (kolizji, renderowania) bierzemy wyłącznie obiekty znajdujące się w promieniu R od gracza (na ten moment darujmy sobie chunki, generowanie mapy runtime przy odkrywaniu nowych terenów), skupmy się wyłącznie na sposobie wygenerowania drzewa.

To co chcemy osiągnąć to wygenerować je stosunkowo małym kosztem, tzn nie chcemy zrobić tego liniowo, chcemy zacząć tylko w pewnych miejscach i uzupełniać quad-tree w miarę potrzeb.

Można założyć, że DO będą punktami wejścia do generowania drzewa, o ile to jest dość proste tak trzeba dobrze przemyśleć samo aktualizowanie drzewa w momencie gdy gracz będzie się przemieszczał: na jakiej zasadzie będą obiekty dodawane do drzewa i usuwane.
komentarz 16 stycznia 2016 przez arek01996 Stary wyjadacz (12,100 p.)
Witam, Ostatnimi czasy trochę pisałem ( co prawda e.14, który był odrobinę mi plany pokrzyżował) napisałem strukturę silnika, tworzenie mapy do pliku w appdata, i jej odczyt wraz ze wszystkimi parametrami, teraz się bawię odrobinę w kompresję, ale to nie jest na dzień dzisiejszy istotne. W każdym razie wczytany chunk zawiera wszystkie statyczne obiekty z danego przedziału. Tj getChunkAt(x, y) i odpowiedni chunk zostaje jak narazie wygenerowany z pliku. Raczej niemożliwe jest wczytanie wszystkich chunków do pamięci RAM, przy tak ogromnym świecie sama mapa waży bez kompresji ok 100 mb zapisana w pliku. Myślałem żeby tak do 2 gb ramu zapełnić aby przy wczytywaniu jakoś strasznie długo się nie czekało oraz zmierzyć czas wczytywania chunku dla np jakiegoś Core 2 Duo i na tej podstawie określić kiedy się mają nowe chunki generować w nowym wątku do pamięci ram. Tj ile czasu zajmie graczowi dojście do końca mapy w RAM, a ile zajmie mu wczytanie do RAM nowego terenu z pliku. Tak aby zanim gracz doszedł chunk byłby już wygenerowany. Drugą sprawą jest rysowanie w SFML. Jeden chunk zawiera ok 10 000 tyś obiektów. Rysowanie wszystkich to spadek wydajności olbrzymi... ale wiadomo nie trzeba rysować tych których się nie widzi. Zatem zrobiłem w pętli w której rysuję warunek, w którym podaje pozycje gracza i jeśli jest w polu widzenia to rysuje,  a jeśli nie to nie... Czy istnieje może inny sposób na obsługę rysowania? Bo pętla i tak się te 10 000 razy musi wykonać. Zmniejszenie chunka nic nie da bo wszystko jest zależne od rozdzielczości w jakiej gracz gra.
komentarz 16 stycznia 2016 przez niezalogowany
Doszedłeś do momentu gdzie trzeba by się było dość mocno zastanowić, a niestety dla odmiany teraz ja mam sesję na głowie. Możesz ustalić stałą wartość pola widzenie przy użyciu sf::View i skalowania obiektów.
0 głosów
odpowiedź 23 stycznia 2016 przez arek01996 Stary wyjadacz (12,100 p.)

Projekt realizuję mniej więcej zgodnie z tym co ustaliliśmy poniżej.

Tutaj Quadtree z kolidującymi obiektami :)

0 głosów
odpowiedź 25 stycznia 2016 przez Kaze47 Obywatel (1,700 p.)

Sam będę się borykał z podobnym problem bo chcę zrobić coś identycznego. Aktualnie jestem na etapie samego generowania świata.

A teoretycznie odnośnie rysowania.

Wczytujesz każdy chunk tylko raz podczas odczytu. Informacje o chunkach zapisujesz w oddzielnych vektorach.

Informacje o nich zmieniasz dopiero gdy zachodzi na nich jakaś interakcja (np. postać rozbija ziemie), nie całą mapę tylko konkretny chunk.

Przykładowo poniżej id vektorów z zapisanymi chunkami które są wczytane w pamięci

2 3 4 - id vektora

4 5 6 7 8 - id chunka z którego informacje znajdują się w vektorach

ten na niebiesko nr 2 jest wyświetlony na ekranie i postac na nim stoi, idąc w prawo wchodzi na nr 3. W tym momencie usuwasz informacje o vektorach 0 bo jesteś odpowiednio daleko, a w to miejsce wpisujesz informacje o kolejnym chunku do którego się zbliżasz. Następnie wyświetlasz wszystko w następującej kolejności:

2 4 0

5 6 7 8 9

Nie wiem czy to zrozumiałe co przedstawiłem ale może trochę Ci to pomoże :)

Podobne pytania

0 głosów
2 odpowiedzi 583 wizyt
0 głosów
4 odpowiedzi 797 wizyt
pytanie zadane 15 czerwca 2019 w Systemy operacyjne, programy przez niezalogowany
0 głosów
1 odpowiedź 314 wizyt
pytanie zadane 18 maja 2019 w C i C++ przez niezalogowany

92,576 zapytań

141,426 odpowiedzi

319,652 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...