Pobierz kod Giganty Świata 2D - część 1
GIGANTY ŚWIATA 2D część 1
O czym tym razem będzie? Ano o kolejnych przemyśleniach dotyczących światów 2D. GIGANTYCZNYCH ŚWIATÓW 2D.Tak ogromnych jak tylko pamięć komputera pozwoli i wola chęci oprogramowania pojedynczego poziomu do takiej gry.
Tu chcę podkreślić ze omawiany model będzie 3 warstwowy. Gdzie każda warstwa może być zbudowana z kafli 25x25 lub 2000x2000 sztuk a nawet więcej. Wymiary kafli od 32x32 pikseli do 256x256. Jak kto woli. Mój leciwy komp- Duron 800, 512 MB RAM-u radzi sobie z 3 warstwami 2500x2500 dla kafli 64x48 pikseli wyśmienicie, FPS ponad 120, czas budowy losowego świata około 10 sekund. Im większy wymiar kafla w pikselach tym FPS będzie lepszy, który i tak jest bardzo dobry, bo od 100FPS wzwyż.
Temat planuję omówić w 3 częściach
1. opis modelu pojedynczej kości (kafla) warstwy świata 2D
2. przykład giganta mapy świata prostokątnego
3. przykład giganta mapy świata izometrycznego
Czas zacząć...
MODEL POJEDYNCZEJ KOŚCI WARSTWY ŚWIATA 2D
Poniżej rysunek przedstawia dwie kości świata 2D. Pojedyncza kośc ma wymiar 64 x 48 pikseli. Proszę zwrócic uwagę, że drewniana (ma to być kasztelania:) budowla jest większa niż wymiar kafla.
Strzałki z klawiatury umożliwiają ruch warstw
Jakie zdania są stawiane na kość takiego świata:
a) musi umieć się poruszać
b) musi istnieć możliwość przyjęcia obrazu większego niż przyjęty rozmiar kafla
c) nie może być rysowana gdy nie zawiera obrazu
d) musi reagować na zmianę kolorów i kanału przezroczystości (Alpha)
e) może być animowana
f) może istnieć test kolizji
g) ... i różne inne pomysły
Spełnienie wszystkich zadań najłatwiej jest zrealizować tworząc klasę bazową pojedynczej kości warstwy. W kodzie programu jest ona zapisana w warstwa_cz1.pas
TKostkaMapy=class(TSimpleAnimSprite)
fPokaz:boolean;//dodatkowa flaga rysowania - ukrywania dla edytora swiatow
typ:TTypSwiata;
indeks:integer;
fSiatka:boolean;//falaga pokazywania suiatki
x0,y0, //stale wspolrzedne zaczepienia kafla
dX,dY:single;//wzgledne przesuniecie kafla
left,
top :integer;//wspolrzedne lewego rogu obrazu przekazanego do klatki
RogX,RogY:integer;//wspolrzedne kolumny i wiersza gornego lewego rogu mapy
idWskMApy :integer;//indeks wskaznika mapy
private
procedure RuchWLewo;
procedure RuchWPrawo;
procedure RuchWGore;
procedure RuchWDol;
procedure Draw;override;
procedure Move(const MoveCount:single);override;
end;
Serce bohatera artykułu zostało podane. Teraz pokrótce omówię realizację zadań wymienionych w podpunktach a, b...
Ad a) musi umieć się poruszać
Proszę teraz odpalić program i wcisnąć klawisze strzałek. Zauważymy, ze warstwa wcale nie porusza się po całym ekranie ale jest uwięziona. Ruch kości jest ograniczony. Kość może przesunąć się w lewo, prawo o odcinek nie większy niż jej szerokość, a w górę lub w dół o odcinek nie większy niż jej wysokość. Poczym wraca na swoje miejsce. Konkretnie do zmiennych
x0,y0, //stale wspolrzedne zaczepienia kafla
wartość przesuwu pamiętana jest w
dX,dY:single;//wzgledne przesuniecie kafla
Procedury reagujące na ruch to:
procedure RuchWLewo;
procedure RuchWPrawo;
procedure RuchWGore;
procedure RuchWDol;
Wywołane w
procedure TKostkaMapy.Move(const MoveCount:single);
begin
inherited Move(MoveCount);
dx:=dx+SkokX;
dy:=dy+SkokY;
RuchWLewo;
RuchWPrawo;
RuchWGore;
RuchWDol;
x:=x0+dx;
y:=y0+dy;
end;
Nie opisuję tego bo to zbyt proste. Jedynie nadmienię, ze
SkokX
SkokY
To zmienne globalne decydujące o przesuwaniu świata zgodnie z życzeniem gracza
procedure TForm1.RuchWSwiecie;
begin
if oisLeft in OmegaInput1.Keyboard.States then begin SkokX:=OmegaTimer1.DeltaSecs*150;end;
if oisRight in OmegaInput1.Keyboard.States then begin SkokX:=-OmegaTimer1.DeltaSecs*150;end;
if oisDown in OmegaInput1.Keyboard.States then begin SkokY:=-OmegaTimer1.DeltaSecs*100;end;
if oisUp in OmegaInput1.Keyboard.States then begin SkokY:=OmegaTimer1.DeltaSecs*100;end;
end;
Tu chce podkreślić znaczenie programowania obiektowego. NIE MA SENSU PRZEKAZYWANIA WARTOŚCI SkokX, SkokY do każdej kości z osobna w jakiś tam pętlach. Jest to ogromy błąd który spowalnia silnik gry. To załatwia główna metoda ojca (nie biologicznego) naszych kości
OmegaSprite1.Collision;
OmegaSprite1.Dead;
OmegaSprite1.Move(OmegaTimer1.DeltaSecs);
OmegaSprite1.Draw;
OmegaSprite1 jest ojcem dla TSimpleAnimSprite, czyli naszej kości
TKostkaMapy=class(TSimpleAnimSprite)
Ad b) musi istnieć możliwość przyjęcia obrazu większego niż przyjęty rozmiar kafla
Bardzo ważna cecha. Spełnienie tego warunku pozwala osadzać w świcie budynki w całości. Jedynie należy rozwiązać problem justowania takiego obrazu. Rozwiązanie jakie ja przyjąłem jest bardzo proste i idealnie pasuje do drzew. Dla kasztelani należało by je zmodyfikować- co nie jest trudne. Idee justowania przedstawia poniższy rysunek poglądowy
jak widać w kaflu 64x48 pikseli należy umieścić obraz o innym rozmiarze, tu 120x86 pikseli. Justowanie dotyczy warstw 1,2 i tak dalej. W kodzie programu zrobiłem to w momencie załadowania obrazka lub jego zmiany w kaflu warstwy pierwszej. Czyli wykonujemy to tymi liniami
Kosc1.Left:=(DaneMapy.W1Kosc -Kosc1.Image.TileWidth ) div 2;//ustaw w osi kafla
Kosc1.Top :=(DaneMapy.H1Kosc -Kosc1.Image.TileHeight)-16;//uastaw 16 pikseli nad dolna podstawa kafla
Wyliczone wartości są przechowywane w zmiennych klasy kafla to jest w
Left
Top
A wykorzystuje się je w procedurze rysowania
procedure TKostkaMapy.Draw;
var
Rect:TRect;
begin
if fSiatka then begin
Image.Renderer.OmegaScreen.DrawRectangle(Round(x+1),Round(y+1),Round(x+DAneMApy.W1Kosc-1),Round(y+DAneMApy.H1Kosc-1),
false,1,125,125,125);
exit;
end;
//nie rysuj jesli indeks wskazuje na -1
if (ImageIndex=-1)then exit;
Image.Draw(Round(x+left),Round(y+top),0,0.5,0.5,1,1,Red,Green,Blue,Alpha,ImageIndex,0);
end;
Podejście bardzo proste.
Ad b) nie może być rysowana, gdy nie zawiera obrazu
c) musi reagować na zmianę kolorów i kanału przezroczystości (Alpha)
To można omówić razem. Kość nie będzie rysowana gdy indeks obrazu wynosi -1 (minus jeden). Zmian kolorów Red, Green, Blue pozwoli nam uzyskać na przykład efekt nocy w poziomie gry. A wszystko to robimy tak
//nie rysuj jesli indeks wskazuje na -1
if (ImageIndex=-1)then exit;
Image.Draw(Round(x+left),Round(y+top),0,0.5,0.5,1,1,Red,Green,Blue,Alpha,ImageIndex,0);
Ad e) może być animowana
To zostawiam własnej inwencji twórczej. Należy przerobić odpowiednie przykłady dołączone do komponentu OMEGI. Można zastosować te rozwiązania bo nasza kość jest oparta na klasie TSimpleAnimSprite
TKostkaMapy=class(TSimpleAnimSprite)
Ad f) może istnieć test kolizji
Tu należy się zadać sobie pytanie:
Czy warto wykrywać kolizję z drzewem lub budynkiem metodą testu pikseli?
Moim zdaniem nie. Metoda ta przy dużej ilości drzew da się nam we znaki. Spowolni grę. Dalej: poza niewidoczną części świata gry nie jest wykonywana. Jej użycie będzie bez sensu dla "automatów". Jak ktoś czytał moje arty o "Wędrówkach w świecie 2D" to może pamięta, ze wprowadziłem tam tablicę siatki drogi. Test kolizji będzie tylko wykonywany przez obiekty ruchome i polega na sprawdzeniu: jest /brak. Stąd też kolizje należy wykonywać w klasach obiektów żywych. W warstwach nie...Ale to jest moje zdanie i nie każdy musi je podzielać.