Odsłona trzecia- ostatnia.
W świecie 2D pojawił się ruch ZIELONYCH i zakazał chodzenia po leśnym runie. Zbudowano drogi… Nasz bohater może tylko chodzić po tych drogach. Zostaje nam go nauczyć tej sztuki
Tak więc stanęliśmy na kolejnym i ostatnim problemie jaki zapowiedziałem w części pierwszej. Oczywiście nie zamyka to problemu wędrowania w świecie 2D ale zamyka podstawowy problem tego cyklu artykułów- zmianę koloru duszka bohatera stojącego za przeszkodą.
Wprowadzamy sieć dróg
W naszym świecie drogi będziemy pamiętać w odpowiedniej tablicy. Tablicy warstwy 0 (zerowej)- podstawowej. W części drugiej jest to ta tablica mapaWarstwa0:array[0..19,0..19], zapisana jako stała w swiatUnit2.pas i wypełniona samymi zerami. Wartość zero odpowiadała indeksowi obrazu-klatki trawy, która jest przechowywany w OmgeaImageList1. W omawianych przykładach posłużyłem się prostym podejściem. W przyjętym modelu założyłem, że:
- klatka gruntu ma wymiar 64×32
- drogi będą nakładane w warstwie 0 (zero)
- obraz całego gruntu (trawa, drogi) jest zawarty w jednym pliku graficznym załadowanym do OmegaImageList- stąd też klatki dróg muszą być tego samego wymiaru co klatka gruntu (64×32)
W praktyce realizacja podpunktu c przedstawiona jest na poniższym zrzucie ekranu.
Ważne aby we właściwościach TileWidth i TileHeight podać wymiary klatki. Pozwoli to wycinać interesujące nas klatki z obrazu gruntu. Zliczanie klatek przebiega wierszami od lewej do prawej.
W kodzie programu robimy to w momencie tworzenia warstwy
1 2 3 4 5 |
//wartswa gruntu .. Warstwa0[w,k]:=TKostka.Create(OmegaSprite1,OmegaImageList1.ImageList.Items[0],MapaWarstwa0[w,k]); .. |
Tu podam odpowiednie indeksy klatek, które są zapisane w tablicy MapaWarstwa0.
//******ODPOWIEDNIKI INDEKSOW***********
0- TRAWA
1- SKRZYZOWANIE DROG
2- DROGA W POZIOMIE
3- DROGA W PIONIE
4- DROGA ZAKRET W LEWO GORNY
5- DROGA ZAKRET W PRAWO GORNY
6- DROGA ZAKRET W LEWO DOLNY
7- DROGA ZAKRET W PRAWO DOLNY
//*********************
W części drugiej wprowadziliśmy dodatkową tablicę do przechowywania występowania pni drzew. W tej części można z tej tablicy zrezygnować(bo zakładamy, że nasz duszek chodzi tylko po określonych kostkach siatki mapy). Ja jednak zostawię wspomnianą tablicę. Można się pokusić aby wprowadzić do lasu polany (ciemniejszą lub jaśniejszą trawę), na które będzie mógł bohater spokojnie wejść. Taka tablica, może również przechowywać informacje o podstawie budynku jaki stoi w świecie 2D (chodzenie za budynkiem). Zastosowanie może być różne.
Aby nauczyć naszego bohatera chodzenia po drogach wystarczy teraz odczytać wartość indeksu przechowywanej klatki w polu w jakim znajduje się gracz. Załatwimy to tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
procedure TGracz.MoznaIsc; begin //Sterowanie Graczem stareX:=x; stareY:=y; if oisUp in Form1.OmegaInput1.keyboard.states then//idz do gory SkokY:=SkokY+1; if oisDown in Form1.OmegaInput1.keyboard.states then//idz w dół SkokY:=SkokY-1; if oisRight in Form1.OmegaInput1.keyboard.states then//idz w prawo SkokX:=SkokX-1; if oisLeft in Form1.OmegaInput1.keyboard.states then//idz w lewo SkokX:=SkokX+1; x:=x-SkokX; y:=y-SkokY; JakaKostka; //sprwdz czy mona byc w nowej pozycji jak nie to zeruj skok if(Drogi[wd,kd]>-1)or(MapaWarstwa0[w,k]=0)//mozna ten warunek tak zorganizowac aby dac wszystko do //tablicy Drogi, dla części trzeciej właściwe by było: if MapaWarstwa0[w,k]=0 then begin SkokX:=0; SkokY:=0; end; //ustwa gracza w starej pozycji x:=stareX; y:=stareY; end; |
W tym miejscu chciałbym powiedzieć jak sprawdzam czy można iść. Zapamiętuję stare położenie gracza. Zwiększam skok przesuwu mapy, ale jej nie przesuwam. Przesuwam gracza (bez ruchu obrazu gracza) o zadaną wartość(ze znakiem minus w stosunku do skoku mapy świata), sprawdzam czy może być w nowym miejscu. Jeśli tak, to nie zeruję skoku mapy, w przeciwnym wypadku zeruję. Uwaga! zawsze ustawiam gracza na poprzedniej pozycji. Ponieważ przesuwamy światem a nie duszkiem. Duszek bohatera zawsze jest w tych samych współrzędnych liczonych względem górnego lewego rogu monitora.
Tyle nowości w części trzeciej.
Pisząc treść części pierwszej obiecałem wspomnieć o sugestiach związanych z przyspieszeniem działania kodu gry jeżeli sprawdzamy test kolizji używając procedury onCollision.Ta procedura znacznie spowalnia działanie kodu gry jeśli mamy kilkanaście lub więcej obiektów z tak ustawionym testem kolizji. Aby przyspieszyć działanie takiego rozwiązania można ustawiać lub zwalniać test kolizji dla ograniczonego obszaru. Na przykład problem strzelania. Duża liczba pocisków, dla których sprawdzamy kolizję z określonym typem obiektów, które przebywają w klatce obecności pocisku. Najwygodniej taką zmianę umieścić w procedurze Move konkretnej klasy obiektu. Poniżej przedstawiam najprostsze rozwiązanie, które w omawianych przykładach jest “wycięte” z kodu (ujęte w nawiasach klamrowych). Rozwiązanie to ustawia test kolizji dla drzewa, które znajduje się w tej samej kolumnie co gracz i w wierszu o różnicy 0,1,2 względem wiersza przebywania gracza.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
procedure TRoslina.Move(const MoveCount:single); begin inherited Move(MoveCount); //przykład prostej metody usuwania testu kolizji {if (k=TGracz(Gracz).k) and (w-TGracz(Gracz).w<=2) then DoCollision:=true else DoCollision:=false;} end; |
Podsumowując
Przedstawione pomysły mogą być wyjściem do tworzenia czegoś w rodzaju obszarów terenu. Otóż FPS wzrasta jeśli operujemy dużymi klatkami gruntu siatki mapy (powiedzmy 128×64 lub 256×128). Mamy mniej do wyświetlenia “kostek”. Taką kostkę można sobie podzielić na mniejsze obszary (załóżmy 32×32) dla których to będziemy losować na przykład występowanie drzew. Inaczej mówiąc w jednej kostce 128×64 możemy posadzić drzew: (128 div 32)*(64 div 32)=4*2=8 drzew. Czyli uzyskujemy gęsty las. Wprowadzając odpowiedni współczynnik zalesienia możemy regulować gęstość obiektów żyjących na jednej kostce gruntu (w omawianych przykładach losowałem jedno drzewo na środku kostki gruntu). To rozwiązanie może posłużyć i do budynków jak i dróg czy też śladów jakie pozostawił po sobie przejeżdżający pojazd (powiedzmy ślady takie to TSprite żyjący 3- 5 minut). Zastosowań może być wiele. Na pewno nasz kod będzie dużo szybszy niż byśmy podzielili świat na kostki 32×32. Stracimy dużo pamięci na grunt i wymusimy więcej operacji graficznych. Oczywiście nie są to jedyne pomysły na przyspieszenie wykonywania kodu gry. Nie chcę tu niczego sugerować. Najlepszą metodą jest stworzenie wstępnego modelu naszego świata i sprawdzanie gdzie można przyspieszyć nasz kod. Tworzenie jak najmniejszej liczby pętli, stosowanie wskaźników itp. Kod omawiany w tych częściach tego nie zawiera, nie jest to bowiem główny cel tego tematu.
Koniec części trzeciej i ostatniej
Pozdrawiam i życzę jak najlepszych własnych rozwiązań
Oksal
Autor: oksal