Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!
Zadejte Vaši e-mailovou adresu:
Kamarád fotí rád?
Přihlas ho k odběru fotomagazínu!
Zadejte e-mailovou adresu kamaráda:
C/C++
Jednoduchá hra Auto v DelphiX (část 2)
11. října 2001, 00.00 | Naprogramujte si jednoduché autíčko v C++ Builderu. S DelphiX, žádný problém.
Nevím, jestli si ještě vzpomínáte na můj článek, který jsem napsal někdy na jaře. Jednalo se o jednoduchou hru Auto, podobnou 8-biťáckým starým dobrým hrám. Jestli jste nečetli podívejte se Hra Auto (1. část)
Jak jste se dočetli v předchozí části, dělali jsme jednoduchou hru s pohybujícím
se autem. Celá hrací plocha se skládala z 11*11 obrázků (políček). Je jasné,
že jsme od toho nemohli čekat plynulý pohyb, ani detailní grafiku.
Před nedávnem jsem dostal nápad, udělat dvě nové variace na stejné téma. Jedna
verze udělaná v DelphiX s použitím SpriteEnginu a další verze v OpenGL. Ale
pěkně po pořádku, dnes si uděláme verzi pro DelphiX. Podívejte se na obrázek,
jaké změny nás postihly:
Zvětšil jsem hrací plochu, zjemnil jsem ohyb po ploše a udělal jsem detailnější grafiku. Jelikož nejsem grafik, grafika není malovaná. Vypomohl jsem si auty ze starých časopisů a domy z katalogů (cestovních kanceláří :)). Pokud ale chcete udělat díru do světa, pořiďte si na grafickou úpravu levného profesionálního grafika, který vám udělá grafiku zadarmo.
Ještě než začneme hru vyvíjet, musíme si ještě předem naznačit, co je našim cílem. Budeme tvořit skrólovací hru, s bočním rolováním. Hráč ovládá auto po obrazovce nahoru a dolů a vyhýbá se protijedoucím vozidlům. Jeho volnost je omezena pouze na pohyb nahoru a dolu. Proti protivníkům lze vystřelit, ale jen omezeným počtem střel (aby to nebylo tak jednoduché). Po určité době se pohyb aut a četnost aut zrychluje.
Nyní se můžeme pustit do programování.
Založíme nový projekt a vložíme potřebné komponenty: DXDraw, DXImageList, DXTimer, DXInput, DXWaveList, DXSound a nesmíme zapomenout na DXSpriteEngine.
Připravíme si bitmapy, ty rozdělíme podle jejich funkce do čtyř skupin:
podklad - to co se zobrazuje na pozadí: cesta, tráva
player - auto, se kterým jedeme
enemy - nepřítel, protivník
střely
Veškerou grafiku si uložíme pomocí nějakého editoru do souboru grafika.dxg.
Jak uložit grafiku, načíst do DXImageListu
se dočtete v tomto
článku.
DXImageList1->Items->LoadFromFile(ExtractFilePath(Application->ExeName)+
"grafika.dxg");
Podklad
Podklad můžeme vyřešit dvěma způsoby. Buď vytvoříme část podkladu - cestu+ krajnici+ okolní trávu jako jeden obrázek a budeme jej opakovat a nebo druhá možnost je podklad poskládat z jednotlivých bitmap, aby nevypadal tak staticky. Zvlášť si připravíme bílou krajnici, žlutou středovou čáru, horní trávník, dolní trávník a samozřejmě silnici. Z těchto bitmap poskládáme podklad. Pro veškeré zobrazování použijeme SpriteEngine. Takže si pro vytvoření podkladů odvodíme třídu přímo ušitou na míru od TBackgroundSprite.
|
Třída neobsahuje žádné speciální finty, pouze jednu proměnou pro rychlost pohybu
FSpeed a odvozenou funkci DoMove. Ta se vyvolává při pohybu. Tato událost má
pouze jeden řádek
X-=FSpeed;
Z tohoto řádku vyplívá pohyb v ose X zleva doprava o FSpeed pixelů.
Teď již nezbývá než inicializovat jednotlivé podklady samostatně. Aby podklad
jako celek nevypadal tak staticky, budou středová čára a cesta skrólovat jinou
rychlostí než tráva na okraji. Úplně do pozadí namapujeme beton (přes celou
plochu).
Podklad->SetMapSize(1, 1);
Podklad->Tile = true;
Namapujeme pouze jednu bitmapu cesta pomocí SetMapSize(1, 1). Vlastností Tile = true, obrázkem pokryjeme celou plochu na pozadí (dlaždice). Na to posadíme trávu, krajnice atd... Tak například
|
Definovali jsme travinu na okraji. Za zmínku stojí řádek trava->SetMapSize(10000, 1). Jelikož nemáme nastaveno Tile = true, posadíme jednu bitmapu na danou souřadnici X, Y, Z. A SetMapSize určí, že se bude 10000x opakovat vedle sebe. Kdyby bylo v závorkách (1, 10000) skládali by se bitmapy pod sebe.
Player
Jak jsem se již zmínil, hráč pohybuje autem nahoru a dolu. Potřebujeme minimálně tři bitmapy, které budou představovat naše auto - pro pohyb nahoru, dolu a rovně. A opět si odvodíme třídu, která bude popisovat auto.
|
Třídu jsem odvodil od TImageSprite a obdařil jsem ji velkou spoustou proměnných. K čemu slouží? Proměnná animace říká, jestli se má sprit animovat nebo ne. Implicitně máme nastaveno false, ale pokud dojde ke kolizi, přepneme na true. Proměnná výbuch indikuje, jestli jsme náhodou nevybuchli. Proměnná Body ukazuje počet nahraných bodů ve hře. PosunY je hodnota, o kterou se při pohybu posuneme nahoru nebo dolu. A nakonec wait je čítač pro čekání.
Podívejme se na události. Nejprve nahlédněme do DoMove. Při každém volání testujeme kolizi procedurou Collision. Mimoto zajišťujeme pohyb nahoru a dolu pomocí kurzorových kláves.
|
Kód v tabulce je reakce na klávesy šipka nahoru a šipka dolu. Sprit se posune
o posunY pixelů v ose Y.
Kód je myslím naprosto výstižný, snad jen za zmínku stojí proměnná wait. Pokud
stisknete šipku nahoru (dolu), změní se bitmapa spritu na porschel (porscher).
Pokud bychom šipku pustíme, okamžitě by se objevil původní obrázek auta (obrázek
porsche). To se nestane, protože to opticky nevypadá moc dobře, je dobré chvíli
počkat. No a k tomu poslouží proměnná wait, kterou nastavíme například na 15
a při každém volání DoMove ji snížíme o 1.
Když je wait 0, Tak zobrazíme původní bitmapu (porsche).
Událost DoMove obsluhuje střelbu přes klávesu
ENTER. Počet vystřelených střel se počítá v globální proměnné palba, a po každém
výstřelu se opět čeká (čítačem prodleva), aby nelítala jedna střela za druhou.
Střelba je též omezena počtem střel (pocetstr).
Tím se dostáváme ke kolizi auta, o tu se stará událost DoCollision.
Při kolizi s kterýmkoli spritem dojde k výbuchu
Image = Form1->DXImageList1->Items->Find("explose");
Proměnné vybuch, animace
a AnimLooped přepneme na true, tím se sprit
začne animovat. AnimSpeed nastavíme na 4/1000
a nezapomeneme přetypovat na double (jinak se explose ani nepohne). Spolu s
autem vybuchne i protivník
((TEnemySprite *) Sprite)->Image = Form1->DXImageList1->Items->Find("explose");
Parametr sprite v události DoCollision přetypujeme na TEnemySprite,
to je speciální třída pro nepřátele.
Enemy
Pro nepřátele jsem vytvořil speciální třídu TEnemySprite,
opět odvozenou od TImageSprite. Třída je podobná
té předchozí. Navíc potřebujeme proměnné movingX
a movingY. To jsou indexy, které jsem zavedl,
abych identifikoval různé pohyby. Tak například pokud movingX
= 1, bude se nepřítel pohybovat doleva, když movingX
= 2, bude se nepřítel pohybovat doprava. Pro movingY
to platí analogicky. Možná vás napadne, jestli to není zbytečné, přece stačilo
by mít pouze proměnné pripocetX a pripocetY
a ty odčítat nebo přičítat podle toho jaký pohyb bychom chtěli udělat. To nás
ale značně omezuje, protože takhle bychom mohli pohybovat nepřítelem pouze do
osmi směru. Použitím movingX a movingY
zvládneme libovolný pohyb.
|
Takže movingX a movingY jsou velmi výhodné, podívejte se na movingX = 3, nepřítel se bude pohybovat v x v kosínusovém intervalu a ještě o jedno doleva, vznikne tak zajímavý pohyb v kombinaci s movingY = 1. Ve hře se jednou za čas objeví vosa, která vyletí z pravého dolního rohu a poletí po kosínusovce nahoru.
Střelba
Naše auto jsem vybavil novou vlastností, střelbou. Do třetice si odvodíme třídu od TImageSprite. Nazval jsem ji TStrela. Má pouze proměnné Síla, což je ničivá síla. Hodnota, která se odečte od hodnoty Zivot u nepřítele. Takže v našem případě je velikost života nepřítele 10 a síla střely také 10. To znamená, že sestřelíme nepřítele jedinou střelou. Pokud budeme chtít, aby byl nepřítel sestřelen na dvakrát, nastavíme sílu střely na 5. Opět tu také máme movingX a movingY. Mimo události DoMove tu musíme vyřešit kolize DoCollision:
((TEnemySprite *)Sprite)->Zivot -= Sila;
Přetypujeme parametr Sprite v události DoCollision
na TEnemySprite a odečteme od života sílu střely.
Na závěr musíme vyřešit to, co dělá naši hru hrou. To je pohyb, musíme zajistit, aby vše bylo v pohybu. K tomu nám poslouží obsluha události OnTimer u časovače DXTimer. Zde zajistíme překreslení obrazovky, umístění nepřátel atd.
Hra funguje asi takto: probíhá cyklus vyvolaný událostí OnTimer, při každé obrátce zajistíme vymazání obrazu a překreslení SpriteEnginu. SpriteEngine přesune všechny sprity na nové umístění a zjistí, jestli některé sprity nekolidují. Pokud došlo ke kolizi hráče Auto->vybuch = true, hra končí.
Auto ve hře ve skutečnosti stojí na jednom místě, pohybuje se jen podklad a nepřátelé. O umístění nepřátel se též staráme mi a generátor náhodných čísel.
V každém průchodu události OnTimer vygenerujeme náhodné číslo. Toto číslo je z intervalu 0 - 1000. Pokud je číslo například 0 - 5 dosadíme nepřítele na náhodně zvolenou (ale pozor vhodně) souřadnici těsně za obrazovku. Definujeme mu pohyb do leva, takže ihned jakoby vyjede na scénu :). To samé provádíme i s baráčky nahoře. Podívejte se jak sázíme do scény nepřátele:
|
Na obrázku vidíte malý výřez z obrázku enemy, zde jsou poskládány auta nepřátel do řádky vedle sebe (12x). Jak již jsem říkal naše náhodné číslo je mále například 0-5. Vybereme tedy z obrázku enemy auto, které je v pořadí rnd (AnimPos), tedy stejném jako náhodné číslo. Pro správnou kolizi je potřeba nastavit hodnoty Width a Height. Nepřítel je umístěn těsně za obrazovku na 10000+DXDraw1->Width. Y souřadnici bereme tak, aby se auto vešlo do vozovky. Opět si pomůžeme generátorem náhodných čísel.
Na závěr bych se krátce zmínil o kolizích, které jsou brány obdélníkově,
což považuji za chybu. To jakou kolizi DelphiX použije nastavujeme přepínačem
PixelCheck. V poloze false, jsou kolize brány
obdélníkově a je to bráno podle hodnot Width
a Height spritu (šířky a délky). Pokud šířku
a výšku nastavíte menší než je skutečná (PatternWidth
a PatternHeight) dosáhnete lepšího výsledku.
Nicméně zase se vám změní umístění spritu na obrazovce. Dejme tomu, že máme
sprit umístěn na souřadnici 100*100. Pokud tedy vyzkoušíme toto umístění spritu
dvakrát, pokaždé s jiným nastavením Width a
Height, budou obrázky umístěny různě. A to
není vůbec příjemné.
Pokud nastavíme PixelCheck na true,
kolize je sledována skutečným tvarem obrázku. Na první pohled je vše v pořádku,
ale trable nastanou při animovaných spritech nebo spritech páskových, které
jsme použili mi. Protože u takových spritů DelphiX bere kolize pouze na první
snímek. Takže pokud bychom v našem případě vzali AnimPos
= 2, tak bychom normálně mohli přejet přes sprit a nedošlo by ke kolizi.
Jak je to možné? To skutečně nevím. Pokud tento problém vyřešíte, prosím napište
mi.
Doufám, že se vám dnešní článek líbil a bude inspirací pro vaši budoucí tvorbu.
Relevantní články
Seriál - Tvorba her v DelphiX
Seriál - Tvorba hry Had v DelphiX
Zobrazení části
obrázku v DelphiX
Komponenta
DXImageList
Sprity v DelphiX
(C++ Builder)
-
25. listopadu 2012
-
30. srpna 2002
-
10. října 2002
-
4. listopadu 2002
-
12. září 2002
-
25. listopadu 2012
-
28. července 1998
-
31. července 1998
-
28. srpna 1998
-
6. prosince 2000
-
27. prosince 2007
-
4. května 2007