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