вторник, 25 декабря 2012 г.

Сделай паузу

Некоторое время назад, мной был разработан Framework, предназначенный для создания несложных 2D-игр, с использованием инструментальной платформы Marmalade, который я описал в цикле статей этого блога. Разумеется, все что я описывал в статьях было предельно упрощено для того, чтобы не увязнуть в деталях реализации, по ходу изложения. Созданному Framework-у не хватает многих возможностей без которых немыслимо его применение в реальных проектах. Сегодня я хочу рассказать о добавлении одной из них.

Речь пойдет о реализации режима паузы. Я думаю, эта возможность будет полезна практически в любой игре. При активации паузы, весь игровой процесс должен на неопределенное время замирать, а при ее снятии, продолжаться с того-же места, где он остановился. В общем-то понятно, что на время паузы, анимированные спрайты должны приостановить всю обработку в методе update, но Дьявол, как всем известно, в деталях.

Начнем с интерфейсов. В метод IObject.update добавим флаг isPaused:

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 следует сказать особо. Мы должны иметь возможность разделять обычные объекты и объекты анимация которых (а также обработка ими событий) не останавливается в режиме паузы. К таким объектам, например, должно относиться меню, содержащее кнопку выхода из состояния паузы (в противном случае, включив паузу, мы никогда не сможем ее выключить). Определим реализацию метода 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.

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;
}


Осталось добавить код приостановки фоновой музыки в класс Desktop.


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();
    }
}


Теперь мы умеем останавливать время в нашем приложении :)

Проект на GitHub обновлен.

Комментариев нет:

Отправить комментарий