Framework in Marmalade (part 2)

  • Tutorial
In a previous article, I started talking 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 event handling.

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

A slightly less significant source of events (on mobile platforms) is the keyboard. Indeed, on a modern mobile device there are not so many buttons 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 appropriately (at least when developing for Android). It is about handling the s3eKeyAbsBSK event, triggered by pressing the "Back" button. Under Android development agreements, 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 begin to refine the Marmalade Framework by adding touchpad event handling. Add a description of the new files to 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	11structTouch {int		x, y;
		bool	isActive, isPressed, isMoved;
		int		id;	
};
classTouchPad {private:
		bool		IsAvailable;
		bool		IsMultiTouch;
		Touch		Touches[MAX_TOUCHES];
	public:
        staticboolisTouchDown(int eventCode);
        staticboolisTouchUp(int eventCode);
		boolisAvailable()const{ return IsAvailable; }
		boolisMultiTouch()const{ return IsMultiTouch; }
		Touch*           getTouchByID(int id);
		Touch*           getTouch(int index){ return &Touches[index]; }	
		Touch*           findTouch(int id);								
		intgetTouchCount()const;
		boolinit();
		voidrelease();
		voidupdate();
		voidclear();
};
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;
}
voidHandleMultiTouchButton(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;
    }
}
voidHandleMultiTouchMotion(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;
    }
}
voidHandleSingleTouchButton(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;
}
voidHandleSingleTouchMotion(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];
	}
	returnNULL;
}
Touch* TouchPad::findTouch(int id) {
	if (!IsAvailable)
		returnNULL;
	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];
		}
	}
	returnNULL;
}
int	TouchPad::getTouchCount() const {
	if (!IsAvailable)
		return0;
	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) returnfalse;
	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);
    }
	returntrue;
}
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 processing is rather low level. The Touchpad module should hide this fact from the development of the application. In order to update the project, run the mkb-file to execute again. After the download is complete, we will see that the TouchPad module has been successfully added to the C ++ project.

We will make the necessary additions to the Desktop module.

Desktop.h
#ifndef _DESKTOP_H_#define _DESKTOP_H_#include<set>#include"s3eKeyboard.h"#include"Scene.h"usingnamespacestd;
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
};
classDesktop {private:
        int width, height;
        bool isChanged;
        Scene* currentScene;
        bool isKeyAvailable;
        bool IsQuitMessageReceived;
        boolcheckBounce(int id, int msg);
        voidgetScreenSizes();
        static int32 ScreenSizeChangeCallback(void* systemData, void* userData);
    public:
        Desktop(): touches() {}
        voidinit();
        voidrelease();
        voidupdate(uint64 timestamp);
        voidrefresh();
        intgetWidth()const{return width;}
        intgetHeight()const{return height;}
        Scene* getScene(){return currentScene;}
        voidsetScene(Scene* scene);
        voidsendQuitMessage(){IsQuitMessageReceived = true;}
        boolisQuitMessageReceived();
        set<int> touches;
    typedefset<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;
    return0;
}
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()) returntrue;
        touches.insert(id);
    } else {
        if (p == touches.end()) returntrue;
        if (TouchPad::isTouchUp(msg)) {
            touches.erase(p);
        }
    }
    returnfalse;
}
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()) {
        returntrue;
    }
    return IsQuitMessageReceived;
}



Here we defined a number of system events and added TouchPad event handling to the update method. You should pay attention to the checkBounce method, whose task is to prevent "bounce" by forming the correct sequence of events of pressing and releasing TouchPad. In addition, an event handler for changing screen sizes has been added to Desktop (for example, as a result of changing its orientation).

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"voidinit(){
    // Initialise Mamrlade graphics system and Iw2D module
    IwGxInit();
    Iw2DInit();
    // Set the default background clear colour
    IwGxSetColClear(0x0, 0x0, 0x0, 0);
    touchPad.init();
    desktop.init();
}
voidrelease(){
    desktop.release();
    touchPad.release();
    Iw2DTerminate();
    IwGxTerminate();
}
intmain(){
    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 Loopwhile (!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();
    return0;
}


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

The processing of keyboard events will be placed 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"usingnamespacestd;
classScene:public AbstractSpriteOwner {
    private:
        AbstractScreenObject* background;
        map<s3eKey, int> keys;
        bool isInitialized;
        uint64 lastTime;
    protected:
        virtualbooldoKeyMessage(int msg, s3eKey key){returnfalse;}
        virtualvoidregKey(s3eKey key);
    public:
        Scene();
        virtualboolinit();
        intgetXSize(int xSize);
        intgetYSize(int ySize);
        virtualintgetXPos(int x){return x;}
        virtualintgetYPos(int y){return y;}
        virtualvoidrefresh();
        virtualvoidupdate(uint64 timestamp);
        virtualboolisBuzy(){returnfalse;}
        virtualboolsendMessage(int id, int x, int y);
    typedefmap<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)) {
        returntrue;
    }
    if (background != NULL) {
        return background->sendMessage(id, x, y);
    }
    returnfalse;
}


For each key included in the list of keys listened to, we check the status, generate keyboard events and pass it to the redefined method doKeyMessage. If this method does not handle the received events, we perform the default processing for s3eKeyBack and s3eKeyAbsBSK, which consists in calling the desktop.sendQuitMessage method, which leads to the application termination.

To complete the article, we only need to add the processing 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"classSprite: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, constchar* res, int x, int y, int zOrder = 0);
        virtual ~Sprite();
        virtualboolsendMessage(int msg, uint64 timestamp = 0, void* data = NULL);
        virtualboolsendMessage(int msg, int x, int y);
        virtualvoidupdate(uint64 timestamp){}
        virtualvoidrefresh();
        virtualvoidaddImage(constchar*res, int state = 0);
        virtual CIw2DImage* getImage(int id = 0);
        virtualintgetState(){return0;}
        virtualintgetWidth();
        virtualintgetHeight();
};
#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, constchar* 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) returnfalse;
    if (!isVisible) returnfalse;
    int id = msg & emtTouchIdMask;
    msg &= emtTouchMask;
    if (capturedId >= 0) {
        if (id != capturedId) returnfalse;
        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)) returnfalse;
        X += owner->getXSize(getWidth());
        Y += owner->getYSize(getHeight());
        if ((x > X)||(y > Y)) returnfalse;
    }
    switch (msg) {
        case emtTouchDown:
            capturedId = id;
            break;
        case emtTouchUp:
            capturedId = -1;
            break;
    }
    if (isBuzy()) {
        returntrue;
    }
    return sendMessage(msg) ||
           owner->sendMessage(msg);
}
void Sprite::addImage(constchar*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 {
        return0;
    }
}
int Sprite::getHeight() {
    CIw2DImage* img = getImage(getState());
    if (img != NULL) {
        return img->GetHeight();
    } else {
        return0;
    }
}
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 whether the touch point falls into the sprite region and, if so, we transmit a non-positional event with the same code to the sprite. Let me remind you that in AbstractSpriteOwner :: update we call sendMessage one by one for all sprites except for Background, in the reverse order of Z-order (used to draw sprites).

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

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

The source code for the current version of the project can be obtained here.

When developing the Marmalade Framework, the following materials were used

Also popular now: