Earlier, I already talked about developing a small gaming 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 the demo application. It lacks many of the features needed to develop a more or less serious application. Fortunately, the Framework is designed flexibly enough so that the missing features can be easily added.
It will be about the implementation of the pause mode. I think this feature will be useful in almost any game. When activating the pause, the whole game process should freeze indefinitely, 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 (the update method) for sprites, the animation of which should be suspended for the duration of the pause. Also, some mechanism is needed that determines whether the object will pause its animation for the duration of the pause. It is clear that if we suspend event handling for a button that disables pause mode, we will never be able to get out of the pause.
Less obvious is that we have to correct the state of all objects that participated in the animation at the time the pause began, when it was removed. The fact is that the Framework is designed in such a way that all the timestamps that control the animation of the sprites are calculated in advance, even before the animation begins. If we spend a sufficiently long time in a state of pause, then all these timestamps will become obsolete and the animation will end sooner than we can see some continuation of it.
')
Let's start with the interfaces. In the IObject.update method, add the isPaused flag, which will be set to true if pause mode is activated:
IObject.hclass IObject { public: ... virtual void update(uint64 timestamp, bool isPaused) = 0; };
In AbstractSpriteOwner, we will add a similar flag and method that we will call to adjust the timestamps of animated objects when the pause is completed:
AbstractSpriteOwner.h class AbstractSpriteOwner: public ISpriteOwner { public: ... virtual void update(uint64 timestamp, bool isPaused); virtual void correctPauseDelta(uint64 pauseDelta); };
Implementing the correctPauseDelta method is trivial. We simply pass the duration of the pause 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 AnimatedSprite implementation, we add the resulting pause length to all timestamps of current animations. We also add an early exit from the update method if pause mode is in effect:
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 default isPausable method is defined in AbstractScreenObject and, if necessary, will be redefined in those of its heirs, which should not be turned off:
AbstractScreenObject.h class AbstractScreenObject: public IScreenObject { public: ... virtual void addPauseDelta(uint64 pauseDelta) {} virtual bool isPausable() const {return true;} };
To make changes to the CompositeSprite, you need to remember that, on the one hand, it is a container of sprites, while on the other hand, it is the successor 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 the suspension and resumption of animation will take the class Scene.
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.