Для того, чтобы завершить Framework, нам не хватает маленькой, но очень важной детали. Для нас просто необходимо научиться сохранять данные. Разумеется, если разрабатывается сложная игра и приходится хранить большие объемы сложноструктурированной информации, без БД (наподобие Sqlite) не обойтись, но часто, вполне достаточно иметь возможность сохранения нескольких числовых значений.
В нашем примере, требуется сохранить два булевских флага, управляющих настройками звуковых эффектов и фоновой музыки. И если нам удастся записать их в простой двоичный файл, мы будем вполне счастливы. Добавим в файл настроек app.icf следующую строку:
[S3E]
DataDirIsRAM=1
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_
#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)
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;
}
}
checkError();
}
s3eFileClose(h);
isModified = false;
}
}
Реализация вполне очевидна и вряд-ли нуждается в комментариях. Далее, добавим вызовы методов dm.init и dm.release в Main.cpp:
#include "Main.h"
...
#include "DataManager.h"
void init() {
#include "DataManager.h"
void init() {
...
dm.init();
}
void release() {
dm.release();
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);
}
#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,
#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;
}
b->addReceiver(eimBack, scene);
return true;
}
Теперь наш пример может сохранять настройки работы со звуком, и на этом цикл статей о Marmalade Framework завершен.
Исходники примера доступны здесь.
Комментариев нет:
Отправить комментарий