Článek popisuje mou první hru pod enginem Quinty. Boxfall je klonem legendárního tetrisu s několika málo odlišnostma (a s několika pěknýma myšlenkama, které možná někdy uskutečním
).
Úvod
Základní myšlenku tetrisu (wikipedia) zná bezpochyby každý. Myšlenka Alexeye Pajitnova dává dokonalou možnost na procvičení programátorského umu a v mém případě taky k vylepšení Quinty.
Programovací jazyk: C++
Nestandartní knihovny: SDL
Poznámka: hra je vybudována na enginu Quinty v1.0.0
Stavební prvky
Základní struktury použité ve hře jsou Tetroid (pro popsání "kostky") a World (třída, která mimo jiné zastřešuje herní oblast).
Struktura Tetroid:
struct Tetroid { int posX; int posY; int block[4][4][4]; int width; int height; int color; int angle; };
Význam jednotlivých proměných:
posX, posY: pozice kostky vzhledem k herní oblasti (použito u padající kostky)
block[4][4][4]: v poli uchovávám pozice jednotlivých částí kostky pro všechny 4 možné směry natočení (máme 4 směry; každá kostka je definovaná polem 4x4)
width, height: šířka výška kostky
color: index barvy
angle: uchovává úhel kostky (0, 1, 2, 3)
Index barvy (color) udává pozici z následujícího obrázku:

Číslo v proměné úhel (angle) udává násobky 45ti stupňů.
Následuje fragment třídy Word, který má na starosti základní práci s herní oblastí.
class World: public GameEngine { public: // Constructor(s)/Destructor World(); virtual ~World(); // General methods void CreateArea(int width, int height, int square_dimensoin, int x, int y); void SetAreaPics(char* border); void BlitAreaBounds(); void BlitArea(); ... private: // Member variables int** m_area; int m_area_x; int m_area_y; int m_area_width; int m_area_height; int m_square_dimension; Image* m_area_border; ... };
Popis třídy (relevantních proměných a metod):
Jak je vidět třída World dědí od třídy GameEngine, která poskytuje základní funkcionalitu enginu.
Dynamické dvourozměrné pole (m_area) uchovává hrací oblast, definovanou šířkou (m_area_width) a výškou (m_area_height). Dále je uchovávána pozice (m_area_x; m_area_y), velikost čtverečku vyznačujícího hranici herní oblasti (m_square_dimension) a referenci na samotný obrázek čtverečku (m_area_border).
Metoda CreateArea slouží k vytvoření dynamické hrací plochy a SetAreaPics k nastavení obrázku okraje.
Další zajímavá metoda třídy World je SetTetroids().
void World::SetTetroids() { Tetroid tetroids[m_number_tetroids] = { { 0, 0, { { {1,0,0,0}, {1,1,1,0}, {0,0,0,0}, {0,0,0,0} }, { {0,1,0,0}, {0,1,0,0}, {1,1,0,0}, {0,0,0,0} }, { {1,1,1,0}, {0,0,1,0}, {0,0,0,0}, {0,0,0,0} }, { {1,1,0,0}, {1,0,0,0}, {1,0,0,0}, {0,0,0,0} } }, 3, 2, 0, 0 }, ... } }
Všechny dílčí proměnné jsou nastaveny na výchozí hodnotu. Trojrozměrné pole block je pro každý útvar a jeho úhel nastaveno jednoduchým způsobem - 1: plné pole, 0: prázdné pole. Tímto způsobem jsou nastaveny a uloženy všechny tvary.
Algoritmy
Základní funkčnost je v podstatě dost jednoduchá:
Pokud je pod aktivním (padajícím) tvarem místo, posuň ho o jednu kostičku dolů. Pokud se kostička dotkne vrchní hrany hrací plochy, hra končí.
V případě vstupu od uživatele je nutné provádět další kontroly:
Jestli tvar neopustí herní plochu (ať už posunem, nebo důsledkem rotace) a jestli rotací nedochází k dalším kolizím.
Jako první uvedu zdrojový kód obsluhy klávesnice.
void World::GE_HandleKeys() { m_keys = GE_GetKeyState(); if (m_keys[GEK_ESCAPE] == GE_PRESSED) GE_Exit(); if (m_keys[GEK_RIGHT] == GE_PRESSED) if (GetFallTetroidCollision(1,0) == false) MoveFallTetroid(1,0); if (m_keys[GEK_LEFT] == GE_PRESSED) if (GetFallTetroidCollision(-1,0) == false) MoveFallTetroid(-1,0); if ((m_up_key++ > 1) && (m_keys[GEK_UP] == GE_PRESSED)) { if (GetFallTetroidCollision(0,0,true) == false) RotateFallTetroid(); m_up_key = 0; } if (m_keys[GEK_DOWN] == GE_PRESSED) m_count = 3; }
Názvy metod určují jejich použití. Poněkud nejasné mohou být proměnné m_up_key a m_count. Obě slouží k nastavení určité prodlevy a to tak, že uměle protahují (popř. zkracují) pauzu mězi dvěma akcema. První proměnná m_up_key hlídá, aby nebyla akce otočení kostky tak citlivá a m_count hlídá určitou prodlevu pro padání aktivní kostky (po stisku šipky dolů se nastaví na hodnotu, která způsobí ignorování prodlevy). Jinak se proměnná m_count iteruje každým průchodem hlavního cyklu hry.
Přesouváme se ke klíčové metodě celé hry.
void World::GE_MainFunction() { if (m_count++ == 3) { if (GetFallTetroidCollision(0,1) == false) MoveFallTetroid(0,1); else { if (GetUpCollision()) GE_Exit(); GenerateFallTetroid(); GenerateNextTetroid(); } m_count = 0; } }
Jako první zkoumáme, zda se kostka může posunout dolů. Pokud ano, posuneme ji.
Následuje metoda na určování kolizí.
bool World::GetFallTetroidCollision(int x_move, int y_move, bool rotate) { // Bounds if (rotate) IncFallTetroidAngle(); if ((y_move>0) || rotate) if ( (m_fall_tetroid.posY + m_fall_tetroid.height > m_area_height) && (rotate) || (m_fall_tetroid.posY + m_fall_tetroid.height == m_area_height) && (rotate == false) ) { if (rotate) DecFallTetroidAngle(); SeatFallTetroid(); return true; } if ((x_move>0) || rotate) if ( (m_fall_tetroid.posX + m_fall_tetroid.width > m_area_width) && (rotate) || (m_fall_tetroid.posX + m_fall_tetroid.width == m_area_width) && (rotate == false) ) { if (rotate) DecFallTetroidAngle(); return true; } if (x_move<0) if (m_fall_tetroid.posX == 0) { if (rotate) DecFallTetroidAngle(); return true; } //************************************************************** if (rotate) DecFallTetroidAngle(); EraseFallTetroid(); if (rotate) IncFallTetroidAngle(); int index; for (int i = 0; i < 4; i++) for (int o = 0; o < 4; o++) { index = GetAddress(i+y_move,o+x_move); if ((index < 0) || (m_area[index] < 1)) continue; if (m_fall_tetroid.block[m_fall_tetroid.angle][i][o] == 1) { if ((x_move == 0) && (rotate == false)) { if (rotate) DecFallTetroidAngle(); SeatFallTetroid(); return true; } if (rotate) DecFallTetroidAngle(); MoveFallTetroid(0,0); return true; } } if (rotate) DecFallTetroidAngle(); return false; }
I když je metoda relativně dlouhá, její pochopení by neměl být problém. V první části se řeší kolize s okrajem (ať už vlivem rotace či posunu) a ve druhé pak kolize s už spadanýma kostkama.
Závěrečná poznámka: text berte pouze jako inspiraci a nastínění možného řešení. Celá hra vznikla pouze jako testovací projekt mého enginu. Určitě se dá spousta věcí vylepšit a spousta udělat jinak, ale v této chvíli už pracuji na nečem jiném a s Boxfall tím pádem končím. Zdrojové kódy nedám k dispozici, veškeré podstané části jsou v článku.
Tisknout tento příspěvek