Writing a game with the animation and the state machine API

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 :


Blog Topics:

Comments