Framework в Мармеладе (часть 3)

от автора

Сегодня мы продолжим описание разработки Marmalade Framework, начатой в 1 части статей этого цикла, усовершенствовав работу с графическими ресурсами, а также добавив работу со звуком и группами изображений, при помощи которых мы обеспечим локализацию приложений.

В первую очередь, внимательно посмотрим на то, как мы загружаем графические ресурсы в Sprite.cpp:

Sprite.cpp:

void Sprite::addImage(const char*res, int state) {     img = Iw2DCreateImage(res); } 

Очевидно, что если одно и то-же изображение будет использоваться многократно, мы загрузим его в память столько раз, сколько спрайтов создадим. Работая таким образом, мы быстро исчерпаем тот скудный объем оперативной памяти, который нам доступен на мобильных платформах. Нам необходим менеджер ресурсов, позволяющий использовать единожды загруженные ресурсы многократно.

ResourceManager.h:

 #ifndef _RESOURCEMANAGER_H_ #define _RESOURCEMANAGER_H_  #include <map> #include <string>  #include "s3e.h" #include "IwResManager.h" #include "IwSound.h"  #include "ResourceHolder.h"  using namespace std;  class ResourceManager {     private:            map<string, ResourceHolder*> res;     public:         ResourceManager(): res() {}         void init();         void release();         ResourceHolder* load(const char* name, int loc);      typedef map<string, ResourceHolder*>::iterator RIter;     typedef pair<string, ResourceHolder*> RPair; };  extern ResourceManager rm;         #endif    // _RESOURCEMANAGER_H_ 

ResourceManager.cpp:

#include "ResourceManager.h" #include "Locale.h"  ResourceManager rm;  void ResourceManager::init() {     IwResManagerInit(); }  void ResourceManager::release() {     for (RIter p = res.begin(); p != res.end(); ++p) {         delete p->second;     }     res.clear();     IwResManagerTerminate(); }  ResourceHolder* ResourceManager::load(const char* name, int loc) {     ResourceHolder* r = NULL;     string nm(name);     RIter p = res.find(nm);     if (p == res.end()) {         r = new ResourceHolder(name, loc);         res.insert(RPair(nm, r));     } else {         r = p->second;     }     return r; } 

Обратите внимание, что мы должны очищать всю динамически выделенную память, в противном случае, мы получим ошибку при завершении приложения. Указатель на загруженный графический ресурс будет храниться в ResourceHolder.

ResourceHolder.h:

#ifndef _RESOURCEHOLDER_H_ #define _RESOURCEHOLDER_H_  #include <string>  #include "s3e.h" #include "Iw2D.h" #include "IwResManager.h"  using namespace std;  class ResourceHolder {     private:         string name;         int loc;         CIw2DImage* data;     public:         ResourceHolder(const char* name, int loc);        ~ResourceHolder() {unload();}         void load();         void unload();         CIw2DImage* getData(); };         #endif    // _RESOURCEHOLDER_H_ 

ResourceHolder.cpp:

#include "ResourceHolder.h" #include "Locale.h"  ResourceHolder::ResourceHolder(const char* name, int loc): name(name)                                                , loc(loc)                                                , data(NULL) { }  void ResourceHolder::load() {     if (data == NULL) {         CIwResGroup* resGroup;         const char* groupName = Locale::getGroupName(loc);         if (groupName != NULL) {             resGroup = IwGetResManager()->GetGroupNamed(groupName);             IwGetResManager()->SetCurrentGroup(resGroup);             data = Iw2DCreateImageResource(name.c_str());         } else {             data = Iw2DCreateImage(name.c_str());         }     } }  void ResourceHolder::unload() {     if (data != NULL) {         delete data;         data = NULL;     } }  CIw2DImage* ResourceHolder::getData() {     load();     return data; } 

В методе ResourceHolder::load можно заметить, что получив имя, мы сначала пытаемся, загрузив какую-то группу (в зависимости от значения полученного в loc) найти ресурс в ней и, если это не удалось, используем имя для загрузки файла. Дело в том, что Marmalade позволяет размещать изображения (и прочие ресурсы) в так называемые группы, чтобы загружать их, как единое целое.

Мы используем этот факт, чтобы обеспечить локализацию приложений. В зависимости от значения параметра loc мы будем загружать группу изображений, связанных с языковыми настройками, имена же самих ресурсов, внутри групп, будут совпадать (сами файлы будут размещены в разных каталогах). Для того, чтобы определить группу, необходимо создать текстовый файл с именем группы и расширением group. Ниже пример определения такого файла для группы изображений.

locale_ru.group:

CIwResGroup {     name "locale_ru"      "./locale_ru/play.png"     "./locale_ru/setup.png"     "./locale_ru/musicoff.png"     "./locale_ru/musicon.png"     "./locale_ru/soundoff.png"     "./locale_ru/soundon.png" } 

Говоря о группах, следует дать две важных рекомендации:

  • Не забывайте о том, что при загрузке файла, имя следует указывать с расширением, а для загрузки изображения из группы, следует использовать одноименный ресурс без расширения
  • Не следует помещать изображения в группы без необходимости (особенно изображения большого размера), поскольку внутри группы они хранятся в неупакованном состоянии и занимают гораздо больше места, чем при использовании упакованных графических форматов (таких как PNG), что напрямую повлияет на размер дистрибутива

Текущую локаль на устройстве мы можем определять кроссплатформенно, используя следующий код.

Locale.h:

#ifndef _LOCALE_H_ #define _LOCALE_H_  enum ELocale {     elNothing       = 0x0,     elImage         = 0x1,     elSound         = 0x2,     elEnImage       = 0x5,     elRuImage       = 0x9,     elEnSound       = 0x6,     elRuSound       = 0xA };  class Locale {     public:         static int getCurrentImageLocale();         static int getCurrentSoundLocale();         static int getCommonImageLocale() {return elImage;}         static int getCommonSoundLocale() {return elSound;}         static const char* getGroupName(int locale); };         #endif    // _LOCALE_H_ 

Locale.cpp:

#include "Locale.h" #include "s3e.h"  const char* Locale::getGroupName(int locale) {     switch (locale) {         case   elImage: return "images";         case elEnSound:         case elRuSound:         case   elSound: return "sounds";         case elEnImage: return "locale_en";         case elRuImage: return "locale_ru";                default: return NULL;     } }  int Locale::getCurrentImageLocale() {     int32 lang = s3eDeviceGetInt(S3E_DEVICE_LANGUAGE);     switch (lang) {         case S3E_DEVICE_LANGUAGE_RUSSIAN: return elRuImage;         default: return elEnImage;     } }  int Locale::getCurrentSoundLocale() {     int32 lang = s3eDeviceGetInt(S3E_DEVICE_LANGUAGE);     switch (lang) {         case S3E_DEVICE_LANGUAGE_RUSSIAN: return elRuSound;         default: return elEnSound;     } } 

Осталось добавить в наш проект поддержку работы со звуком. Мы будем использовать две подсистемы:

  • S3E Audio — для проигрывания фоновой музыки (поддерживает ряд кодеков и стерео)
  • S3E Sound — для проигрывания звуковых эффектов (поддерживает возможность одновременного проигрывания нескольких звуков)

Принципы работы с этими подсистемами хорошо описаны в этой статье и я не буду подробно на них останавливаться. Опишу лишь изменения, которые необходимо внести в проект.

В файл настроек приложения мы добавляем параметр, управляющий количеством одновременно проигрываемых звуков.

app.icf:

[SOUND] MaxChannels=16 

В файл проекта добавляем следующие описания.

mf.mkb:

#!/usr/bin/env mkb options {     module_path="$MARMALADE_ROOT/examples" }  subprojects {     iw2d     iwresmanager     SoundEngine } ... files {     ...     [Data]     (data)     locale_en.group     locale_ru.group     sounds.group }  assets {     (data)     background.png     sprite.png     music.mp3      (data-ram/data-gles1, data)     locale_en.group.bin     locale_ru.group.bin     sounds.group.bin } 

Описание группы звуковых ресурсов будет выглядеть следующим образом.

sounds.group:

CIwResGroup {     name "sounds"      "./sounds/menubutton.wav"     "./sounds/sound.wav"      CIwSoundSpec     {         name        "menubutton"         data        "menubutton"         vol         0.9         loop        false     }      CIwSoundSpec     {         name        "sound"         data        "sound"         vol         0.9         loop        false     }      CIwSoundGroup     {         name        "sound_effects"         maxPolyphony     8         killOldest    true         addSpec        "menubutton"         addSpec        "sound"     } } 

Здесь определены два звуковых эффекта и мы можем управлять их настройками (например громкостью vol). В менеджере ресурсов добавляем инициализацию и завершение звуковой подсистемы.

ResourceManager.cpp:

void ResourceManager::init() {     IwResManagerInit(); #ifdef IW_BUILD_RESOURCES     IwGetResManager()->AddHandler(new CIwResHandlerWAV); #endif     IwGetResManager()->LoadGroup("sounds.group");     if (Locale::getCurrentImageLocale() == elEnImage) {         IwGetResManager()->LoadGroup("locale_en.group");     }     if (Locale::getCurrentImageLocale() == elRuImage) {         IwGetResManager()->LoadGroup("locale_ru.group");     } }  void ResourceManager::release() {     for (RIter p = res.begin(); p != res.end(); ++p) {         delete p->second;     }     res.clear();     IwResManagerTerminate();     IwSoundTerminate(); } 

В Main.cpp не забываем добавить вызов метода update для звуковой подсистемы:

Main.cpp:

#include "Main.h"  #include "s3e.h" #include "Iw2D.h" #include "IwGx.h" #include "IwSound.h"  #include "ResourceManager.h" #include "TouchPad.h" #include "Desktop.h" #include "Scene.h" #include "Background.h" #include "Sprite.h"  void init() { 	// Initialise Mamrlade graphics system and Iw2D module 	IwGxInit();     Iw2DInit();  	// Init IwSound 	IwSoundInit();  	// Set the default background clear colour 	IwGxSetColClear(0x0, 0x0, 0x0, 0);  	// Initialise the resource manager 	rm.init();  	touchPad.init(); 	desktop.init(); }  void release() { 	desktop.release(); 	touchPad.release();  	// Shut down the resource manager 	rm.release();  	Iw2DTerminate(); 	IwGxTerminate(); }  int main() {     init();    {          Scene scene;         new Background(&scene, "background.png", 1, elNothing);         new Sprite(&scene, "sprite.png", 122, 100, 2, elNothing);         desktop.setScene(&scene);          int32 duration = 1000 / 25;         // Main Game Loop         while (!desktop.isQuitMessageReceived()) {             // Update keyboard system             s3eKeyboardUpdate();             // Update Iw Sound Manager             IwGetSoundManager()->Update();             // Update             touchPad.update();             uint64 timestamp = s3eTimerGetMs();             desktop.update(timestamp);              // Clear the screen             IwGxClear(IW_GX_COLOUR_BUFFER_F | IW_GX_DEPTH_BUFFER_F);             touchPad.clear();             // Refresh             desktop.refresh();             // Show the surface             Iw2DSurfaceShow();             // Yield to the opearting system             s3eDeviceYield(duration);         }     }     release();     return 0; } 

Группы ресурсов компилируются из описания при выполнении метода LoadGroup под отладчиком. Если мы что-то сделали неправильно, то получим сообщение об ошибке:

image

В результате компиляции, появляется каталог data-ram/data-gles1, содержащий двоичное представление загруженных групп. Работая под отладчиком, мы можем удалять содержимое этого каталога (оно будет пересоздано), но при выполнении сборки под мобильную платформу (iOS или Android) оно должно присутствовать. В противном случае, сборка завершиться с ошибкой.

Завершая разговор о ресурсах, следует упомянуть о еще одном интересном моменте. Мы легко можем обеспечить персистентность при помощи следующей настройки:

app.icf:

[S3E] DataDirIsRAM=1 

В результате, каталог data, ранее содержащий файлы ресурсов, доступных только на чтение, становится доступен на запись. Эта возможность кроссплатформенна и мы можем создавать файлы, для хранения настроек приложения, как на iOS, так и на Android. Реализацию менеджера хранимых данных я приводить не буду, поскольку она тривиальна. Желающие могут самостоятельно посмотреть ее здесь.

В следующей статье мы рассмотрим вопрос создания анимированных и составных спрайтов.

При разработке Marmalade Framework использовались следующие материалы

ссылка на оригинал статьи http://habrahabr.ru/post/161959/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *