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

в 8:15, , рубрики: android, iOS, marmalade, Разработка под android, разработка под iOS, метки: , ,

В предыдущей статье я начал рассказывать о разработке небольшого Framework-а для создания 2D-игр, с использованием инструментальной системы Marmalade, предоставляющей возможность разработки и сборки приложений для ряда платформ, в том числе iOS и Android. Мы научились работать с графическими ресурсами и построили небольшое тестовое приложение. Сегодня я хочу рассказать об обработке событий.

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

Несколько менее значимым источником событий (на мобильных платформах) является клавиатура. Действительно, на современном мобильном устройстве не так уж много кнопок, чтобы получать от них сколь-нибудь значимую информацию. Но есть по крайней мере одно клавиатурное событие, которое нам придется отработать, для того, чтобы приложение вело себя адекватно (по крайней мере при разработке под Android). Речь идет об обработке события s3eKeyAbsBSK, инициируемого нажатием кнопки «Назад». По соглашениям разработки на Android, нажатие этой кнопки должно закрывать текущую активность и возвращать интерфейс к предыдущей.

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

Начнем доработку Marmalade Framework с добавления обработки Touchpad-событий. Добавим описание новых файлов в mkb-файл.

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

Как мы видим, обработка событий довольно низкоуровнева. Модуль Touchpad должен скрыть этот факт от разработчка приложения. Для того, чтобы обновить проект, запустим mkb-файл на выполнение снова. После завершения загрузки, мы увидим, что модуль TouchPad успешно добавлен в C++ проект.

Внесем необходимые дополнения в модуль Desktop.

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

Здесь мы определили ряд системных событий и добавили обработку событий TouchPad в метод update. Следует обратить внимание на метод checkBounce, задачей которого является предотвращние «дребезга», путем формирования правильной последовательности событий нажатия и отпускания TouchPad. Помимо этого, в Desktop добавлен обработчик событий изменения размеров экрана (например в результате изменения его ориентации).

Модуль Main изменяется незначительно.

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

Мы убрали непосредственную проверку нажатия кнопки s3eKeyAbsBSK и заменили вызов s3eDeviceCheckQuitRequest в условии цикла на вызов метода Desktop::isQuitMessageReceived. В основной цикл приложения добавлены вызовы методов Touchpad::update и clear. Кроме того, добавлены вызовы инициализации и завершения, для модуля TouchPad, в методы init и release соответственно.

Обработку клавиатурных событий поместим в модуль Scene.

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

Для каждой клавиши, занесенной в список прослушиваемых клавиш keys, мы проверяем состояние, формируем события клавиатуры и передаем переопределяемому методу doKeyMessage. Если этот метод не обрабатывает полученные события, мы выполняем обработку по умолчанию для s3eKeyBack и s3eKeyAbsBSK, заключающуюся в вызове метода desktop.sendQuitMessage, приводящего к завершению приложения.

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

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

В обработчике позиционного события sendMessage мы проверяем попадает ли точка касания в регион спрайта и если да, передаем непозиционное событие с тем-же кодом спрайту. Напомню, что в AbstractSpriteOwner::update мы поочередно вызываем sendMessage для всех спрайтов кроме Background-а, в порядке обратном Z-order (используемому для отрисовки спрайтов).

Для событий отпускания касания используется специальная обработка. Получив событие emtTouchDown. Sprite запоминает id-касания и передает последующие события с тем-же id в обработчик того-же спрайта, независимо от координат касания.

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

Исходные тексты текущей версии проекта можно получить здесь

Автор: GlukKazan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js