Found at: http://publish.ez.no/article/articleprint/2/

Creating games with QCanvas



The Qt class QCanvas features optimized 2D graphics with game related functions, such as sprites and collision detection. This article describes how to use this in a small game.

Russian translation


You've got the whole world in your hands...

QCanvas background


The QCanvas class fits well for 2D games development, as it is optimized for speed and has some very useful features, like sprites (animated images), collision detection (essential in most 2D games), various built in shape classes and built in animation features.
To use it, you add a QCanvas to your application, and add a QCanvasView (a QWidget) to view the canvas. Then you can add QCanvasItems to the canvas. (The QCanvasView must be subclassed in order to interact with the canvas items.) We are going to demonstrate this in a small game.

The game


The game used in the demonstration is very simple, but strangely amusing: It consists of a 2D box and a ball. The ball can be picked up and tossed using the mouse, and will bounce back and forth against the walls of the box until air resistance, friction and gravity stops it, or you pick it up again. In order to make it all a bit more interesting, we use the box as an analogue of the universe, and the ball is, yup, that's correct: the Earth. (Some people might argue that there is no air resistance in space because there is no air, and that the force of gravity being directed downwards doesn't make any sense in space. These are usually the same kind of people that, when you comment on the birds happy singing on a nice spring day, will tell you that the song actually is an aggressive marking of territory. It's technically correct, but where's the fun in that?)



A close-up of the planet and stars

The classes


We use 3 classes: Ball, which is subclassed from QCanvasSprite (which inherits QCanvasItem). This is the bouncing globe, it contains routines to control its movement. BounceCanvasView is subclassed from QCanvasView, and contains the mouse interaction functions. Finally, BounceWidget (a QWidget) is the application window, it contains the canvasview and a "Quit" button.

BounceWidget


The BounceWidget constructor creates a QCanvas and sets the BounceCanvasView to view it. We then tell the canvas to update every 10 ms, and create a planet and set its initial location and speed. We also set the graphics for the planet and the background. To change them, simply replace "stars.png" and "earth.png" with something else. (Note: The image of the earth is of course square, not a circle. You must use a format that supports transparency, like PNG or GIF, to achieve the illusion of a circle.)

BounceWidget constructor



BounceWidget::BounceWidget( QWidget *parent, const char *name )
    : QWidget( parent, name )
{
    // Create and setup canvas and view
    Canvas = new QCanvas( this, "Canvas" );
    Canvas->setBackgroundColor( Qt::black );
    Canvas->setBackgroundPixmap( QPixmap( "stars.png" ) );
    Canvas->setAdvancePeriod( 10 );
    Canvas->resize( 500, 500 );
    CanvasView = new BounceCanvasView( Canvas, this, "CanvasView" );

    // setup the quit button
    QuitButton = new QPushButton( "&Quit", this, "QuitButton" );
    connect( QuitButton, SIGNAL( clicked() ), qApp, SLOT( quit() ) );

    // setup the layout manager
    QVBoxLayout *vlay = new QVBoxLayout( this );
    vlay->setResizeMode( QLayout::Fixed );
    vlay->addWidget( CanvasView );
    vlay->addWidget( QuitButton );

    // add a planet to our scenery
    Ball *sphere = new Ball( new QCanvasPixmapArray( "earth.png" ), Canvas );
    sphere->move( 100, 150 );
    sphere->setXVelocity( 2 );
    sphere->setYVelocity( 4 );
    sphere->show();
}



BounceCanvasView


The BounceCanvasView has a QTimer that is started when we pick up the ball. It will emit a signal every 10 ms until we drop the ball. The signal is connected to the timeout() slot, which records the current mouse position and place it in a queue of the 10 most recent mouse positions. When the ball is released, we use this queue to calculate an average velocity of the mouse movement for the last 100 ms. Then the ball is thrown using this value. This gives a nice simulation of a throw, we can make the ball go fast or slow, or just drop it.

BounceCanvasView::contentsMouseReleaseEvent


This is what happens when the mouse is released. (See the last page of this article for the full source code.)


void BounceCanvasView::contentsMouseReleaseEvent( QMouseEvent* )
{
    if ( Moving )    // if we are holding the ball
    {
        Timer->stop();    // stop the timer

        if ( MousePosQueue.count() )  // if we have recorded mouse positions...
        {
            // ...then calculate average velocity
            int count = MousePosQueue.count();
            QPoint velocity( 0, 0 );
            QPoint pos;
            QPoint prevPos = *MousePosQueue.head();

            while ( MousePosQueue.count() )
            {
                pos = *MousePosQueue.dequeue();
                velocity += pos - prevPos;
                prevPos = pos;
            }

            // set the balls new velocity
            Moving->setXVelocity( velocity.x() / count );
            Moving->setYVelocity( velocity.y() / count );
        }
        else  // if we haven't recorded anything (fast click)
        {
            // set zero velocity (we drop the ball)
            Moving->setXVelocity( 0 );
            Moving->setYVelocity( 0 );
        }

        Moving->setZ( 1 );
        canvas()->setAdvancePeriod( 10 );  // restart the animation
        Moving = 0;     // clear the pointer, we are no longer holding the ball
    }
}



The Ball


This class contains the physical data of the ball (mass and resistance), the hit() function, which determines whether or not we have hit a transparent pixel, and the advance(int) function, which works in two phases. In the first phase, we apply resistance and gravity, then we check whether the ball has hit a wall, and reverse it's velocity it if it has. Finally it is moved to be exactly adjacent to the wall, and further movement in this direction is disabled, since we don't want it to move through the wall. The second phase moves the ball a single step. You don't have to worry about calling advance(int) with the correct arguments, this is done by the canvas.

Ball::advance



void Ball::advance( int phase )
{

    if ( phase == 0 ) // stuff to be done before we move
    {
        // apply resistance to slow down the ball
        setXVelocity( xVelocity() * Air_resistance );
        setYVelocity( yVelocity() * Air_resistance );

        // apply gravity
        setYVelocity( yVelocity() + Mass );

        // right border hit check
        if ( !canvas()->onCanvas( x() + width()/2 + xVelocity(), 1 ) )
        {
            MoveX = false;
            move( canvas()->width() - width()/2, y() );
            setXVelocity( xVelocity() * -1 * Bump_resistance );
        }
        // left border hit check
        else if ( !canvas()->onCanvas( x() - width()/2 + xVelocity(), 1 ) )
        {
            MoveX = false;
            move( 0 + width()/2, y() );
            setXVelocity( xVelocity() * -1 * Bump_resistance );
        }

        // lower border hit check
        if ( !canvas()->onCanvas( 1, y() + height()/2 + yVelocity() ) )
        {
            MoveY = false;
            move( x(), canvas()->height() - height()/2 );
            setYVelocity( yVelocity() * -1 * Bump_resistance );
        }
        // upper border hit check
        else if ( !canvas()->onCanvas( 1, y() - height()/2 + yVelocity() ) )
        {
            MoveY = false;
            move( x(), 0 + height()/2 );
            setYVelocity( yVelocity() * -1 * Bump_resistance );
        }
    }
    else    // time to move
    {
        if ( MoveX && MoveY )
            move( x() + xVelocity(), y() + yVelocity() );
        else if ( MoveX )
            move( x() + xVelocity(), y() );
        else if ( MoveY )
            move( x(), y() + yVelocity() );

        MoveX = MoveY = true;
    }
}



Main


main.cpp is simple, like in most Qt applications. We create a QApplication object and a BounceWidget object. We set the window caption, and that's about it.


int main( int argc, char **argv )
{
    QApplication a( argc, argv );

    BounceWidget bw( 0, "BounceWidget" );
    a.setMainWidget( &bw );
    bw.setCaption( "Astrophysics for beginners!" );
    bw.show();

    return a.exec();
}


Voila! A bouncing globe!


Now what?


The following cool features are left for the reader to implement:
1. Add the sun and the rest of the planets, and make them bounce against each other as well as the walls. Make buttons to add/remove them.
2. Make it possible to hit the ball like you would hit a tennis ball, by moving the mouse towards the ball with the left button pressed. You'll need to make the timer continuously active, so you can record the mouse velocity when you hit the ball, enabling you to hit hard or softly. If you want to make the ball move correctly when you hit it at an angle, you'll also have to add some clever calculations for that.
3. Make the ball spin as well as bounce, depending on how you hit/throw it, and/or make it (the earth) spin around it's axis. You'll have to create the extra graphics needed yourself.
4. Add an option to zero the resistance, making the ball bounce into infinity. (and beyond..!)
5. Make the entire window shake when the ball hits the walls real hard.

Now grab the source:
Download the attached file bounce.tar.gz (27 Kb) at the bottom of this page.
Bounce is released under the GPL.
Requires: Qt and tmake, available from TrollTech.
(It was developed with Qt-2.2.1, but may work with any previous release, provided it has the QCanvas class.)

Build instructions:

tar -zxf bounce.tar.gz
cd bounce
tmake -o Makefile bounce.pro
make
./bounce


Have fun!

Attached files:

bounce.tar.gz

| Back to normal page view |