📜 ⬆️ ⬇️

Framework in Marmalade (part 2)

In the previous article, I began to talk about developing a small Framework for creating 2D games using the Marmalade tool system, which provides the ability to develop and build applications for a number of platforms, including iOS and Android. We learned how to work with graphic resources and built a small test application. Today I want to talk about handling events.

The main source of events on mobile platforms is the TouchPad, which provides the application with information about the user's interaction with the touch screen. On most modern devices, Multitouch technology is supported, allowing you to track multiple touches simultaneously. Of course, the gaming framework must correctly handle multitouch events.

A slightly less significant source of events (on mobile platforms) is the keyboard. Indeed, there are not so many buttons on a modern mobile device to receive any meaningful information from them. But there is at least one keyboard event that we will have to work out in order for the application to behave adequately (at least when developing for Android). It is about handling the s3eKeyAbsBSK event, triggered by pressing the “Back” button. According to the development agreements on Android, clicking this button should close the current activity and return the interface to the previous one.

In this article we will not consider other sources of events (such as an accelerometer), because, although they are used in the development of gaming applications, their consideration requires writing a separate article.
')
We’ll start redesigning the Marmalade Framework by adding Touchpad event handling. Add a description of the new files in the mkb file.

mf.mkb
#!/usr/bin/env mkb ... files { [Main] (source/Main) ... TouchPad.cpp TouchPad.h } ... 


TouchPad.h
 #ifndef _TOUCHPAD_H_ #define _TOUCHPAD_H_ #include "IwGeom.h" #include "s3ePointer.h" #define MAX_TOUCHES 11 struct Touch { int x, y; bool isActive, isPressed, isMoved; int id; }; class TouchPad { private: bool IsAvailable; bool IsMultiTouch; Touch Touches[MAX_TOUCHES]; public: static bool isTouchDown(int eventCode); static bool isTouchUp(int eventCode); bool isAvailable() const { return IsAvailable; } bool isMultiTouch() const { return IsMultiTouch; } Touch* getTouchByID(int id); Touch* getTouch(int index) { return &Touches[index]; } Touch* findTouch(int id); int getTouchCount() const; bool init(); void release(); void update(); void clear(); }; extern TouchPad touchPad; #endif // _TOUCHPAD_H_ 


TouchPad.cpp
 #include "TouchPad.h" #include "Desktop.h" TouchPad touchPad; bool TouchPad::isTouchDown(int eventCode) { return (eventCode & emtTouchMask) == emtTouchDown; } bool TouchPad::isTouchUp(int eventCode) { return (eventCode & emtTouchMask) == emtTouchUp; } void HandleMultiTouchButton(s3ePointerTouchEvent* event) { Touch* touch = touchPad.findTouch(event->m_TouchID); if (touch != NULL) { touch->isPressed = event->m_Pressed != 0; touch->isActive = true; touch->x = event->m_x; touch->y = event->m_y; touch->id = event->m_TouchID; } } void HandleMultiTouchMotion(s3ePointerTouchMotionEvent* event) { Touch* touch = touchPad.findTouch(event->m_TouchID); if (touch != NULL) { if (touch->isActive) { touch->isMoved = true; } touch->isActive = true; touch->x = event->m_x; touch->y = event->m_y; } } void HandleSingleTouchButton(s3ePointerEvent* event) { Touch* touch = touchPad.getTouch(0); touch->isPressed = event->m_Pressed != 0; touch->isActive = true; touch->x = event->m_x; touch->y = event->m_y; touch->id = 0; } void HandleSingleTouchMotion(s3ePointerMotionEvent* event) { Touch* touch = touchPad.getTouch(0); if (touch->isActive) { touch->isMoved = true; } touch->isActive = true; touch->x = event->m_x; touch->y = event->m_y; } Touch* TouchPad::getTouchByID(int id) { for (int i = 0; i < MAX_TOUCHES; i++) { if (Touches[i].isActive && Touches[i].id == id) return &Touches[i]; } return NULL; } Touch* TouchPad::findTouch(int id) { if (!IsAvailable) return NULL; for (int i = 0; i < MAX_TOUCHES; i++) { if (Touches[i].id == id) return &Touches[i]; } for (int i = 0; i < MAX_TOUCHES; i++) { if (!Touches[i].isActive) { Touches[i].id = id; return &Touches[i]; } } return NULL; } int TouchPad::getTouchCount() const { if (!IsAvailable) return 0; int r = 0; for (int i = 0; i < MAX_TOUCHES; i++) { if (Touches[i].isActive) { r++; } } return r; } void TouchPad::update() { for (int i = 0; i < MAX_TOUCHES; i++) { Touches[i].isMoved = false; } if (IsAvailable) { s3ePointerUpdate(); } } void TouchPad::clear() { for (int i = 0; i < MAX_TOUCHES; i++) { if (!Touches[i].isPressed) { Touches[i].isActive = false; } Touches[i].isMoved = false; } } bool TouchPad::init() { IsAvailable = s3ePointerGetInt(S3E_POINTER_AVAILABLE) ? true : false; if (!IsAvailable) return false; for (int i = 0; i < MAX_TOUCHES; i++) { Touches[i].isPressed = false; Touches[i].isActive = false; Touches[i].isMoved = false; Touches[i].id = 0; } IsMultiTouch = s3ePointerGetInt(S3E_POINTER_MULTI_TOUCH_AVAILABLE) ? true : false; if (IsMultiTouch) { s3ePointerRegister(S3E_POINTER_TOUCH_EVENT, (s3eCallback)HandleMultiTouchButton, NULL); s3ePointerRegister(S3E_POINTER_TOUCH_MOTION_EVENT, (s3eCallback)HandleMultiTouchMotion, NULL); } else { s3ePointerRegister(S3E_POINTER_BUTTON_EVENT, (s3eCallback)HandleSingleTouchButton, NULL); s3ePointerRegister(S3E_POINTER_MOTION_EVENT, (s3eCallback)HandleSingleTouchMotion, NULL); } return true; } void TouchPad::release() { if (IsAvailable) { if (IsMultiTouch) { s3ePointerUnRegister(S3E_POINTER_TOUCH_EVENT, (s3eCallback)HandleMultiTouchButton); s3ePointerUnRegister(S3E_POINTER_TOUCH_MOTION_EVENT, (s3eCallback)HandleMultiTouchMotion); } else { s3ePointerUnRegister(S3E_POINTER_BUTTON_EVENT, (s3eCallback)HandleSingleTouchButton); s3ePointerUnRegister(S3E_POINTER_MOTION_EVENT, (s3eCallback)HandleSingleTouchMotion); } } } 


As we can see, event handling is pretty low level. The Touchpad module should hide this fact from the application developer. In order to update the project, run the mkb file for execution again. After the download is complete, we will see that the TouchPad module has been successfully added to the C ++ project.

We make the necessary additions to 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, emtTouchEvent = 0x10, emtTouchIdMask = 0x07, emtTouchMask = 0x70, emtMultiTouch = 0x14, emtTouchDown = 0x30, emtTouchUp = 0x50, emtTouchMove = 0x70, emtSingleTouchDown = 0x30, emtSingleTouchUp = 0x50, emtSingleTouchMove = 0x70, emtMultiTouchDown = 0x34, emtMultiTouchUp = 0x54, emtMultiTouchMove = 0x74, emtKeyEvent = 0x80, emtKeyAction = 0x82, emtKeyDown = 0x81, emtKeyPressed = 0x83, emtKeyReleased = 0x82 }; class Desktop { private: int width, height; bool isChanged; Scene* currentScene; bool isKeyAvailable; bool IsQuitMessageReceived; bool checkBounce(int id, int msg); void getScreenSizes(); static int32 ScreenSizeChangeCallback(void* systemData, void* userData); public: Desktop(): touches() {} void init(); void release(); void update(uint64 timestamp); void refresh(); int getWidth() const {return width;} int getHeight() const {return height;} Scene* getScene() {return currentScene;} void setScene(Scene* scene); void sendQuitMessage() {IsQuitMessageReceived = true;} bool isQuitMessageReceived(); set<int> touches; typedef set<int>::iterator TIter; }; extern Desktop desktop; #endif // _DESKTOP_H_ 


Desktop.cpp
 #include "Desktop.h" #include "Iw2D.h" #include "TouchPad.h" Desktop desktop; void Desktop::init() { IsQuitMessageReceived = false; getScreenSizes(); setScene(NULL); isKeyAvailable = (s3eKeyboardGetInt(S3E_KEYBOARD_HAS_KEYPAD) || s3eKeyboardGetInt(S3E_KEYBOARD_HAS_ALPHA)); s3eSurfaceRegister(S3E_SURFACE_SCREENSIZE, ScreenSizeChangeCallback, NULL); } void Desktop::release() { s3eSurfaceUnRegister(S3E_SURFACE_SCREENSIZE, ScreenSizeChangeCallback); touches.clear(); } int32 Desktop::ScreenSizeChangeCallback(void* systemData, void* userData) { desktop.isChanged = true; return 0; } void Desktop::setScene(Scene* scene) { if (scene != NULL) { scene->init(); } currentScene = scene; } void Desktop::getScreenSizes() { width = Iw2DGetSurfaceWidth(); height = Iw2DGetSurfaceHeight(); isChanged = false; } bool Desktop::checkBounce(int id, int msg) { TIter p = touches.find(id); if (TouchPad::isTouchDown(msg)) { if (p != touches.end()) return true; touches.insert(id); } else { if (p == touches.end()) return true; if (TouchPad::isTouchUp(msg)) { touches.erase(p); } } return false; } void Desktop::update(uint64 timestamp) { if (isChanged) { getScreenSizes(); } int cnt = touchPad.getTouchCount(); if (cnt > 0) { for (int i = 0; i < MAX_TOUCHES; i++) { Touch* t = touchPad.getTouch(i); if (t->isActive) { int msg = (cnt > 1)?emtMultiTouchUp:emtSingleTouchUp; if (t->isMoved) { msg = (cnt > 1)?emtMultiTouchMove:emtSingleTouchMove; } if (t->isPressed) { msg = (cnt > 1)?emtMultiTouchDown:emtSingleTouchDown; } if (!checkBounce(t->id, msg)) { if (currentScene != NULL) { currentScene->sendMessage(msg | (t->id & emtTouchIdMask), t->x, t->y); } } } } } if (isKeyAvailable) { s3eKeyboardUpdate(); } if (currentScene != NULL) { currentScene->update(timestamp); } } void Desktop::refresh() { if (currentScene != NULL) { currentScene->refresh(); } } bool Desktop::isQuitMessageReceived() { if (s3eDeviceCheckQuitRequest()) { return true; } return IsQuitMessageReceived; } 



Here we defined a number of system events and added TouchPad event handling to the update method. Attention should be paid to the checkBounce method, the task of which is to prevent bounce, by forming the correct sequence of pressing and releasing events of the TouchPad. In addition, a screen size event handler has been added to Desktop (for example, as a result of changing its orientation).

The Main module changes slightly.

Main.cpp
 #include "Main.h" #include "s3e.h" #include "Iw2D.h" #include "IwGx.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(); // Set the default background clear colour IwGxSetColClear(0x0, 0x0, 0x0, 0); touchPad.init(); desktop.init(); } void release() { desktop.release(); touchPad.release(); Iw2DTerminate(); IwGxTerminate(); } int main() { init(); { Scene scene; new Background(&scene, "background.png", 1); new Sprite(&scene, "sprite.png", 122, 100, 2); desktop.setScene(&scene); int32 duration = 1000 / 25; // Main Game Loop while (!desktop.isQuitMessageReceived()) { // Update keyboard system s3eKeyboardUpdate(); // Update touchPad.update(); desktop.update(s3eTimerGetMs()); // 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; } 


We removed the direct test for pressing the s3eKeyAbsBSK button and replaced the s3eDeviceCheckQuitRequest call in the loop condition with the call to the Desktop :: isQuitMessageReceived method. Calls to Touchpad :: update and clear have been added to the main application loop. In addition, initialization and termination calls were added for the TouchPad module to the init and release methods, respectively.

We will place the processing of keyboard events in the Scene module.

Scene.h
 #ifndef _SCENE_H_ #define _SCENE_H_ #include <map> #include <set> #include "s3eKeyboard.h" #include "AbstractSpriteOwner.h" #include "AbstractScreenObject.h" using namespace std; class Scene: public AbstractSpriteOwner { private: AbstractScreenObject* background; map<s3eKey, int> keys; bool isInitialized; uint64 lastTime; protected: virtual bool doKeyMessage(int msg, s3eKey key) {return false;} virtual void regKey(s3eKey key); public: Scene(); virtual bool init(); int getXSize(int xSize); int getYSize(int ySize); virtual int getXPos(int x) {return x;} virtual int getYPos(int y) {return y;} virtual void refresh(); virtual void update(uint64 timestamp); virtual bool isBuzy() {return false;} virtual bool sendMessage(int id, int x, int y); typedef map<s3eKey, int>::iterator KIter; typedef pair<s3eKey, int> KPair; }; #endif // _SCENE_H_ 


Scene.cpp
 #include "Scene.h" #include "Desktop.h" Scene::Scene(): AbstractSpriteOwner() , isInitialized(false) , background(NULL) , keys() , lastTime(0) { regKey(s3eKeyBack); regKey(s3eKeyAbsBSK); } bool Scene::init() { bool r = !isInitialized; isInitialized = true; return r; } int Scene::getXSize(int xSize) { if (background != NULL) { return (getDesktopWidth() * xSize) / background->getWidth(); } return xSize; } int Scene::getYSize(int ySize) { if (background != NULL) { return (getDesktopHeight() * ySize) / background->getHeight(); } return ySize; } void Scene::refresh() { init(); if (background == NULL) { for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) { if (p->second->isBackground()) { background = p->second; break; } } } AbstractSpriteOwner::refresh(); } void Scene::regKey(s3eKey key) { keys.insert(KPair(key, 0)); } void Scene::update(uint64 timestamp) { for (KIter p = keys.begin(); p != keys.end(); ++p) { int msg = 0; int32 keyState = s3eKeyboardGetState(p->first); if (keyState & (S3E_KEY_STATE_DOWN | S3E_KEY_STATE_PRESSED)) { msg = emtKeyDown; if (p->second == 0) { msg = emtKeyPressed; p->second = 1; } } if (keyState == S3E_KEY_STATE_UP) { if (p->second == 1) { msg = emtKeyReleased; p->second = 0; } } if (msg != 0) { if (doKeyMessage(msg, p->first)) { lastTime = timestamp; } else { if (timestamp - lastTime >= 1000) { lastTime = 0; } if ((lastTime == 0)&&(msg == emtKeyPressed)) { switch (p->first) { case s3eKeyBack: case s3eKeyAbsBSK: desktop.sendQuitMessage(); break; } } } } } AbstractSpriteOwner::update(timestamp); } bool Scene::sendMessage(int id, int x, int y) { if (AbstractSpriteOwner::sendMessage(id, x, y)) { return true; } if (background != NULL) { return background->sendMessage(id, x, y); } return false; } 


For each key listed in the list of keys listened to, we check the status, generate keyboard events and pass to the override method doKeyMessage. If this method does not handle the received events, we perform the default processing for s3eKeyBack and s3eKeyAbsBSK, which is to call the desktop.sendQuitMessage method, which causes the application to terminate.

To complete the article, we need to add the handling of positional events to the Sprite class.

Sprite.h
 #ifndef _SPRITE_H_ #define _SPRITE_H_ #include "AbstractScreenObject.h" #include "ISprite.h" #include "ISpriteOwner.h" #include "Locale.h" class Sprite: public AbstractScreenObject , public ISprite { protected: ISpriteOwner* owner; CIw2DImage* img; int capturedId; public: Sprite(ISpriteOwner* owner, int x, int y , int zOrder = 0); Sprite(ISpriteOwner* owner, const char* res, int x, int y, int zOrder = 0); virtual ~Sprite(); virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL); virtual bool sendMessage(int msg, int x, int y); virtual void update(uint64 timestamp) {} virtual void refresh(); virtual void addImage(const char*res, int state = 0); virtual CIw2DImage* getImage(int id = 0); virtual int getState() {return 0;} virtual int getWidth(); virtual int getHeight(); }; #endif // _SPRITE_H_ 


Sprite.cpp
 #include "Sprite.h" #include "Locale.h" #include "Desktop.h" Sprite::Sprite(ISpriteOwner* owner, int x, int y , int zOrder): AbstractScreenObject(x, y) , owner(owner) , capturedId(-1) , img(NULL) { owner->addSprite((AbstractScreenObject*)this, zOrder); } Sprite::Sprite(ISpriteOwner* owner, const char* res, int x, int y, int zOrder): AbstractScreenObject(x, y) , owner(owner) , capturedId(-1) , img(NULL) { addImage(res, 0); owner->addSprite((AbstractScreenObject*)this, zOrder); } Sprite::~Sprite() { if (img != NULL) { delete img; } } bool Sprite::sendMessage(int msg, uint64 timestamp, void* data) { return owner->sendMessage(msg, timestamp, data); } bool Sprite::sendMessage(int msg, int x, int y) { if ((msg & emtTouchEvent) != emtTouchEvent) return false; if (!isVisible) return false; int id = msg & emtTouchIdMask; msg &= emtTouchMask; if (capturedId >= 0) { if (id != capturedId) return false; if (msg == emtTouchDown) { capturedId = -1; } } if (capturedId < 0) { int X = owner->getXSize(owner->getXPos(getXPos())); int Y = owner->getYSize(owner->getYPos(getYPos())); if ((x < X)||(y < Y)) return false; X += owner->getXSize(getWidth()); Y += owner->getYSize(getHeight()); if ((x > X)||(y > Y)) return false; } switch (msg) { case emtTouchDown: capturedId = id; break; case emtTouchUp: capturedId = -1; break; } if (isBuzy()) { return true; } return sendMessage(msg) || owner->sendMessage(msg); } void Sprite::addImage(const char*res, int state) { img = Iw2DCreateImage(res); } CIw2DImage* Sprite::getImage(int id) { return img; } int Sprite::getWidth() { CIw2DImage* img = getImage(getState()); if (img != NULL) { return img->GetWidth(); } else { return 0; } } int Sprite::getHeight() { CIw2DImage* img = getImage(getState()); if (img != NULL) { return img->GetHeight(); } else { return 0; } } void Sprite::refresh() { init(); CIw2DImage* img = getImage(getState()); if (isVisible && (img != NULL)) { CIwMat2D m; m.SetRot(getAngle()); m.ScaleRot(IW_GEOM_ONE); m.SetTrans(CIwSVec2(owner->getXSize(owner->getXPos(getXPos())), owner->getYSize(owner->getYPos(getYPos())))); Iw2DSetTransformMatrix(m); Iw2DSetAlphaMode(alpha); Iw2DDrawImage(img, CIwSVec2(0, 0), CIwSVec2(owner->getXSize(getWidth()), owner->getYSize(getHeight()))); } } 


In the sendMessage positional event handler, we check if the touch point falls into the sprite region and, if so, pass a non-positional event with the same code to the sprite. Let me remind you that in AbstractSpriteOwner :: update we alternately call sendMessage for all sprites except the Background, in the reverse order of the Z-order (used to render the sprites).

For drop-release events, special handling is used. Having received an emtTouchDown event. Sprite remembers the touch id and sends subsequent events with the same id to the handler of the same sprite, regardless of the touch coordinates.

So, we have learned to handle events, but in order to show some noticeable activity related to their processing, we still have a lot of work to do. In the next article we will learn how to work with resources and sound.

Source texts of the current version of the project can be obtained here.

The following materials were used in developing the Marmalade Framework .

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


All Articles