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

このシリーズの記事では、 Marmaladeを使用して2Dゲームを作成するために設計された小さなフレームワークの開発について説明します。 マーマレードは、クロスプラットフォームアプリケーションを開発するためのAPIを提供し、AndroidやiOSを含むそれらを構築できるようにします。 マーマレードでの作業は非常に快適で、そのヘルプシステムには多数の例が付属していますが、開発プロセス自体は非常に低レベルです。 既製のフレームワークを使用すると、初心者の開発者の生活を大幅に促進できます。



これから何をするかをよりよく理解するために、要件のリストから始めます。 したがって、私たちが開発しているフレームワークは:







開発プロセス自体については、この記事と後続の記事で順次説明します。 以下はクラス図です:



画像



主なインターフェースとクラス:







開発は、MarkbadeがC ++プロジェクトを構築するための記述であるmkbファイルを構築することから始めます。



mf.mkb
#!/usr/bin/env mkb options { } subprojects { iw2d } includepath { ./source/Main ./source/Common ./source/Scene } files { [Main] (source/Main) Main.cpp Main.h Desktop.cpp Desktop.h [Common] (source/Common) IObject.h IScreenObject.h ISprite.h ISpriteOwner.h AbstractScreenObject.h AbstractScreenObject.cpp AbstractSpriteOwner.h AbstractSpriteOwner.cpp [Scene] (source/Scene) Scene.cpp Scene.h Background.cpp Background.h Sprite.cpp Sprite.h [Data] (data) } assets { (data) background.png sprite.png (data-ram/data-gles1, data) }
      
      









空のセクションは無視できます(後で必要になります)。 サブプロジェクトのセクションでは、使用するサブプロジェクトについて説明します(現在はiw2d Marmaladeサブシステムのみであり、2Dグラフィックを操作できます)。 includepathは、名前が示すように、hファイルを含むディレクトリの名前をリストします。 ファイルセクションでは、ソースファイルについて説明します(角括弧内の名前はMSVCプロジェクト内のフォルダの名前を定義し、括弧内のパスはディスク上のこのフォルダの場所を示します)。 資産セクションでは、アプリケーションで使用されるリソースについて説明します。



次に、hファイルとcppファイルの空白を事前に作成し、適切なプロジェクトフォルダーに配置します。その後、MKBファイルを起動して実行します。 すべてが正しく完了すると、Microsoft Visual Studioが開き、プロジェクトが表示されます。



画像



アプリケーションのメインループはMain.cppにあります。



Main.cpp
 #include "Main.h" #include "s3e.h" #include "Iw2D.h" #include "IwGx.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); desktop.init(); } void release() { desktop.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 (!s3eDeviceCheckQuitRequest()) { // Update keyboard system s3eKeyboardUpdate(); if ((s3eKeyboardGetState(s3eKeyAbsBSK) & S3E_KEY_STATE_DOWN) == S3E_KEY_STATE_DOWN) break; // Update desktop.update(s3eTimerGetMs()); // Clear the screen IwGxClear(IW_GX_COLOUR_BUFFER_F | IW_GX_DEPTH_BUFFER_F); // Refresh desktop.refresh(); // Show the surface Iw2DSurfaceShow(); // Yield to the opearting system s3eDeviceYield(duration); } } release(); return 0; }
      
      









これはマーマレードプロジェクトのかなり定型的なコードです。 initでは、作業するすべてのサブシステムが初期化され、次にシーンとそのコンテンツが作成され、メインシーンとして表示され、デスクトップに渡されます(これは、リリース関数が呼び出される前にシーンオブジェクトが削除されるようにオペレーターユニットで行われます) 。



アプリケーションのメインサイクルでは、「s3eKeyAbsBSK」ボタンを押して終了条件を確認します(以降の記事でキーボードの操作を検討します)。その後、デスクトップを更新し、現在のタイムスタンプ値を渡し、画面をクリアし、デスクトップの再描画を呼び出し、Iw2DSurfaceShowを呼び出して画面に変更を表示します、その後s3eDeviceYieldを呼び出してオペレーティングシステムに制御を移します。 メインループの最後で、リリース関数のリソースをクリアします。



Desktopクラスは、デバイス画面との対話を提供します。



Desktop.h
 #ifndef _DESKTOP_H_ #define _DESKTOP_H_ #include "Scene.h" class Desktop { private: int width, height; Scene* currentScene; public: 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); }; extern Desktop desktop; #endif // _DESKTOP_H_
      
      









画面サイズは、Iw2DGetSurfaceWidthおよびIw2DGetSurfaceHeight呼び出しを使用して取得されます。



Desktop.cpp
 #include "Desktop.h" #include "Iw2D.h" Desktop desktop; void Desktop::init() { width = Iw2DGetSurfaceWidth(); height = Iw2DGetSurfaceHeight(); setScene(NULL); } void Desktop::setScene(Scene* scene) { if (scene != NULL) { scene->init(); } currentScene = scene; } void Desktop::update(uint64 timestamp) { if (currentScene != NULL) { currentScene->update(timestamp); } } void Desktop::refresh() { if (currentScene != NULL) { currentScene->refresh(); } }
      
      







以前に開発したアーキテクチャに従って必要なインターフェイスを作成します。



IObject.h
 #ifndef _IOBJECT_H_ #define _IOBJECT_H_ #include "s3e.h" class IObject { public: virtual ~IObject() {} virtual bool isBuzy() = 0; virtual int getState() = 0; virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL) = 0; virtual bool sendMessage(int msg, int x, int y) = 0; virtual void update(uint64 timestamp) = 0; virtual void refresh() = 0; }; #endif // _IOBJECT_H_
      
      







IScreenObject.h
 #ifndef _ISCREENOBJECT_H_ #define _ISCREENOBJECT_H_ #include "s3e.h" #include "IObject.h" class IScreenObject: public IObject { public: virtual int getXPos() = 0; virtual int getYPos() = 0; virtual int getWidth() = 0; virtual int getHeight() = 0; }; #endif // _ISCREENOBJECT_H_
      
      







ISprite.h
 #ifndef _ISPRITE_H_ #define _ISPRITE_H_ #include "Locale.h" #include "Iw2D.h" #include "IwGx.h" class ISprite { public: virtual void addImage(const char* res, int state = 0) = 0; virtual CIw2DImage* getImage(int state = 0) = 0; }; #endif // _ISPRITE_H_
      
      







ISpriteOwner.h
 #ifndef _ISPRITEOWNER_H_ #define _ISPRITEOWNER_H_ #include "IObject.h" #include "AbstractScreenObject.h" class ISpriteOwner: public IObject { public: virtual void addSprite(AbstractScreenObject* sprite, int zOrder) = 0; virtual bool setZOrder(AbstractScreenObject* sprite, int z) = 0; virtual int getDesktopWidth() = 0; virtual int getDesktopHeight() = 0; virtual int getXSize(int xSize) = 0; virtual int getYSize(int ySize) = 0; virtual int getXPos(int x) = 0; virtual int getYPos(int y) = 0; }; #endif // _ISPRITEOWNER_H_
      
      









補助クラスAbstractScreenObjectでは、参照カウンターを維持し、スプライトの場所のパラメーターを保存します。



AbstractScreenObject.h
 #ifndef _ABSTRACTSCREENOBJECT_H_ #define _ABSTRACTSCREENOBJECT_H_ #include <string> #include "Iw2D.h" #include "IScreenObject.h" using namespace std; class AbstractScreenObject: public IScreenObject { private: static int idCounter; int id; int usageCounter; protected: virtual bool init(); CIw2DAlphaMode alpha; int xPos, yPos, angle; int xDelta, yDelta; bool isVisible; bool isInitialized; public: AbstractScreenObject(int x, int y); virtual ~AbstractScreenObject() {} int getId() const {return id;} void incrementUsage(); bool decrementUsage(); virtual int getXPos() {return xPos + xDelta;} virtual int getYPos() {return yPos + yDelta;} virtual int getWidth() {return 0;} virtual int getHeight() {return 0;} virtual bool isBackground() {return false;} virtual bool isBuzy() {return false;} int getAngle() const {return angle;} void move(int x = 0, int y = 0); void setXY(int x = 0, int y = 0); void clearXY(); void setAngle(int a) {angle = a;} void setAlpha(CIw2DAlphaMode a) {alpha = a;} bool setState(int state) {return false;} }; #endif // _ABSTRACTSCREENOBJECT_H_
      
      







AbstractScreenObject.cpp
 #include "AbstractScreenObject.h" #include "Desktop.h" int AbstractScreenObject::idCounter = 0; AbstractScreenObject::AbstractScreenObject(int x, int y): xPos(x), alpha(IW_2D_ALPHA_NONE),yPos(y), angle(0), xDelta(0), yDelta(0), isVisible(true), isInitialized(false), usageCounter(0) { id = ++idCounter; } bool AbstractScreenObject::init() { bool r = !isInitialized; isInitialized = true; return r; } void AbstractScreenObject::incrementUsage() { usageCounter++; } bool AbstractScreenObject::decrementUsage() { usageCounter--; return (usageCounter == 0); } void AbstractScreenObject::move(int x, int y) { xDelta += x; yDelta += y; } void AbstractScreenObject::setXY(int x, int y) { xPos = x; yPos = y; } void AbstractScreenObject::clearXY() { xDelta = 0; yDelta = 0; }
      
      









スプライトの実装に目を向けます。



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; 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) {return false;} 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" Sprite::Sprite(ISpriteOwner* owner, int x, int y, int zOrder): AbstractScreenObject(x, y) , owner(owner) , 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) , 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); } 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()))); } }
      
      









ここでは、addImageメソッド(リソースから画像をロードする)と更新(画像を画面に表示する)に注意する価値があります。 後者では、SetRot、ScaleRot、SetTransの呼び出しシーケンスを実験しない方が良いです。 これらの呼び出しに適切なパラメーターを渡すことで、画像の回転と拡大縮小を実現し、原点からの相対位置に画像を転送できます。



SetTransおよびIw2DrawImageの呼び出しでは、getXSizeおよびgetYSizeメソッドを使用して、論理座標を画面座標に変換します。 少し後で、それらを検討します。 ScaleRotと3番目の(オプションのパラメーター)Iw2DrawImageの使用の重要な違いに注意する必要があります。 それらの最初のもので画像のスケーリングをアフィン変換を実行できる場合、2番目の場合、XおよびY軸に沿って元の画像を個別にスケーリングできます(必要なアスペクト比を実現するため)。



BackgroundクラスはSpriteを継承し、refreshメソッドをオーバーライドします。 画像の画面サイズは、所有者から提供されたデータに基づいて計算されるのではなく、デスクトップサイズから直接取得されます



Background.h
 #ifndef _BACKGROUND_H_ #define _BACKGROUND_H_ #include "Sprite.h" #include "Locale.h" class Background: public Sprite { public: Background(ISpriteOwner* owner, const char* res, int zOrder); virtual bool isBackground() {return true;} virtual void refresh(); }; #endif // _BACKGROUND_H_
      
      









Background.cpp
 #include "Background.h" Background::Background(ISpriteOwner* owner, const char* res, int zOrder): Sprite(owner, res, 0, 0, zOrder) {} void Background::refresh() { CIwMat2D m; m.SetRot(0); m.ScaleRot(IW_GEOM_ONE); m.SetTrans(CIwSVec2(0, 0)); Iw2DSetTransformMatrix(m); Iw2DSetAlphaMode(alpha); Iw2DDrawImage(img, CIwSVec2(0, 0), CIwSVec2(owner->getDesktopWidth(), owner->getDesktopHeight())); }
      
      









AbstractSpriteOwnerは、スプライトのストレージを管理し、画面上の表示のZシーケンスを実行します。



AbstractSpriteOwner.h
 #ifndef _ABSTRACTSPRITEOWNER_H_ #define _ABSTRACTSPRITEOWNER_H_ #include <map> #include "IObject.h" #include "ISpriteOwner.h" #include "AbstractScreenObject.h" using namespace std; class AbstractSpriteOwner: public ISpriteOwner { protected: multimap<int, AbstractScreenObject*> zOrder; public: AbstractSpriteOwner(); virtual ~AbstractSpriteOwner(); virtual void addSprite(AbstractScreenObject* sprite, int z); virtual bool setZOrder(AbstractScreenObject* sprite, int z); virtual int getDesktopWidth(); virtual int getDesktopHeight(); virtual int getState() {return 0;} virtual void update(uint64 timestamp); virtual void refresh(); virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL) {return false;} virtual bool sendMessage(int msg, int x, int y); typedef multimap<int, AbstractScreenObject*>::iterator ZIter; typedef multimap<int, AbstractScreenObject*>::reverse_iterator RIter; typedef pair<int, AbstractScreenObject*> ZPair; }; #endif // _ABSTRACTSPRITEOWNER_H_
      
      







AbstractSpriteOwner.cpp
 #include "AbstractSpriteOwner.h" #include "Desktop.h" #include "ISprite.h" AbstractSpriteOwner::AbstractSpriteOwner(): zOrder() {} AbstractSpriteOwner::~AbstractSpriteOwner() { for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) { if (p->second->decrementUsage()) { delete p->second; } } } void AbstractSpriteOwner::addSprite(AbstractScreenObject* sprite, int z) { sprite->incrementUsage(); zOrder.insert(ZPair(z, sprite)); } bool AbstractSpriteOwner::setZOrder(AbstractScreenObject* sprite, int z) { for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) { if (p->second == sprite) { zOrder.erase(p); zOrder.insert(ZPair(z, sprite)); return true; } } return false; } int AbstractSpriteOwner::getDesktopWidth() { return desktop.getWidth(); } int AbstractSpriteOwner::getDesktopHeight() { return desktop.getHeight(); } void AbstractSpriteOwner::update(uint64 timestamp) { for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) { p->second->update(timestamp); } } void AbstractSpriteOwner::refresh() { for (ZIter p = zOrder.begin(); p != zOrder.end(); ++p) { p->second->refresh(); } } bool AbstractSpriteOwner::sendMessage(int msg, int x, int y) { for (RIter p = zOrder.rbegin(); p != zOrder.rend(); ++p) { if (p->second->isBackground()) continue; if (p->second->sendMessage(msg, x, y)) { return true; } } return false; }
      
      









AbstractSpriteOwnerの後継はSceneです:



Scene.h
 #ifndef _SCENE_H_ #define _SCENE_H_ #include "s3eKeyboard.h" #include "AbstractSpriteOwner.h" #include "AbstractScreenObject.h" using namespace std; class Scene: public AbstractSpriteOwner { private: AbstractScreenObject* background; bool isInitialized; 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); }; #endif // _SCENE_H_
      
      







Scene.cpp
 #include "Scene.h" #include "Desktop.h" Scene::Scene(): AbstractSpriteOwner() , isInitialized(false) , background(NULL) {} 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::update(uint64 timestamp) { 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; }
      
      









すべて準備完了です。 実行時にアプリケーションを起動すると...エラーが発生します:



画像



実際、モバイルプラットフォームのダイナミックメモリは非常に貴重なリソースであり、マーマレードでは慎重に考慮されています。 app.icf設定ファイルに必要な変更を加えます(同時に、横画面の方向を修正します)。



app.icf
 [S3E] DispFixRot=FixedLandscape MemSize=70000000 MemSizeDebug=70000000
      
      









これが今日の試練の終わりであり、アプリケーションを再度起動すると、背景画像の上にスプライトが表示されます。 次の記事では、イベント処理について説明します。



現在のバージョンのソースコードはこちらです。




All Articles