V tomto príklade sa konečne blížime reálnej hre. Dávame MyWidget nové meno GameBoard a pridávame zopár slotov.
Premiestňujeme definíciu do súboru gamebrd.h a implementáciu do gamebrd.cpp.
CannonField má teraz stav koniec hry (game over).
CannonField má teraz stav game over a pár nových funkcií.
bool gameOver() const { return gameEnded; }
Táto funkcia vracia TRUE ak je hra skončená, FALSE ak pokračuje.
void setGameOver(); void restartGame();
Tu máme dva nové sloty: setGameOver() a restartGame().
bool gameEnded;
Privátna premenná so stavom hry. TRUE zmanená, že hra skončila, FALSE naopak, že pokračuje.
#include <qfont.h>
Vložíme definíciu triedy QFont.
gameEnded = FALSE;
Tento riadok bol pridaný do konštruktora. Na začiatku hra nie je skončená.
void CannonField::setGameOver() { if ( gameEnded ) return; if ( shooting ) stopShooting(); gameEnded = TRUE; repaint(); }
Tento slot ukončí hru. Musí byť volaný zvonku objektu CannonField, pretože tento widget nevie, kedy ukončiť hru. Toto je doležitý princíp návrhu v komponentovom programovaní. Snažíme sa vytvoriť komponent maximálne flexibilný, aby bol použiteľný v rôznych hrách s rôznymi pravidlami.
Ak je hra už skončená, ihneĎ sa vrátime. Ak hra neskončila, zastavíme strelu, nastavíme príznak (premennú) skončenia hry a prekreslíme celý widget.
void CannonField::restartGame() { if ( shooting ) stopShooting(); gameEnded = FALSE; repaint(); }
Tento slot spustí novú hru. Ak je strela vo vzduchu, zastavíme ju.
Potom nastavíme premennú gameEnded
na FALSE a prekreslíme widget.
Zmeny v CannonField::paintEvent():
if ( gameEnded ) { p.setPen( black ); p.setFont( QFont( "Courier", 48, QFont::Bold ) ); p.drawText( rect(), AlignCenter, "Game Over" ); } else {
Rozšírili sme obsluhu udalosti paint event o zobrazenie textu "Game Over"
ak je hra skončená, t.j. ak je premenná gameEnded
TRUE.
Tu nás nezaujíma obdĎžnik, ktorý treba upraviť, pretože rýchlosť
nie je podstatná, keĎ je hra skončená.
Na nakreslenie textu najprv nastavíme čierne pero. Farba pera je použitá keĎ píšeme text. Potom nastavíme 48 bodov veľký tučný font z rodiny fontov Courier. Nakoniec vypíšeme text vycentrovane v obdĎžniku widgetu. Na niektorých systémoch (hlavne niektorých X serveroch) to bohužiaľ chvíľu trvá, kým sa nahrá taký veľký font. Pretože Qt má pre fonty vlastnú cache, všimnete si to len pri prvom použití fontu.
if ( shooting && updateR.intersects( shotRect() ) ) paintShot( &p ); if ( updateR.intersects( targetRect() ) ) paintTarget( &p ); }
Ak nie je hra skončená, kreslíme len strelu alebo terč.
Tento súbor je nový. Obsahuje definíciu triedy GameBoard, ktorá naposledy vystupovala pod menom MyWidget.
class GameBoard : public QWidget { Q_OBJECT public: GameBoard( QWidget *parent=0, const char *name=0 ); protected: void resizeEvent( QResizeEvent * ); protected slots: void fire(); void hit(); void missed(); void newGame(); private: QPushButton *quit; QPushButton *shoot; QPushButton *restart; LCDRange *angle; LCDRange *force; QLCDNumber *hits; QLCDNumber *shotsLeft; CannonField *cannonField; };
Pridali sme štyri sloty. Sú chránené a používané interne.
Ďalej sme pridali dve čísla QLCDNuber: hits
(zásahy) a
shotsLeft
(ostávajúce strely), ktoré zobrazujú stav hry.
Nové je aj tlačidlo restart
.
Aj tento súbor je nový. Obsahuje implementáciu triedy GameBoard, naposledy nazvanej MyWidget.
Urobili sme pár zmien v konštruktore triedy GameBoard.
connect( cannonField, SIGNAL(hit()),SLOT(hit()) ); connect( cannonField, SIGNAL(missed()),SLOT(missed()) );
Tentokrát chceme niečo urobiť ak strela trafí alebo mine terč. Takže spojíme signály hit() a missed() s dvoma chránenými slotmi (s rovnakými menami) tejto triedy.
connect( shoot, SIGNAL(clicked()), SLOT(fire()) );
Predtým sme pripájali signál tlačidla shoot clicked() priamo na slot shoot() CannonFieldu. Teraz chceme sledovať počet výstrelov, preto ho spájame s chráneným slotom fire() tejto triedy.
Všimnite si, aké je ľahké zmeniť správanie programu keď pracujeme so samostatnými komponentmi.
restart = new QPushButton( "New Game", this, "newgame" ); restart->setFont( QFont( "Times", 18, QFont::Bold ) ); connect( restart, SIGNAL(clicked()), SLOT(newGame()) );
Vytvárame, nastavujeme a pripájame tlačidlo restart podobne ako ostatné tlačidlá. Stlačenie tohto tlačidla aktivuje slot newGame() tohto widgetu.
hits = new QLCDNumber( 2, this, "hits" ); shotsLeft = new QLCDNumber( 2, this, "shotsleft" ); QLabel *hitsL = new QLabel( "HITS", this, "hitsLabel" ); QLabel *shotsLeftL = new QLabel( "SHOTS LEFT", this, "shotsleftLabel" );
Vytvárame štyri nové widgety. Všimnite si, že sa nestaráme o uchovanie smerníkov na popisy (label) v triede GameBoard, preto si žijú svoj vlastný život. Ako bolo prv spomenuté, keď bude rušený objekt GameBoard, Qt zruší ja oba popisy.
quit->setGeometry( 10, 10, 75, 30 ); angle->setGeometry( 10, quit->y() + quit->height() + 10, 75, 130 ); force->setGeometry( 10, angle->y() + angle->height() + 10, 75, 130 ); cannonField->move( angle->x() + angle->width() + 10, angle->y() ); shoot->setGeometry( 10, 315, 75, 30 ); restart->setGeometry( 380, 10, 110, 30 ); hits->setGeometry( 130, 10, 40, 30 ); hitsL->setGeometry( hits->x() + hits->width() + 5, 10, 60, 30 ); shotsLeft->setGeometry( 240, 10, 40, 30 ); shotsLeftL->setGeometry( shotsLeft->x()+shotsLeft->width()+5, 10, 60, 30 );
Pretože máme veľké množstvo widgetov, chceme nastaviť geometriu na jednom mieste. To nám zjednoduší budúce zmeny geometrie.
newGame();
Zavoláme slot newGame() a začína sranda.
void GameBoard::fire() { if ( cannonField->gameOver() || cannonField->isShooting() ) return; shotsLeft->display( shotsLeft->intValue() - 1 ); cannonField->shoot(); }
Táto funkcia vystrelí strelu. Ak je hra skončená alebo je strela vo vzduchu, neurobí nič. Inak znížime počet ostávajúcich striel o jednu a povieme kanónu, že má vystreliť.
void GameBoard::hit() { hits->display( hits->intValue() + 1 ); if ( shotsLeft->intValue() == 0 ) cannonField->setGameOver(); else cannonField->newTarget(); }
Tento slot bude aktivovaný keď strela zasiahne cieľ. Zvýšime počet zásahov. Ak už neostali žiadne strely, je koniec hry. Inak prinútime CannonField vytvoriť nový terč.
void GameBoard::missed() { if ( shotsLeft->intValue() == 0 ) cannonField->setGameOver(); }
Tento slot je aktivovaný ak strela minie terč. Ak už neostali žiadne strely, hra končí.
void GameBoard::newGame() { shotsLeft->display( 15 ); hits->display( 0 ); cannonField->restartGame(); cannonField->newTarget(); }
Tento slot je aktivovaný keď užívateľ stlačí tlačidlo restart. Je volaný aj z konštruktora. Najprv nastaví počet striel na 15. Všimnite si, že toto je jediné miesto v programe, kde nastavujeme počet striel. Zmenou tohto jedniného čísla zmeníte pravidlá hry. Potom vynulujeme počet zásahov, reštartujeme hru a vygenerujeme nový terč.
Tento súbor sme presunuli na diétu. MyWidget zmizol a ostala len funkcia main().
Zásahy a strely sú zobrazované a program sleduje ich počet. Hra sa môže skončiť a máme tlačidlo na spustenie novej hry.
Pridajte náhodný faktor vetra a zobrazte ho užívateľovi.
Vytvorte efekt výbuchu pri zásahu terča.
Implementujte viacero terčov.
Teraz môžete ísť na kapitolu štrnásť.
[Predchádzajúci tutoriál] [Ďalší tutoriál] [Hlavná stránka tutoriálu]
Copyright © 1998 Troll Tech | Trademarks | Qt version 1.42
|