マーマレードのフレームワーク(パート2)

前回の記事でMarmaladeツールシステムを使用して2Dゲームを作成するための小さなフレームワークの開発について話し始めました。Marmaladeツールシステムは、iOSおよびAndroidを含む多くのプラットフォームのアプリケーションを開発および構築する機能を提供します。 グラフィックリソースの使用方法を学び、小さなテストアプリケーションを構築しました。 今日は、イベント処理についてお話したいと思います。



モバイルプラットフォームでのイベントの主なソースはTouchPadです。これは、タッチスクリーンとのユーザーの対話に関する情報をアプリケーションに提供します。 最近のほとんどのデバイスでは、マルチタッチテクノロジーがサポートされており、複数のタッチを一度に追跡できます。 もちろん、ゲームフレームワークはマルチタッチイベントを正しく処理する必要があります。



イベントのわずかに重要性の低いソース(モバイルプラットフォーム)はキーボードです。 実際、最新のモバイルデバイスには、意味のある情報を受信するためのボタンがそれほど多くありません。 ただし、アプリケーションが適切に動作するためには、少なくとも1つのキーボードイベントが必要です(少なくともAndroid用に開発する場合)。 「戻る」ボタンを押してトリガーされるs3eKeyAbsBSKイベントの処理についてです。 Android開発契約によると、このボタンをクリックすると、現在のアクティビティが閉じて、インターフェースが以前のアクティビティに戻ります。



この記事では、他のイベントソース(加速度計など)は考慮しません。これらはゲームアプリケーションの開発に使用されますが、考慮するには別の記事を書く必要があるためです。



タッチパッドイベント処理を追加して、Marmalade Frameworkの改良を開始します。 新しいファイルの説明を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); } } }
      
      







ご覧のとおり、イベント処理はかなり低レベルです。 タッチパッドモジュールは、アプリケーションの開発からこの事実を隠す必要があります。 プロジェクトを更新するには、mkbファイルを実行して再度実行します。 ダウンロードが完了すると、TouchPadモジュールがC ++プロジェクトに正常に追加されたことがわかります。



デスクトップモジュールに必要な追加を行います。



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イベント処理を追加しました。 checkBounceメソッドに注意する必要があります。このメソッドのタスクは、タッチパッドを押したり放したりするイベントの正しいシーケンスを形成することにより、「バウンス」を防ぐことです。 さらに、画面サイズを変更するためのイベントハンドラがデスクトップに追加されました(たとえば、向きを変更した結果として)。



メインモジュールはわずかに変更されます。



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メソッドの呼び出しに置き換えました。 タッチパッドへの呼び出し:: updateおよびclearメソッドがメインアプリケーションループに追加されました。 さらに、TouchPadモジュールの初期化呼び出しと完了呼び出しが、それぞれinitメソッドとreleaseメソッドに追加されました。



キーボードイベントの処理は、シーンモジュールに配置されます。



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







リッスンされたキーのリストに含まれる各キーについて、ステータスを確認し、キーボードイベントを生成して、再定義されたメソッド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では、バックグラウンドを除くすべてのスプライトに対して、Zオーダーの逆順(スプライトの描画に使用)でsendMessageを1つずつ呼び出します。



タッチリリースイベントでは、特別な処理が使用されます。 emtTouchDownイベントを受け取った。 スプライトはIDタッチを記憶し、タッチの座標に関係なく、同じIDを持つ後続のイベントを同じスプライトのハンドラーに渡します。



そのため、イベントの処理方法を学びましたが、イベントの処理に関連する顕著なアクティビティを示すために、まだやるべきことがたくさんあります。 次の記事では、リソースとサウンドを扱う方法を学びます。



プロジェクトの現在のバージョンのソースコードは、 ここから入手できます。



以下の資料は 、マーマレードフレームワークの開発に使用されました。




All Articles