Ранее я уже рассказывал о разработке небольшого игрового Framework-а с использованием инструментальной платформы Marmalade. Разумеется, в том виде, в котором он выложен на GitHub он вряд ли пригоден для разработки чего-то более сложного чем демонстрационное приложение. В нем не хватает многих возможностей, необходимых для разработки более-менее серьезного приложения. К счастью, Framework спроектирован достаточно гибко, чтобы недостающие возможности можно было легко добавить.
Речь пойдет о реализации режима паузы. Я думаю, эта возможность будет полезна практически в любой игре. При активации паузы, весь игровой процесс должен на неопределенное время замирать, а при ее снятии, продолжаться с того-же места, где он остановился.
Главное, что необходимо сделать — остановить обработку всех событий (метод update) для спрайтов, анимация которых должна приостанавливаться на время паузы. Также, необходим какой-то механизм, определяющий будет ли объект приостанавливать свою анимацию на время паузы. Понятно, что если мы приостановим обработку событий для кнопки, отключающей режим паузы, то выйти из паузы нам не удастся никогда.
Менее очевидным является то, что мы должны корректировать состояние всех объектов, участвовавших в анимации на момент начала паузы, при ее снятии. Дело в том, что Framework спроектирован таким образом, что все timestamp-ы, управляющие анимацией спрайтов, рассчитываются заранее, еще до начала анимации. Если мы проведем в состоянии паузы достаточно большое время, то все эти timestamp-ы устареют и анимация закончится раньше чем мы успеем увидеть какое-то ее продолжение.
Начнем с интерфейсов. В метод IObject.update добавим флаг isPaused, который будет устанавливаться в состояние true, в случае если активирован режим паузы:
class IObject {
public:
...
virtual void update(uint64 timestamp, bool isPaused) = 0;
};
В AbstractSpriteOwner добавиться аналогичный флаг и метод, который мы будем вызывать для корректировки таймстампов анимированных объектов при завершении паузы:
class AbstractSpriteOwner: public ISpriteOwner {
public:
...
virtual void update(uint64 timestamp, bool isPaused);
virtual void correctPauseDelta(uint64 pauseDelta);
};
Реализация метода correctPauseDelta тривиальна. Мы просто передаем продолжительность паузы в миллисекундах всем вложенным спрайтам:
...
void AbstractSpriteOwner::correctPauseDelta(uint64 pauseDelta) {
for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) {
p->second->addPauseDelta(pauseDelta);
}
}
В реализации AnimatedSprite мы добавляем полученную продолжительность паузы ко всем таймстампам текущих анимаций. Также мы добавляем досрочный выход из метода update в случае, если действует режим паузы:
...
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;
...
}
Метод isPausable по умолчанию определяется в AbstractScreenObject и, при необходимости, будет переопределяться в тех его наследниках, отключать анимацию которых не стоит:
class AbstractScreenObject: public IScreenObject {
public:
...
virtual void addPauseDelta(uint64 pauseDelta) {}
virtual bool isPausable() const {return true;}
};
Чтобы внести изменения в CompositeSprite, необходимо помнить, что он, с одной стороны, является контейнером спрайтов, в то время как с другой, сам является наследником AnimatedSprite и может обрабатывать правила анимации:
...
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);
}
Основную работу по приостановке и возобновлению анимации возьмет на себя класс Scene.
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();
};
...
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;
}
Осталось добавить код приостановки фоновой музыки в класс Desktop:
class Desktop {
private:
...
bool isMusicStarted;
public:
Desktop(): touches(), names(), currentScene(NULL), isMusicStarted(false) {}
...
void startMusic(const char* res);
void stopMusic();
void suspend();
void resume();
};
...
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();
}
}
Теперь, метод Scene.suspend умеет приостанавливать анимацию всех игровых спрайтов и фоновой музыки, а метод Scene.resume возобновлять их с того-же места.
Автор: GlukKazan