Writing a game with the animation and the state machine API

Posted by Alexis Menard on April 1, 2009 · 9 comments

If you’ve checked out the Qt solution for the Animation API, then you’ve probably found the game called Sub-Attaq. This game was an idea I had when I started working on the Kinetic project. The idea came from a game I played 6-7 years ago but i don’t remember its name anymore. For those who don’t know, it’s a simple 2D game where you control a boat and you can drop bombs on submarines.

Anyway, the goal of this game was first to play with it, and at the end to get a high score rank for the office (competition is always good). Oops, I forgot the obvious reason : testing our API for animations. So I started writing a basic game but I was busy with 4.5.0… so I let this game as it was, just some basic game logic and couple of animations.

But some months ago, a new state machine framework popped in the Kinetic repository. I have to admit that Kent convinced me when he introduced me the framework and I had participated in some API reviews to give him feedback. Then an idea came in my mind, why not use it in Sub-Attaq? The context of this game matched well with state-charts, so why not… Then I started thinking and drawing the state chart for the whole game on my white board. After some hours of drawing and erasing, I finally managed to have a complete state chart.

I first decided to design the welcome screen (made by Andreas) and the game logic. Here is the diagram :

Welcome Screen and Game State

Then I refined the game part with this diagram that show the losing, winning, pausing and playing states:

The game

Of course here it is pretty basic, no levels, no points management. It will come later but we have to start with something.

The next step was to create the behavior of submarines, they basically move from one edge to another and they have a return animation. Here is the chart :

Sumarines

We have bombs and torpedoes too. It is pretty basic. When they are launched, if they don’t hit anything, then the execution is finished. If they hit we destroy the target and finish.

Bombs and Torpedos

The last item is the boat. It is a bit more complex because user can control it with keys, it can move right and left, and drop bombs. It has three speeds and user can decrease the speed by pressing the opposite key of the current direction and increase it by pressing the key of the current direction. Here is the boat chart :

The boat

After some drawing i ended up implementing that in Sub-Attaq. The implementation was a success, I killed lots of code, simplified the whole code base and made the code very clean. It allows you to quickly understand all behaviors by just reading the definition of all state-machines. So let’s take some examples with a classic before/after.

* Pausing the game : Before i had a boolean in my scene that i was setting to true when user was pressed P and when P was pressed again i was setting it to false and restarted all animations. I was connected to this key press action and the slot looked like this:


void onPauseKeyPressed() {
if (pause) {
AnimationManager::self()->resumeAll();
scene->boat->setEnabled(false);
pause = false;
} else {
AnimationManager::self()->pauseAll();
scene->boat->setEnabled(true);
scene->boat->setFocus();
pause = true;
}
}

Now, I have defined a pause state and a play state with two transitions (that check the P key) from pause to play and play back to pause.


/*We have one view, it receive the key press event*/
QKeyEventTransition *pressPplay = new QKeyEventTransition(scene->views().at(0),QEvent::KeyPress,Qt::Key_P);
QKeyEventTransition *pressPpause = new QKeyEventTransition(scene->views().at(0),QEvent::KeyPress,Qt::Key_P);


/*Pause "P" is triggered, the player pause the game*/
playState->addTransition(pressPplay, pauseState);


/*To get back playing when the game has been paused*/
pauseState->addTransition(pressPpause, playState);

Pause state looks like this :


void PauseState::onEntry()
{
AnimationManager::self()->pauseAll();
scene->boat->setEnabled(false);
}
void PauseState::onExit()
{
AnimationManager::self()->resumeAll();
scene->boat->setEnabled(true);
scene->boat->setFocus();
}

Nicer, isn’t it?

* Submarines : Before, I had to connect the submarine to the moving animation’s finished signal in order to launch the rotation animation. And after, I had to connect the rotation animation finished signal to an another slot that would run the moving animation again when the rotation was finished.

Now here is the state machine for the submarine :


/*This state is when the boat is moving/rotating*/
QState *moving = new QState(machine->rootState());


/*This state is when the boat is moving from left to right and right to left*/
MovementState *movement = new MovementState(this,moving);


/*This state is when the boat is rotating*/
ReturnState *rotation = new ReturnState(this,moving);


/*This is the initial state of the moving root state*/
moving->setInitialState(movement);


/*This is the initial state of the machine*/
machine->setInitialState(moving);


/*End*/
QFinalState *final = new QFinalState(machine->rootState());


/*If the moving animation is finished we move to the return state*/
movement->addFinishedTransition(rotation);


/*If the return animation is finished we move to the moving state*/
rotation->addFinishedTransition(movement);


/*### Add a nice animation when the submarine is destroyed*/
moving->addTransition(this, SIGNAL(subMarineDestroyed()),final);

The code when we enter in MovementState (which is a QAnimationState) is like this :


void onEntry()
{
if (submarine->currentDirection() == SubMarine::Left) {
movementAnimation->setEndValue(QPointF(0,submarine->y()));
movementAnimation->setDuration(submarine->x()/submarine->currentSpeed()*15);
}
else /*if (submarine->currentDirection() == SubMarine::Right)*/ {
movementAnimation->setEndValue(QPointF(submarine->scene()->width()-submarine->size().width(),submarine->y()));
movementAnimation->setDuration((submarine->scene()->width()-submarine->size().width()-submarine->x())/submarine->currentSpeed()*15);
}
movementAnimation->setStartValue(submarine->pos());
QAnimationState::onEntry();
}

Then the animation will be played, and when it is finished, the machine will enter the rotation/return state. Isn’t it easier to read?

To conclude, I can say that if you can use the state machine approach in your project, DO IT! The code is much more clean, well defined and more readable, and avoids spaghetti code. It makes it easier to thinking of all states before coding.

For Sub-Attaq, the next step is to implement different levels, a score logic and a top ten highscore. I would like to add animations (e.g. explosions) but I need some graphics to achieve it and (cough cough) I am pretty bad at that. And I would like to have different kinds of submarines too.

To finish let’s show a video of the game :

QShare(this)

No related posts.


9 comments

1 Dan Leinir Turthra Jensen April 1, 2009 at 10:36 am
 

Most amazing bit of stuff i’ve seen in a while – this makes me only that much more up on doing that thesis on Behaviour Trees that i and another guy here at uni have been pondering on doing the next two semesters… Yeah, related due to test implementation being planned as using Qt ;)

2 aamer4yu April 1, 2009 at 7:52 pm
 

This state machine can be quite interesting thing.. It will make a better cordination betwen requirements and implementation.
Wonder if Rational Rose like thing is for Qt (Cute Rose :-D ),,, say with Qt 5 ;-)

3 Michael Pyne April 1, 2009 at 8:10 pm
 

Drop bombs on submarines?!? How heartless is that?? ;)

4 Wysota April 2, 2009 at 2:08 am
 

I remember an “opposite” game from 8 bit Atari computers. I think it was called “Seawolf” and you were controlling a submarine and shooting torpedoes at ships floating on the surface with different speeds (I remember there were yellow motorboats that were pretty fast and hard to hit). Some of the ships were dropping depth charges and between the submarine and the ships there was a belt of slowly moving mines that were obscuring the target. There was even a two player mode so you could have played with a friend. Great game, many hours spent on it… It was about twenty years ago :)

5 Wysota April 2, 2009 at 2:10 am
 

Aaah… I even found a screenshot :)

http://www.atarimania.com/8bit/screens/seawolf_ii_gun_fight_3.gif

6 alimbourg April 2, 2009 at 8:36 am
 

Add sounds. If you have some time for that: more than animations, it’s 40% of the overall effect. Really :)

7 alexis.menard April 2, 2009 at 3:33 pm
 

@alimbourg : Sound are planned with phonon.

8 Ben Schleimer April 5, 2009 at 6:56 pm
 

Ah I see you’ve discovered the joys of manually laying out the statechart.
If you ask me, it’s much easier to do statechart development using an automatic layout system.
Eg, code the statechart using xml and use action names to denote enter/exot/transition actions and
encode conditions as an action returning a bool. Then you can compose conditions together.

Then use an xml->dot converter to generate the diagram. and write a xml->C++ translator.
Or you can skip writing the xml yourself and go straight for UML notation using rational rose (nod to aamer4yu)
and write a UML->C++ converter. Note that UML notation is extremely generalized (with orthogonal states and allowing transitions to cross between orthogonal states. ) and would be hard to constrain what is possible with your state model.
(eg. when are the actions processed, how do you determine what the final set of states is given an input set of states and an event, what effect can an action have on conditions, etc, etc)

Anyway, good to see that state programming is going places… :)

9 yaayaa April 10, 2009 at 10:31 am
 

What IDE/GUI did you use to make the State machines (screenshots in your post) ?

Thanks

Comments on this entry are closed.

Previous post:

Next post: