📜 ⬆️ ⬇️

Framework in Marmalade (part 4)

In the last article of the Marmalade Framework development cycle , we will add animated and composite sprites to the project, as well as build a small demo application illustrating how to use the library.

Since we are developing an event-oriented Framework, the execution of any action (and animation including) will be performed as a result of processing any event. The set of basic events will be defined in the Desktop module.

Desktop.h
#ifndef _DESKTOP_H_ #define _DESKTOP_H_ #include <set> #include "s3eKeyboard.h" #include "Scene.h" using namespace std; enum EMessageType { emtNothing = 0x00, emtHide = 0x01, emtShadow = 0x02, emtShow = 0x03, emtSwitch = 0x04, emtInit = 0x05, emtFix = 0x08, emtStartAnimation = 0x06, emtStopAnimation = 0x07, emtActivate = 0x09, emtSystemMessage = 0x0F, emtTouchEvent = 0x10, emtTouchIdMask = 0x03, emtTouchMask = 0x78, emtMultiTouch = 0x14, emtTouchOut = 0x18, emtTouchDown = 0x30, emtTouchUp = 0x50, emtTouchOutUp = 0x58, emtTouchMove = 0x70, emtSingleTouchDown = 0x30, emtSingleTouchUp = 0x50, emtSingleTouchMove = 0x70, emtMultiTouchDown = 0x34, emtMultiTouchUp = 0x54, emtMultiTouchMove = 0x74, emtKeyEvent = 0x80, emtKeyAction = 0x82, emtKeyDown = 0x81, emtKeyPressed = 0x83, emtKeyReleased = 0x82 }; ... 


As we will see later, we can easily define new events without modifying the Desktop module. Listing EMessageType is nothing more than a convenient way to put all the basic event codes together The task of the animated sprite is to encapsulate the processing of basic events. The animated sprite interface contains only two methods:

IAnimatedSprite.h
 #ifndef _IANIMATEDSPRITE_H_ #define _IANIMATEDSPRITE_H_ #include <string> #include "Desktop.h" using namespace std; class IAnimatedSprite { public: virtual bool isValidMessage(int msg) = 0; virtual void doMessage(int msg, void* data = NULL, uint64 timestamp = 0) = 0; }; #endif // _IANIMATEDSPRITE_H_ 


The isValidMessage method checks whether a sprite can process an event of a specified type (if an event cannot be processed by a sprite, it is passed to its containing container — a composite sprite or scene), and the doMessage method performs processing. In addition to the event code, msg, you can pass a pointer to arbitrary data, data, and a timestamp, which is calculated at each loop iteration in Main.cpp, to the handler.
')
The implementation of AnimatedSprite will look like this:

AnimatedSprite.h
 #ifndef _ANIMATEDSPRITE_H_ #define _ANIMATEDSPRITE_H_ #include <map> #include <vector> #include "Sprite.h" #include "IAnimatedSprite.h" #include "AnimateMessage.h" #include "ResourceManager.h" #define REFRESH_CNT 2 using namespace std; class AnimatedSprite: public Sprite, public IAnimatedSprite { protected: struct Message { Message(int id, uint64 timestamp, void* data = NULL): id(id), timestamp(timestamp), data(data) {} Message(const Message& m): id(m.id), timestamp(m.timestamp), data(m.data) {} int id; void* data; uint64 timestamp; }; struct CurrentMessage { CurrentMessage(AnimateMessage* message, uint64 timestamp): message(message), timestamp(timestamp), lastTimeDelta(0), isEmpty(false) {} CurrentMessage(const CurrentMessage& m): message(m.message), timestamp(m.timestamp), lastTimeDelta(m.lastTimeDelta), isEmpty(m.isEmpty) {} AnimateMessage* message; uint64 timestamp; uint64 lastTimeDelta; bool isEmpty; }; int state; map<int, ResourceHolder*> images; map<int, AnimateMessage*> rules; uint64 lastTimestamp; vector<Message> messages; vector<CurrentMessage> currentMessages; bool isAnimated; int refreshCnt; public: AnimatedSprite(ISpriteOwner* scene, int x, int y, int zOrder = 0); AnimatedSprite(ISpriteOwner* scene, const char* res, int x, int y, int zOrder = 0, int loc = elNothing); ~AnimatedSprite(); void clearMessageRules() {rules.clear();} void addMessageRule(int msg, AnimateMessage* rule); virtual void addImage(const char*res, int id = 0, int loc = 0); virtual CIw2DImage* getImage(int id = 0); virtual int getState(); virtual bool setState(int newState); virtual void update(uint64 timestamp); virtual void refresh(); virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL); virtual bool isBuzy() {return false;} virtual bool isValidMessage(int msg) {return (msg <= emtSystemMessage);} virtual void doMessage(int msg, void* data = NULL, uint64 timestamp = 0); virtual void unload(); typedef map<int, ResourceHolder*>::iterator IIter; typedef pair<int, ResourceHolder*> IPair; typedef map<int, AnimateMessage*>::iterator RIter; typedef pair<int, AnimateMessage*> RPair; typedef vector<Message>::iterator MIter; typedef vector<CurrentMessage>::iterator CIter; }; #endif // _ANIMATEDSPRITE_H_ 


AnimatedSprite.cpp
 #include "AnimatedSprite.h" #include "Desktop.h" #include "Locale.h" AnimatedSprite::AnimatedSprite(ISpriteOwner* scene, int x, int y, int zOrder): Sprite(scene, x, y, zOrder) , state(0) , images() , lastTimestamp(0) , messages() , currentMessages() , isAnimated(false) , refreshCnt(REFRESH_CNT) , rules() {} AnimatedSprite::AnimatedSprite(ISpriteOwner* scene, const char* res, int x, int y, int zOrder, int loc): Sprite(scene, x, y, zOrder) , state(0) , images() , lastTimestamp(0) , messages() , currentMessages() , isAnimated(false) , refreshCnt(REFRESH_CNT) , rules() { AnimatedSprite::addImage(res, 0, loc); } AnimatedSprite::~AnimatedSprite() { for (RIter p = rules.begin(); p != rules.end(); ++p) { delete p->second; } } void AnimatedSprite::unload() { for (IIter p = images.begin(); p != images.end(); ++p) { p->second->unload(); } } void AnimatedSprite::addMessageRule(int msg, AnimateMessage* rule) { RIter p = rules.find(msg); if (p != rules.end()) { return; } rules.insert(RPair(msg, rule)); } void AnimatedSprite::addImage(const char*res, int id, int loc) { ResourceHolder* img = rm.load(res, loc); images.insert(IPair(id, img)); } bool AnimatedSprite::setState(int newState) { IIter p = images.find(newState); if (p == images.end()) { return false; } state = newState; return true; } CIw2DImage* AnimatedSprite::getImage(int id) { IIter p = images.find(id); if (p == images.end()) { return NULL; } return p->second->getData(); } int AnimatedSprite::getState() { return state; } void AnimatedSprite::doMessage(int msg, void* data, uint64 timestamp) { init(); int s = getState(); switch (msg) { case emtStartAnimation: isAnimated = true; break; case emtStopAnimation: isAnimated = false; break; case emtSwitch: s++; if (getImage(s) == NULL) { s = 0; } setState(s); return; case emtHide: isVisible = false; return; case emtShadow: isVisible = true; alpha = IW_2D_ALPHA_HALF; return; case emtShow: isVisible = true; alpha = IW_2D_ALPHA_NONE; return; }; if (timestamp == 0) { timestamp = s3eTimerGetMs(); } RIter p = rules.find(msg); if (p != rules.end()) { for (CIter q = currentMessages.begin(); q != currentMessages.end(); ++q) { if (q->isEmpty) { q->isEmpty = false; q->message = p->second; q->timestamp = timestamp; q->lastTimeDelta = 0; return; } } currentMessages.push_back(CurrentMessage(p->second, timestamp)); } } bool AnimatedSprite::sendMessage(int msg, uint64 timestamp, void* data) { if (!isValidMessage(msg)) { return false; } if (timestamp <= lastTimestamp) { doMessage(msg, data); return true; } messages.push_back(Message(msg, timestamp, data)); return true; } void AnimatedSprite::update(uint64 timestamp) { bool isEmpty = true; for (MIter p = messages.begin(); p != messages.end(); ++p) { if (p->timestamp <= lastTimestamp) continue; if (p->timestamp <= timestamp) { doMessage(p->id, p->data, p->timestamp); continue; } isEmpty = false; } if (isEmpty) { messages.clear(); } isEmpty = true; for (CIter p = currentMessages.begin(); p != currentMessages.end(); ++p) { if (p->isEmpty) continue; uint64 timeDelta = timestamp - p->timestamp; if (!p->message->update(timeDelta, p->lastTimeDelta)) { p->isEmpty = true; continue; } p->lastTimeDelta = timeDelta; isEmpty = false; } if (isEmpty) { currentMessages.clear(); } lastTimestamp = timestamp; } void AnimatedSprite::refresh() { if (isAnimated) { if (--refreshCnt <= 0) { refreshCnt = REFRESH_CNT; doMessage(emtSwitch); } } Sprite::refresh(); } 


Unlike a regular sprite, an animated one can load multiple images by associating them with some numeric values. These values ​​will be referred to as sprite states. Event handling will be done in doMessage.

Another important component of our library will be a composite sprite, allowing you to manipulate a set of sprites (including composite and animated) as a single entity. The implementation of this class is very primitive:

CompositeSprite.h
 #ifndef _COMPOSITESPRITE_H_ #define _COMPOSITESPRITE_H_ #include "AnimatedSprite.h" #include "AbstractSpriteOwner.h" #include "Scene.h" class CompositeSprite: public AnimatedSprite , public AbstractSpriteOwner { protected: ISpriteOwner* owner; public: CompositeSprite(ISpriteOwner* scene, int x, int y, int zOrder); virtual int getXSize(int xSize); virtual int getYSize(int ySize); virtual int getXPos(int x); virtual int getYPos(int y); virtual bool setState(int newState); virtual void refresh(); virtual void update(uint64 timestamp); virtual bool isBuzy(); virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL); virtual bool sendMessage(int msg, int x, int y); virtual void unload(); }; #endif // _COMPOSITESPRITE_H_ 


CompositeSprite.cpp
 #include "CompositeSprite.h" CompositeSprite::CompositeSprite(ISpriteOwner* scene, int x, int y, int zOrder): AnimatedSprite(scene, x, y, zOrder), owner(scene), AbstractSpriteOwner() {} int CompositeSprite::getXSize(int xSize) { return owner->getXSize(xSize); } int CompositeSprite::getYSize(int ySize) { return owner->getYSize(ySize); } int CompositeSprite::getXPos(int x) { return AbstractScreenObject::getXPos() + owner->getXPos(x); } int CompositeSprite::getYPos(int y) { return AbstractScreenObject::getYPos() + owner->getYPos(y); } void CompositeSprite::refresh() { if (isVisible) { init(); AbstractSpriteOwner::refresh(); } } void CompositeSprite::update(uint64 timestamp) { AnimatedSprite::update(timestamp); AbstractSpriteOwner::update(timestamp); } bool CompositeSprite::isBuzy() { return AnimatedSprite::isBuzy(); } bool CompositeSprite::sendMessage(int msg, uint64 timestamp, void* data) { return AnimatedSprite::sendMessage(msg, timestamp, data) || owner->sendMessage(msg, timestamp, data); } bool CompositeSprite::sendMessage(int msg, int x, int y) { if (!isVisible) return false; return AbstractSpriteOwner::sendMessage(msg, x, y); } bool CompositeSprite::setState(int newState) { state = newState; return true; } void CompositeSprite::unload() { AbstractSpriteOwner::unload(); } 


In order to be able to define a fairly complex animation, we define the rules of animation, in accordance with the following scheme:

image

Several collections are added to AnimatedSprite. The rules will contain the correspondence of the numeric codes of the messages to some of the more detailed AnimateMessage animation rules. The currentMessages list will contain the list of rules that are used for the animation at the present time (several rules can be processed at the same time), the messages will contain the messages to be processed.

In turn, AnimateMessage will contain a list of basic AnimateAction actions, for each of which the start and end time of the action will be specified, relative to the start time of the animation (these values ​​will be the same for actions performed one time).

AnimateMessage.h
 #ifndef _ANIMATEMESSAGE_H_ #define _ANIMATEMESSAGE_H_ #include <set> #include "s3eTypes.h" #include "AnimateAction.h" using namespace std; class AnimateMessage { private: set<AnimateAction*> actions; public: AnimateMessage(); ~AnimateMessage(); bool update(uint64 newDelta, uint64 oldDelta); void addAction(AnimateAction* action) {actions.insert(action);} typedef set<AnimateAction*>::iterator AIter; }; #endif // _ANIMATEMESSAGE_H_ 


AnimateMessage.cpp
 #include "AnimateMessage.h" AnimateMessage::AnimateMessage(): actions() {} AnimateMessage::~AnimateMessage() { for (AIter p = actions.begin(); p != actions.end(); ++p) { delete *p; } } bool AnimateMessage::update(uint64 newDelta, uint64 oldDelta) { bool r = false; for (AIter p = actions.begin(); p != actions.end(); ++p) { if ((*p)->isSheduled(oldDelta)) { r = true; (*p)->update(newDelta); } else { (*p)->clear(); } } return r; } 


The update method takes two timestamps: the current and the previous values. Its implementation is trivial. All AnimateAction is iterated and, in case its animation is not completed, the update method is called. Otherwise, the AnimateAction state is reset to its original state. If at least one element is found, the animation of which is not completed, the update method returns true.

AnimateAction.h
 #ifndef _ANIMATEACTION_H_ #define _ANIMATEACTION_H_ #include "s3eTypes.h" #include "AbstractScreenObject.h" class AnimateAction { private: uint64 startDelta; uint64 stopDelta; protected: AbstractScreenObject* sprite; virtual void doAction(int timeDelta) = 0; virtual int getTimeInterval() {return (int)(stopDelta - startDelta);} public: AnimateAction(AbstractScreenObject* sprite, uint64 startDelta, uint64 stopDelta); virtual ~AnimateAction() {} virtual bool isSheduled(uint64 timeDelta); virtual void update(uint64 timeDelta); virtual void clear() {} }; #endif // _ANIMATEACTION_H_ 


AnimateAction.cpp
 #include "AnimateAction.h" AnimateAction::AnimateAction(AbstractScreenObject* sprite, uint64 startDelta, uint64 stopDelta): sprite(sprite) , startDelta(startDelta) , stopDelta(stopDelta) { } bool AnimateAction::isSheduled(uint64 timeDelta) { return timeDelta < stopDelta; } void AnimateAction::update(uint64 timeDelta) { if (timeDelta >= startDelta) { uint64 delta = timeDelta - startDelta; if (timeDelta > stopDelta) { delta = stopDelta - startDelta; } doAction((int)delta); } } 


The isSheduled method returns true if the received timestamp is less than the completion time of the action. If the received timestamp is greater than or equal to the start time of the action, the value of the time elapsed since the start of the action (but no longer than the total duration of the execution of the action) is passed to the doAction method. Since the previous timestamp value is passed to the isSheduled method, at least one doAction is guaranteed to be executed.

Inheriting from AnimateAction, we can expand the list of basic animation effects. As examples of the heirs of AnimateAction, consider the MoveAction — which performs the rectilinear movement of an object and the SoundAction, which plays the sound effect, after a set time.

MoveAction.h
 #ifndef _MOVEACTION_H_ #define _MOVEACTION_H_ #include "AnimateAction.h" class MoveAction: public AnimateAction { private: int x, y; int startX, startY; bool isCleared; protected: virtual void doAction(int timeDelta); virtual void clear() {isCleared = true;} public: MoveAction(AbstractScreenObject* sprite, uint64 startDelta, uint64 stopDelta, int x, int y); MoveAction(AbstractScreenObject* sprite, uint64 delta, int x, int y); }; #endif // _MOVEACTION_H_ 


MoveAction.cpp
 #include "MoveAction.h" MoveAction::MoveAction(AbstractScreenObject* sprite, uint64 startDelta, uint64 stopDelta, int x, int y): AnimateAction(sprite, startDelta, stopDelta) , x(x), y(y), isCleared(true) {} MoveAction::MoveAction(AbstractScreenObject* sprite, uint64 delta, int x, int y): AnimateAction(sprite, delta, delta) , x(x), y(y), isCleared(true) {} void MoveAction::doAction(int timeDelta) { if (isCleared) { startX = sprite->getXDelta(); startY = sprite->getYDelta(); isCleared = false; } int timeInterval = getTimeInterval(); if (timeInterval <= 0) { sprite->setDeltaXY(x, y); } else if (timeDelta > timeInterval) { sprite->setDeltaXY(x, y); } else { int xInterval = x - startX; int yInterval = y - startY; int xDelta = (xInterval * timeDelta) / timeInterval; int yDelta = (yInterval * timeDelta) / timeInterval; sprite->setDeltaXY(startX + xDelta, startY + yDelta); } } 


SoundAction.h
 #ifndef _SOUNDACTION_H_ #define _SOUNDACTION_H_ #include <string> #include "IwSound.h" #include "AnimateAction.h" #include "Locale.h" using namespace std; class SoundAction: public AnimateAction { private: string res; int loc; bool checkSound(); protected: virtual void doAction(int timeDelta); public: SoundAction(AbstractScreenObject* sprite, uint64 timeDelta, const char* r, int loc = elSound); }; #endif // _SOUNDACTION_H_ 


SoundAction.cpp
 #include "SoundAction.h" #include "Desktop.h" SoundAction::SoundAction(AbstractScreenObject* sprite, uint64 timeDelta, const char* r, int loc): AnimateAction(sprite, timeDelta, timeDelta) , res(r), loc(loc) { } void SoundAction::doAction(int timeDelta) { CIwResGroup* resGroup; const char* groupName = Locale::getGroupName(loc); if (checkSound() &&(groupName != NULL)) { resGroup = IwGetResManager()->GetGroupNamed(groupName); CIwSoundSpec* SoundSpec = (CIwSoundSpec*)resGroup->GetResNamed(res.c_str(), IW_SOUND_RESTYPE_SPEC); CIwSoundInst* SoundInstance = SoundSpec->Play(); } } bool SoundAction::checkSound() { IObject* o = (IObject*)desktop.getName("soundon"); if (o != NULL) { return (o->getState() != 0); } return false; } 


As we can see, the AnimateAction kit can easily be expanded.

Using the animation mechanisms described above, we will create a small user interface to control the settings of a typical Android application. For a start, we need buttons. The buttons should respond to pressing by performing a simple animation:

Button.h
 #ifndef _BUTTON_H_ #define _BUTTON_H_ #include "AnimatedSprite.h" #include "AbstractSpriteOwner.h" enum EButtonMessage { ebmDown = 0x0100, ebmUp = 0x0101, ebmOutUp = 0x0111, ebmPressed = 0x0102 }; class Button: public AnimatedSprite { protected: AnimateMessage* msgDown; AnimateMessage* msgUp; int message; AbstractSpriteOwner* receiver; void configure(); public: Button(ISpriteOwner* scene, const char* res, int x, int y, int zOrder = 0, int loc = elNothing); Button(ISpriteOwner* scene, int x, int y, int zOrder = 0); virtual bool isValidMessage(int msg); virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL); virtual void doMessage(int msg, void* data = NULL, uint64 timestamp = 0); virtual bool isPausable() const {return false;} void addReceiver(int m, AbstractSpriteOwner* r); }; #endif // _BUTTON_H_ 


Button.cpp
 #include "Button.h" #include "Desktop.h" #include "MoveAction.h" #include "SendMessageAction.h" #include "SoundAction.h" Button::Button(ISpriteOwner* scene, const char* res, int x, int y, int zOrder, int loc): AnimatedSprite(scene, res, x, y, zOrder, loc), receiver(NULL) { Button::configure(); } Button::Button(ISpriteOwner* scene, int x, int y, int zOrder): AnimatedSprite(scene, x, y, zOrder), receiver(NULL) { Button::configure(); } void Button::configure() { msgDown = new AnimateMessage(); msgDown->addAction(new MoveAction(this, 0, 50, 10, 10)); msgDown->addAction(new SoundAction(this, 50, "menubutton")); addMessageRule(ebmDown, msgDown); msgUp = new AnimateMessage(); msgUp->addAction(new MoveAction(this, 100, 150, 0, 0)); addMessageRule(ebmOutUp, msgUp); msgUp = new AnimateMessage(); msgUp->addAction(new MoveAction(this, 100, 150, 0, 0)); msgUp->addAction(new SendMessageAction(this, 100, ebmPressed)); msgUp->addAction(new SendMessageAction(this, 110, emtInit)); addMessageRule(ebmUp, msgUp); } bool Button::isValidMessage(int msg) { switch (msg) { case emtTouchDown: case emtTouchUp: case ebmDown: case ebmUp: case ebmOutUp: case ebmPressed: return true; default: return AnimatedSprite::isValidMessage(msg); } } void Button::doMessage(int msg, void* data, uint64 timestamp) { if (msg == ebmPressed) { if (receiver != NULL) { receiver->sendMessage(message, 0, (IObject*)this); } return; } AnimatedSprite::doMessage(msg, data, timestamp); } bool Button::sendMessage(int msg, uint64 timestamp, void* data) { if ((msg & emtTouchEvent) != 0) { switch (msg & emtTouchMask) { case emtTouchDown: sendMessage(ebmDown, desktop.getCurrentTimestamp()); break; case emtTouchUp: sendMessage(ebmUp, desktop.getCurrentTimestamp()); break; case emtTouchOutUp: sendMessage(ebmOutUp, desktop.getCurrentTimestamp()); break; } return true; } return AnimatedSprite::sendMessage(msg, timestamp, data); } void Button::addReceiver(int m, AbstractSpriteOwner* r) { message = m; receiver = r; } 


In the sendMessage method, we process Touchpad event codes to form internal events that the button works with. In configure, animation is associated with these events; when clicked, the button moves 10 units down and to the right, when released, returns to its place. In the event that, when released, the touch point has not left the button, an ebmPressed event is generated, with which we can associate an arbitrary handler.

In addition to the usual buttons, we need buttons, switches, with a variable picture. Since this button should also be animated when pressed, we inherit it from Button:

SwitchButton.h
 #ifndef _SWITCHBUTTON_H_ #define _SWITCHBUTTON_H_ #include "Button.h" class SwitchButton: public Button { protected: void configure(); public: SwitchButton(ISpriteOwner* scene, int x, int y, int zOrder = 0); virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL); }; #endif // _SWITCHBUTTON_H_ 


SwitchButton.cpp
 #include "SwitchButton.h" #include "Desktop.h" #include "MoveAction.h" #include "SendMessageAction.h" #include "SoundAction.h" SwitchButton::SwitchButton(ISpriteOwner* scene, int x, int y, int zOrder): Button(scene, x, y, zOrder) { SwitchButton::configure(); } 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); } return true; } return Button::sendMessage(msg, timestamp, data); } 


We simply add emtSwitch event firing buttons to the animation, which switches the image for any AnimatedSprite.

Now everything is ready for the development of the user interface. The Intro class will contain two screens - the start screen and the settings screen.

Intro.h
 #ifndef _INTRO_H_ #define _INTRO_H_ #include "Scene.h" #include "CompositeSprite.h" enum EIntroMessage { eimPlay = 0x100, eimSettings = 0x101, eimBack = 0x102, eimCheckMusic = 0x103 }; enum EIntroStatus { eisMain = 0, eisSettings = 1 }; class Intro: public Scene { private: Sprite* background; CompositeSprite* title; CompositeSprite* menu; CompositeSprite* settings; int state; void checkMusic(); protected: virtual bool doKeyMessage(int msg, s3eKey key); virtual int getState() {return state;} void setState(int s) {state = s;} public: Intro(); virtual bool init(); virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL); }; extern Intro* introScene; #endif // _INTRO_H_ 


Intro.cpp
 #include "Intro.h" #include "Background.h" #include "IntroTitle.h" #include "IntroMenu.h" #include "IntroSound.h" #include "Desktop.h" Intro* introScene = NULL; Intro::Intro(): state(eisMain) { introScene = this; } bool Intro::init() { if (!Scene::init()) return false; regKey(s3eKeyBack); regKey(s3eKeyAbsBSK); #if defined IW_DEBUG regKey(s3eKeyLSK); #endif background = new Background(this, "background.png", 1); title = new IntroTitle(this, 2); menu = new IntroMenu(this, 3); settings = new IntroSound(this, 4); settings->doMessage(emtHide); checkMusic(); return true; } bool Intro::doKeyMessage(int msg, s3eKey key) { if (msg == emtKeyPressed) { switch (state) { case eisSettings: sendMessage(eimBack); return true; } } return false; } bool Intro::sendMessage(int msg, uint64 timestamp, void* data) { switch (msg) { case eimPlay: // TODO: return true; case eimSettings: background->setAlpha(IW_2D_ALPHA_HALF); title->doMessage(emtHide); menu->doMessage(emtHide); settings->doMessage(emtShow); setState(eisSettings); return true; case eimBack: background->setAlpha(IW_2D_ALPHA_NONE); title->doMessage(emtShow); menu->doMessage(emtShow); settings->doMessage(emtHide); setState(eisMain); return true; case emtInit: case eimCheckMusic: checkMusic(); return true; } return false; } void Intro::checkMusic() { bool f = false; IObject* o = (IObject*)desktop.getName("musicon"); if (o == NULL) { desktop.stopMusic(); return; } f = (o->getState() != 0); if (f) { desktop.startMusic("music.mp3"); } else { desktop.stopMusic(); } } 


The Intro class represents the only scene in our application that loads multiple CompositeSprite. SendMessage defines event handlers. Next, we define several composite sprites whose task will be to display the name of the program, as well as the menu, implemented as a set of buttons.

IntroTitle.h
 #ifndef _INTROTITLE_H_ #define _INTROTITLE_H_ #include <string.h> #include "CompositeSprite.h" class IntroTitle: public CompositeSprite { public: IntroTitle(Scene* scene, int zOrder): CompositeSprite(scene, 0, 0, zOrder) {} virtual bool init(); virtual void refresh(); }; #endif // _INTROTITLE_H_ 


IntroTitle.cpp
 #include "IntroTitle.h" #include "Sprite.h" bool IntroTitle::init() { if (!AbstractScreenObject::init()) return false; // Sprite settings setXY(122, 100); // Sprite components new Sprite(this, "sprite.png", 0, 0, 1); return true; } void IntroTitle::refresh() { CompositeSprite::refresh(); } 


IntroMenu.h
 #ifndef _INTROMENU_H_ #define _INTROMENU_H_ #include "CompositeSprite.h" #include "Button.h" class IntroMenu: public CompositeSprite { private: Scene* scene; Button* firstButton; Button* secondButton; public: IntroMenu(Scene* scene, int zOrder): CompositeSprite(scene, 0, 0, zOrder), scene(scene) {} virtual bool init(); }; #endif // _INTROMENU_H_ 


IntroMenu.cpp
 #include "IntroMenu.h" #include "Locale.h" #include "Intro.h" #include "Desktop.h" bool IntroMenu::init() { if (!AbstractScreenObject::init()) return false; setXY(297, 384); firstButton = new Button(this, "play", 0, 0, 1, Locale::getCurrentImageLocale()); firstButton->addReceiver(eimPlay, scene); secondButton = new Button(this, "setup", 0, 157, 2, Locale::getCurrentImageLocale()); secondButton->addReceiver(eimSettings, scene); return true; } 


IntroSound.h
 #ifndef _INTROSOUND_H_ #define _INTROSOUND_H_ #include "CompositeSprite.h" class IntroSound: public CompositeSprite { private: Scene* scene; public: IntroSound(Scene* scene, int zOrder): CompositeSprite(scene, 0, 0, zOrder), scene(scene) {} virtual bool init(); }; #endif // _INTROSOUND_H_ 


IntroSound.cpp
 #include "IntroSound.h" #include "SwitchButton.h" #include "Button.h" #include "Intro.h" #include "Locale.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(1); 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); Button* b = new Button(this, "back.png", -300, 350, 3, Locale::getCommonImageLocale()); b->addReceiver(eimBack, scene); return true; } 


By running the application, you can make sure that the buttons react to pressing, and also perform the specified switching between screens.

So, we managed to develop the simplest Framework, at the same time having studied some of the features of the Marmalade API for developing cross-platform mobile applications. Of course, the developed library lacks very much to be considered a full-fledged gaming Framework (for example, it does not support the implementation of physics), but its underlying flexible architecture makes it easy to add any necessary extensions.

The source code of the project is located at the following link .

In developing the Marmalade Framework, the following materials were used .

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


All Articles