πŸ“œ ⬆️ ⬇️

Framework in Marmalade (part 3)

Today we will continue the description of the development of the Marmalade Framework , begun in part 1 of the articles in this series, improving the work with graphic resources, as well as adding work with sound and groups of images, with the help of which we will ensure application localization.

First of all, take a closer look at how we load graphic resources into Sprite.cpp:

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


Obviously, if the same image is used repeatedly, we will load it into memory as many times as we create sprites. Working in this way, we will quickly exhaust the meager amount of RAM that is available to us on mobile platforms. We need a resource manager that allows you to use once loaded resources multiple times.

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


Please note that we must clear all dynamically allocated memory, otherwise, we will get an error when the application terminates. A pointer to the downloaded graphic resource will be stored in the 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_ </spoiler> <spoiler title="ResourceHolder.cpp"> <source lang="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; } 


In the ResourceHolder :: load method, you can see that when we get the name, we first try by loading some group (depending on the value obtained in loc) to find the resource in it and, if that failed, use the name to load the file. The fact is that Marmalade allows you to place images (and other resources) into so-called groups in order to upload them as a whole.

We use this fact to provide application localization. Depending on the value of the loc parameter, we will upload a group of images related to the language settings, but the names of the resources themselves, inside the groups, will be the same (the files will be placed in different directories). In order to define a group, you must create a text file with the group name and the extension group. Below is an example of defining such a file for a group of images.

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


Speaking of groups, two important recommendations should be made:


We can define the current locale on the device cross-platform using the following code.

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


It remains to add to our project support for working with sound. We will use two subsystems:


The principles of working with these subsystems are well described in this article and I will not dwell on them in detail. I will describe only the changes that need to be made to the project.

In the application settings file, we add a parameter that controls the number of simultaneously played sounds.

app.icf
 [SOUND] MaxChannels=16 


The following descriptions are added to the project file.

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 } 


The description of the audio resource group will be as follows.

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


Two sound effects are defined here and we can control their settings (for example, the volume of vol). In the resource manager, we add initialization and completion of the audio subsystem.

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


In Main.cpp, do not forget to add a call to the update method for the audio subsystem:

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


Resource groups are compiled from the description when the LoadGroup method is executed under the debugger. If we did something wrong, we get an error message:

image

As a result of the compilation, the data-ram / data-gles1 directory appears, containing the binary representation of the loaded groups. Working under the debugger, we can delete the contents of this directory (it will be recreated), but when building for a mobile platform (iOS or Android) it should be present. Otherwise, the build will fail.

Concluding the conversation about resources, we should mention another interesting point. We can easily ensure persistence with the following setting:

app.icf
 [S3E] DataDirIsRAM=1 


As a result, the data directory, which previously contains read-only resource files, becomes writable. This feature is cross-platform and we can create files to store application settings, both on iOS and on Android. I will not give implementation of the manager of the stored data as it is trivial. Anyone can see it here .

It should be noted that after setting the DataDirIsRAM flag, Marmalade moves the data-gles1 directory containing the compiled group resources from data-ram to data. This does not affect the work under the debugger, since the directory is created automatically when groups are loaded, but it can lead to the fact that the distribution kit for a mobile device (Android or iPhone) can be mistakenly built with irrelevant resources. To prevent this from happening, you need to make a simple change to the project's mkb file:

  ... - (data-ram/data-gles1, data) + (data-ram/data-gles1, data/data-gles1) locale_en.group.bin locale_ru.group.bin sounds.group.bin 

I want to thank Mezomish for pointing out this defect.

In the next article we will complete the development of the Framework and build a small demo application.

The following materials were used in developing the Marmalade Framework .

Source: https://habr.com/ru/post/161959/


All Articles