
Marmalade Framework (pause mode)
- Tutorial
Earlier, I talked about developing a small game Framework using the Marmalade tool platform . Of course, in the form in which it is posted on GitHub, it is hardly suitable for developing something more complex than a demo application. It lacks many of the features needed to develop a more or less serious application. Fortunately, the Framework is designed flexibly enough to easily add the missing features.
It will be about the implementation of the pause mode. I think this feature will be useful in almost any game. When a pause is activated, the entire game process should freeze for an indefinite period of time, and when it is removed, continue from the same place where it stopped.
The main thing that needs to be done is to stop processing all events (update method) for sprites, the animation of which should be paused for a while. Also, some mechanism is needed to determine whether an object will pause its animation for a while. It is clear that if we suspend event processing for a button that disables the pause mode, we will never be able to exit the pause.
Less obvious is that we must adjust the state of all objects that participated in the animation at the time the pause started, when it was removed. The fact is that the Framework is designed in such a way that all timestamps that control sprite animation are calculated in advance, even before the animation starts. If we spend a sufficiently long time in a pause state, then all these timestamps will become obsolete and the animation will end before we have time to see any continuation of it.
Let's start with the interfaces. Add the isPaused flag to the IObject.update method, which will be set to true if the pause mode is activated:
AbstractSpriteOwner will add a similar flag and method that we will call to adjust the timestamps of animated objects when the pause ends:
The implementation of the correctPauseDelta method is trivial. We simply pass the pause duration in milliseconds to all nested sprites:
In the implementation of AnimatedSprite, we add the obtained pause duration to all timestamps of the current animations. We also add an early exit from the update method if there is a pause mode:
The isPausable method is defined by default in AbstractScreenObject and, if necessary, will be overridden in its descendants, whose animation should not be disabled:
To make changes to CompositeSprite, you need to remember that, on the one hand, it is a container of sprites, while on the other, it is itself an inheritor of AnimatedSprite and can handle animation rules:
The main work on pausing and resuming animation will be done by the Scene class.
It remains to add the background music pause code to the Desktop class:
Now, the Scene.suspend method can pause the animation of all game sprites and background music, and the Scene.resume method can resume them from the same place.
It will be about the implementation of the pause mode. I think this feature will be useful in almost any game. When a pause is activated, the entire game process should freeze for an indefinite period of time, and when it is removed, continue from the same place where it stopped.
The main thing that needs to be done is to stop processing all events (update method) for sprites, the animation of which should be paused for a while. Also, some mechanism is needed to determine whether an object will pause its animation for a while. It is clear that if we suspend event processing for a button that disables the pause mode, we will never be able to exit the pause.
Less obvious is that we must adjust the state of all objects that participated in the animation at the time the pause started, when it was removed. The fact is that the Framework is designed in such a way that all timestamps that control sprite animation are calculated in advance, even before the animation starts. If we spend a sufficiently long time in a pause state, then all these timestamps will become obsolete and the animation will end before we have time to see any continuation of it.
Let's start with the interfaces. Add the isPaused flag to the IObject.update method, which will be set to true if the pause mode is activated:
IObject.h
class IObject {
public:
...
virtual void update(uint64 timestamp, bool isPaused) = 0;
};
AbstractSpriteOwner will add a similar flag and method that we will call to adjust the timestamps of animated objects when the pause ends:
AbstractSpriteOwner.h
class AbstractSpriteOwner: public ISpriteOwner {
public:
...
virtual void update(uint64 timestamp, bool isPaused);
virtual void correctPauseDelta(uint64 pauseDelta);
};
The implementation of the correctPauseDelta method is trivial. We simply pass the pause duration in milliseconds to all nested sprites:
AbstractSpriteOwner.cpp
...
void AbstractSpriteOwner::correctPauseDelta(uint64 pauseDelta) {
for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) {
p->second->addPauseDelta(pauseDelta);
}
}
In the implementation of AnimatedSprite, we add the obtained pause duration to all timestamps of the current animations. We also add an early exit from the update method if there is a pause mode:
AnimatedSprite.cpp
...
void AnimatedSprite::addPauseDelta(uint64 pauseDelta) {
for (CIter p = currentMessages.begin(); p != currentMessages.end(); ++p) {
p->timestamp += pauseDelta;
}
}
void AnimatedSprite::update(uint64 timestamp, bool isPaused) {
if (isPaused && isPausable()) return;
...
}
The isPausable method is defined by default in AbstractScreenObject and, if necessary, will be overridden in its descendants, whose animation should not be disabled:
AbstractScreenObject.h
class AbstractScreenObject: public IScreenObject {
public:
...
virtual void addPauseDelta(uint64 pauseDelta) {}
virtual bool isPausable() const {return true;}
};
To make changes to CompositeSprite, you need to remember that, on the one hand, it is a container of sprites, while on the other, it is itself an inheritor of AnimatedSprite and can handle animation rules:
CompositeSprite.cpp
...
void CompositeSprite::addPauseDelta(uint64 pauseDelta) {
AbstractSpriteOwner::correctPauseDelta(pauseDelta);
AnimatedSprite::addPauseDelta(pauseDelta);
}
void CompositeSprite::update(uint64 timestamp, bool isPaused) {
AnimatedSprite::update(timestamp, isPaused);
AbstractSpriteOwner::update(timestamp, isPaused);
}
The main work on pausing and resuming animation will be done by the Scene class.
Scene.h
class Scene: public AbstractSpriteOwner {
protected:
...
bool IsPaused;
uint64 pauseTimestamp;
public:
...
virtual void update(uint64 timestamp, bool isPaused);
bool isPaused() const {return IsPaused;}
void suspend();
void resume();
};
Scene.cpp
...
void Scene::update(uint64 timestamp, bool) {
if (IsPaused && (pauseTimestamp == 0)) {
pauseTimestamp = pauseTimestamp;
}
if (!IsPaused && (pauseTimestamp != 0)) {
uint64 pauseDelta = timestamp - pauseTimestamp;
if (pauseDelta > 0) {
correctPauseDelta(pauseDelta);
}
pauseTimestamp = 0;
}
...
AbstractSpriteOwner::update(timestamp, IsPaused);
}
bool Scene::sendMessage(int id, int x, int y) {
if (AbstractSpriteOwner::sendMessage(id, x, y)) {
return true;
}
if (IsPaused) return false;
if (background != NULL) {
return background->sendMessage(id, x, y);
}
return false;
}
void Scene::suspend() {
desktop.suspend();
IsPaused = true;
}
void Scene::resume() {
desktop.resume();
IsPaused = false;
}
It remains to add the background music pause code to the Desktop class:
Desktop.h
class Desktop {
private:
...
bool isMusicStarted;
public:
Desktop(): touches(), names(), currentScene(NULL), isMusicStarted(false) {}
...
void startMusic(const char* res);
void stopMusic();
void suspend();
void resume();
};
Desktop.cpp
...
void Desktop::startMusic(const char* res) {
if (s3eAudioIsCodecSupported(S3E_AUDIO_CODEC_MP3) &&
s3eAudioIsCodecSupported(S3E_AUDIO_CODEC_PCM))
s3eAudioPlay(res, 0);
isMusicStarted = true;
}
void Desktop::stopMusic() {
isMusicStarted = false;
s3eAudioStop();
}
void Desktop::suspend() {
if (isMusicStarted) {
s3eAudioPause();
}
}
void Desktop::resume() {
if (isMusicStarted) {
s3eAudioResume();
}
}
Now, the Scene.suspend method can pause the animation of all game sprites and background music, and the Scene.resume method can resume them from the same place.