четверг, 25 октября 2012 г.

8. Сохраняем данные

Для того, чтобы завершить Framework, нам не хватает маленькой, но очень важной детали. Для нас просто необходимо научиться сохранять данные. Разумеется, если разрабатывается сложная игра и приходится хранить большие объемы сложноструктурированной информации, без БД (наподобие Sqlite) не обойтись, но часто, вполне достаточно иметь возможность сохранения нескольких числовых значений. 
В нашем примере, требуется сохранить два булевских флага, управляющих настройками звуковых эффектов и фоновой музыки. И если нам удастся записать их в простой двоичный файл, мы будем вполне счастливы. Добавим в файл настроек app.icf следующую строку:

[S3E]
DataDirIsRAM=1

В результате этого, каталог data, в котором мы храним наше ресурсы и ранее  доступный только на чтение, становится доступным на запись. Самое замечательное в этом, что таким же доступным на запись он остается и при установке приложения на Android или iPhone (про другие платформы не скажу, поскольку не проверял). Остальное - дело техники. Добавим в проект класс, который гордо назовем DataManager-ом:

DataManager.h:

#ifndef _DATAMANAGER_H_
#define _DATAMANAGER_H_

#define FILE_NAME "props.cfg"

#include "s3eFile.h"

enum EDataProperty {
    edpSoundOn                = 0,
    edpMusicOn                = 1,
    SLOTS_CNT                 = 2
};

class DataManager {
    public:
        DataManager(): isModified(false), IsInitialized(false) {}
        void init();
        void release();
        void flush();
        int  getValue(int propId);
        void setValue(int propId, int propValue);
        void setMinValue(int propId, int propValue);
        void setMaxValue(int propId, int propValue);
        const char* getError() {return error;}
        void  setInitialized() {IsInitialized = true;}
        bool  isInitialized() const {return IsInitialized;}
    private:
        bool load();
        void initialize();
        bool isModified;
        void checkError();
        int32 data[SLOTS_CNT];
        const char* error;
        bool  IsInitialized;
};

extern DataManager dm;
       
#endif    // _DATAMANAGER_H_


DataManager.cpp:

#include "DataManager.h"

DataManager dm;

void DataManager::init() {
    initialize();
    load();
}

void DataManager::release() {
    flush();
}

void DataManager::initialize() {
    data[edpSoundOn]        = 1;
    data[edpMusicOn]        = 1;
}

int DataManager::getValue(int propId) {
    if (propId < SLOTS_CNT) {
        return data[propId];
    }
    return 0;
}

void DataManager::setValue(int propId, int propValue) {
    if (propId < SLOTS_CNT) {
        data[propId] = propValue;
        isModified = true;
    }
}

void DataManager::setMinValue(int propId, int propValue) {
    if (propId < SLOTS_CNT) {
        if ((data[propId] == 0)||(data[propId] > propValue)) {
            data[propId] = propValue;
            isModified = true;
        }
    }
}

void DataManager::setMaxValue(int propId, int propValue) {
    if (propId < SLOTS_CNT) {
        if ((data[propId] == 0)||(data[propId] < propValue)) {
            data[propId] = propValue;
            isModified = true;
        }
    }
}

void DataManager::checkError() {
    s3eFileGetError();
    error = s3eFileGetErrorString();
}

bool DataManager::load() {
    if (!s3eFileCheckExists(FILE_NAME)) return false;
    s3eFile* h = s3eFileOpen(FILE_NAME, "rb");
    if (h == NULL) {
        getError();
        return false;
    }
    int32 sz = s3eFileGetSize(h);
    if ((sz % sizeof(int32)) != 0) return false;
    sz = sz / sizeof(32);
    if (sz > SLOTS_CNT) {
        sz = SLOTS_CNT;
    }
    if (s3eFileRead((void*)data, sizeof(int32), sz, h) != sz) {
        initialize();
        checkError();
    }
    s3eFileClose(h);
    return true;
}

void DataManager::flush() {
    if (isModified) {
        s3eFile* h = s3eFileOpen(FILE_NAME, "wb");
        if (h == NULL) {
            checkError();
            return;
        }
        if (s3eFileWrite((const void*)data, sizeof(int32), SLOTS_CNT, h) 
                            != SLOTS_CNT) {
            checkError();
        }
        s3eFileClose(h);
        isModified = false;
    }
}


Реализация вполне очевидна и вряд-ли нуждается в комментариях. Далее, добавим вызовы методов dm.init и dm.release в Main.cpp:

#include "Main.h"
...
#include "DataManager.h"

void init() {
   ...
    dm.init();
}

void release() {
    dm.release();
   ...
}
...

Теперь, нужно добавить в SwitchButton возможность сохранения текущего значения кнопки в DataManager:

#include "SwitchButton.h"
#include "Desktop.h"
#include "MoveAction.h"
#include "SendMessageAction.h"
#include "SoundAction.h"
#include "DataManager.h"

SwitchButton::SwitchButton(ISpriteOwner* scene, int x, int y, int zOrder): Button(scene, x, y, zOrder), dataId(-1) {
    SwitchButton::configure();
}

void SwitchButton::setDataId(int id) {
    dataId = id;
    state = dm.getValue(id);
}

void SwitchButton::configure() {
    msgUp->addAction(new SendMessageAction(this, 50, emtSwitch));
}

bool SwitchButton::sendMessage(int msg, uint64 timestamp, void* data) {
    if (msg == emtSwitch) {
        doMessage(msg, 0, timestamp);
        if (receiver != NULL) {
            receiver->sendMessage(message, 0, (IObject*)this);
        }
        if (dataId >= 0) {
            dm.setValue(dataId, state);
        }
        return true;
    }
    return Button::sendMessage(msg, timestamp, data);
}


Осталось связать экземпляры SwitchButton, которые мы создаем в меню IntroSound, с идентификаторами сохраняемых значений:

#include "IntroSound.h"
#include "SwitchButton.h"
#include "Button.h"
#include "Intro.h"
#include "Locale.h"
#include "DataManager.h"

bool IntroSound::init() {
    if (!AbstractScreenObject::init()) return false;
    setXY(346, 227);
    SwitchButton* s = new SwitchButton(this, 0, 0, 1);
        s->addImage("musicoff", 0, Locale::getCurrentImageLocale());
        s->addImage("musicon", 1, Locale::getCurrentImageLocale());
        s->setName("musicon");
        s->setState(0);
        s->setDataId(edpMusicOn);
    s->addReceiver(eimCheckMusic, scene);
    s = new SwitchButton(this, 0, 157, 2);
        s->addImage("soundoff", 0, Locale::getCurrentImageLocale());
        s->addImage("soundon", 1, Locale::getCurrentImageLocale());
        s->setName("soundon");
        s->setState(1);
        s->setDataId(edpSoundOn);
    Button* b = new Button(this, "back.png", -300, 350, 3, 
                           Locale::getCommonImageLocale());
    b->addReceiver(eimBack, scene);
    return true;
}


Теперь наш пример может сохранять настройки работы со звуком, и на этом цикл статей о Marmalade Framework завершен.

Исходники примера доступны здесь.

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

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