Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Classes principales  ·  Annotées  ·  Classes groupées  ·  Fonctions  · 

Qt Tutorial - Chapter 13: Game Over

Screenshot of tutorial thirteen

In this example we start to approach a real playable game with a score. We give MyWidget a new name (GameBoard) and add some slots.

We put the definition in gamebrd.h and the implementation in gamebrd.cpp.

The CannonField now has a game over state.

The layout problems in LCDRange are fixed.

Line-by-line Walkthrough

t13/lcdrange.h

    #include <qwidget.h>

    class QSlider;
    class QLabel;

    class LCDRange : public QWidget

We inherit QWidget rather than QVBox. QVBox is very easy to use, but again it showed its limitations so we switch to the more powerful and slightly harder to use QVBoxLayout. (As you remember, QVBoxLayout is not a widget, it manages one.)

t13/lcdrange.cpp

    #include <qlayout.h>

We need to include qlayout.h now to get the other layout management API.

    LCDRange::LCDRange( QWidget *parent, const char *name )
            : QWidget( parent, name )

We inherit QWidget in the usual way.

The other constructor has the same change. init() is unchanged, except that we've added some lines at the end:

        QVBoxLayout * l = new QVBoxLayout( this );

We create a QVBoxLayout with all the default values, managing this widget's children.

        l->addWidget( lcd, 1 );

At the top we add the QLCDNumber with a non-zero stretch.

        l->addWidget( slider );
        l->addWidget( label );

Then we add the other two, both with the default zero stretch.

This stretch control is something QVBoxLayout (and QHBoxLayout, and QGridLayout) offers but classes like QVBox do not. In this case we're saying that the QLCDNumber should stretch and the others should not.

t13/cannon.h

The CannonField now has a game over state and a few new functions.

        bool  gameOver() const { return gameEnded; }

This function returns TRUE if the game is over or FALSE if a game is going on.

        void  setGameOver();
        void  restartGame();

Here are two new slots: setGameOver() and restartGame().

        void  canShoot( bool );

This new signal indicates that the CannonField is in a state where the shoot() slot makes sense. We'll use it below to enable/disable the Shoot button.

        bool gameEnded;

This private variable contains the game state. TRUE means that the game is over, and FALSE means that a game is going on.

t13/cannon.cpp

        gameEnded = FALSE;

This line has been added to the constructor. Initially, the game is not over (luckily for the player :-).

    void CannonField::shoot()
    {
        if ( isShooting() )
            return;
        timerCount = 0;
        shoot_ang = ang;
        shoot_f = f;
        autoShootTimer->start( 50 );
        emit canShoot( FALSE );
    }

We added a new isShooting() function, so shoot() uses it instead of testing directly. Also, shoot tells the world that the CannonField cannot shoot now.

    void CannonField::setGameOver()
    {
        if ( gameEnded )
            return;
        if ( isShooting() )
            autoShootTimer->stop();
        gameEnded = TRUE;
        repaint();
    }

This slot ends the game. It must be called from outside CannonField, because this widget does not know when to end the game. This is an important design principle in component programming. We choose to make the component as flexible as possible to make it usable with different rules (for example, a multi-player version of this in which the first player to hit ten times wins could use the CannonField unchanged).

If the game has already been ended we return immediately. If a game is going on we stop the shot, set the game over flag, and repaint the entire widget.

    void CannonField::restartGame()
    {
        if ( isShooting() )
            autoShootTimer->stop();
        gameEnded = FALSE;
        repaint();
        emit canShoot( TRUE );
    }

This slot starts a new game. If a shot is in the air, we stop shooting. We then reset the gameEnded variable and repaint the widget.

moveShot() too emits the new canShoot(TRUE) signal at the same time as either hit() or miss().

Modifications in CannonField::paintEvent():

    void CannonField::paintEvent( QPaintEvent *e )
    {
        QRect updateR = e->rect();
        QPainter p( this );

        if ( gameEnded ) {
            p.setPen( black );
            p.setFont( QFont( "Courier", 48, QFont::Bold ) );
            p.drawText( rect(), AlignCenter, "Game Over" );
        }

The paint event has been enhanced to display the text "Game Over" if the game is over, i.e., gameEnded is TRUE. We don't bother to check the update rectangle here because speed is not critical when the game is over.

To draw the text we first set a black pen; the pen color is used when drawing text. Next we choose a 48 point bold font from the Courier family. Finally we draw the text centered in the widget's rectangle. Unfortunately, on some systems (especially X servers with Unicode fonts) it can take a while to load such a large font. Because Qt caches fonts, you will notice this only the first time the font is used.

        if ( updateR.intersects( cannonRect() ) )
            paintCannon( &p );
        if ( isShooting() && updateR.intersects( shotRect() ) )
            paintShot( &p );
        if ( !gameEnded && updateR.intersects( targetRect() ) )
            paintTarget( &p );
    }

We draw the shot only when shooting and the target only when playing (that is, when the game is not ended).

t13/gamebrd.h

This file is new. It contains the definition of the GameBoard class, which was last seen as MyWidget.

    class QPushButton;
    class LCDRange;
    class QLCDNumber;
    class CannonField;

    #include "lcdrange.h"
    #include "cannon.h"

    class GameBoard : public QWidget
    {
        Q_OBJECT
    public:
        GameBoard( QWidget *parent=0, const char *name=0 );

    protected slots:
        void  fire();
        void  hit();
        void  missed();
        void  newGame();

    private:
        QLCDNumber  *hits;
        QLCDNumber  *shotsLeft;
        CannonField *cannonField;
    };

We have now added four slots. These are protected and are used internally. We have also added two QLCDNumbers (hits and shotsLeft) which display the game status.

t13/gamebrd.cpp

This file is new. It contains the implementation of the GameBoard class, which was last seen as MyWidget.

We have made some changes in the GameBoard constructor.

        cannonField = new CannonField( this, "cannonField" );

cannonField is now a member variable, so we carefully change the constructor to use it. (The good programmers at Trolltech never forget this, but I do. Caveat programmor - if "programmor" is Latin, at least. Anyway, back to the code.)

        connect( cannonField, SIGNAL(hit()),
                 this, SLOT(hit()) );
        connect( cannonField, SIGNAL(missed()),
                 this, SLOT(missed()) );

This time we want to do something when the shot has hit or missed the target. Thus we connect the hit() and missed() signals of the CannonField to two protected slots with the same names in this class.

        connect( shoot, SIGNAL(clicked()), SLOT(fire()) );

Previously we connected the Shoot button's clicked() signal directly to the CannonField's shoot() slot. This time we want to keep track of the number of shots fired, so we connect it to a protected slot in this class instead.

Notice how easy it is to change the behavior of a program when you are working with self-contained components.

        connect( cannonField, SIGNAL(canShoot(bool)),
                 shoot, SLOT(setEnabled(bool)) );

We also use the cannonField's canShoot() signal to enable or disable the Shoot button appropriately.

        QPushButton *restart
            = new QPushButton( "&New Game", this, "newgame" );
        restart->setFont( QFont( "Times", 18, QFont::Bold ) );

        connect( restart, SIGNAL(clicked()), this, SLOT(newGame()) );

We create, set up, and connect the New Game button as we have done with the other buttons. Clicking this button will activate the newGame() slot in this widget.

        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" );

We create four new widgets. Note that we don't bother to keep the pointers to the QLabel widgets in the GameBoard class because there's nothing much we want to do with them. Qt will delete them when the GameBoard widget is destroyed, and the layout classes will resize them appropriately.

        QHBoxLayout *topBox = new QHBoxLayout;
        grid->addLayout( topBox, 0, 1 );
        topBox->addWidget( shoot );
        topBox->addWidget( hits );
        topBox->addWidget( hitsL );
        topBox->addWidget( shotsLeft );
        topBox->addWidget( shotsLeftL );
        topBox->addStretch( 1 );
        topBox->addWidget( restart );

The number of widgets in the top-right cell is getting large. Once it was empty; now it's full enough that we group together the layout setting for better overview.

Notice that we let all the widgets have their preferred sizes, instead putting the stretch just to the left of the New Game button.

        newGame();
    }

We're all done constructing the GameBoard, so we start it all using newGame(). (NewGame() is a slot, but as we said, slots can be used as ordinary functions, too.)

    void GameBoard::fire()
    {
        if ( cannonField->gameOver() || cannonField->isShooting() )
            return;
        shotsLeft->display( shotsLeft->intValue() - 1 );
        cannonField->shoot();
    }

This function fires a shot. If the game is over or if there is a shot in the air, we return immediately. We decrement the number of shots left and tell the cannon to shoot.

    void GameBoard::hit()
    {
        hits->display( hits->intValue() + 1 );
        if ( shotsLeft->intValue() == 0 )
            cannonField->setGameOver();
        else
            cannonField->newTarget();
    }

This slot is activated when a shot has hit the target. We increment the number of hits. If there are no shots left, the game is over. Otherwise, we make the CannonField generate a new target.

    void GameBoard::missed()
    {
        if ( shotsLeft->intValue() == 0 )
            cannonField->setGameOver();
    }

This slot is activated when a shot has missed the target. If there are no shots left, the game is over.

    void GameBoard::newGame()
    {
        shotsLeft->display( 15 );
        hits->display( 0 );
        cannonField->restartGame();
        cannonField->newTarget();
    }

This slot is activated when the user clicks the Restart button. It is also called from the constructor. First it sets the number of shots to 15. Note that this is the only place in the program where we set the number of shots. Change it to whatever you like to change the game rules. Next we reset the number of hits, restart the game, and generate a new target.

t13/main.cpp

This file has just been on a diet. MyWidget is gone, and the only thing left is the main() function, unchanged except for the name change.

Behavior

The cannon can shoot at a target; a new target is automatically created when one has been hit.

Hits and shots left are displayed and the program keeps track of them. The game can end, and there's a button to start a new game.

(See Compiling for how to create a makefile and build the application.)

Exercises

Add a random wind factor and show it to the user.

Make some splatter effects when the shot hits the target.

Implement multiple targets.

You're now ready for Chapter 14.

[Previous tutorial] [Next tutorial] [Main tutorial page]

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 64
  2. Apercevoir la troisième dimension ou l'utilisation multithreadée d'OpenGL dans Qt, un article des Qt Quarterly traduit par Guillaume Belz 0
  3. Les développeurs ignorent-ils trop les failles découvertes dans leur code ? Prenez-vous en compte les remarques des autres ? 17
  4. BlackBerry 10 : premières images du prochain OS de RIM qui devrait intégrer des widgets et des tuiles inspirées de Windows Phone 0
  5. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  6. Adieu qmake, bienvenue qbs : Qt Building Suite, un outil déclaratif et extensible pour la compilation de projets Qt 17
  7. La rubrique Qt a besoin de vous ! 1
Page suivante

Le Qt Quarterly au hasard

Logo

Abusons de QMap

Qt Quarterly est la revue trimestrielle proposée par Nokia et à destination des développeurs Qt. Ces articles d'une grande qualité technique sont rédigés par des experts Qt. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 3.3
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web