Чтобы лучше представлять себе, что мы собираемся делать, начнем со списка требований. Итак, разрабатываемый нами Framework должен:
- Быть событийно-ориентированным и, в том числе, поддерживать отложенные события (выполняющиеся через заданное количество миллисекунд)
- Поддерживать анимацию 2D-спрайтов, включающую их плавное перемещение, масштабирование и циклическое изменение картинки
- Поддерживать одновременную анимацию нескольких спрайтов
- Обрабатывать события touchPad-а (с корректной обработкой MultiTouch) и клавиатуры
- Поддерживать управление проигрыванием фоновой музыки и звуковыми эффектами (в том числе с проигрыванием нескольких звуков одновременно)
Сам процесс разработки будет последовательно описан в этой и последующих статьях. Ниже, представлена диаграмма классов:
Основные интерфейсы и классы:
- IObject — обеспечивает обработку событий и базовую функциональность отображения и обновления объекта
- IScreenObject — интерфейс объекта имеющего экранное отображение
- ISprite — интерфейс спрайта, позволяющий отобразить произвольный графический ресурс
- IAnimatedSprite — интерфейс анимированного спрайта, обеспечивающий доступ к нескольким графическим ресурсам, связанным с различными состояниями объекта
- ISpriteOwner — интерфейс контейнера спрайтов (сцены или составного спрайта)
- IAbstractSpriteOwner — абстрактная реализация экранного объекта, позволяющая отображать его, перемещать по экрану, масштабировать и т.п.
- Sprite — реализация спрайта
- Background — фоновое изображение
- AnimatedSprite — реализация анимированного спрайта, обеспечивающая обработку базовых событий (скрыть/показать объект, включить анимацию и.т.п.)
- CompositeSprite — реализация составного спрайта
Начнем разработку с построения mkb-файла, представляющего собой описание, по которому Marmalade построит C++ проект.
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) }
На пустые разделы пока можно не обращать внимания (они понадобятся нам в последующем). В разделе subprojects описываются подпроекты, которые мы используем (в настоящее время это только подсистема iw2d Marmalade, которая позволит нам работать с 2D-графикой). В includepath, как это очевидно из названия, перечисляем имена каталогов, содержащих h-файлы. В разделе files описываются исходные файлы (имя в квадратных скобках определяет имя папки в проекте MSVC, а путь в круглых скобках показывает, где эта папка размещается на диске). В разделе assets описываются ресурсы, используемые приложением.
Далее, заранее создадим заготовки 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; }
Это достаточно шаблонный код для проектов Marmalade. В init инициализируются все подсистемы, с которыми мы будем работать, далее создается сцена и ее наполнение, которые мы будем отображать и передается в desktop в качестве главной сцены (это делается в операторном блоке, для того чтобы обеспечить удаление объекта scene до вызова функции release).
В главном цикле приложения, мы проверяем условие выхода по нажатию кнопки «s3eKeyAbsBSK» (работу с клавиатурой мы рассмотрим в последующих статьях), после чего обновляем desktop, передавая ему текущее значение timestamp, очищаем экран, вызываем перерисовку desktop, отображаем изменения на экране вызовом Iw2DSurfaceShow, после чего передаем управление операционной системе, вызовом s3eDeviceYield. По завершении главного цикла, очищаем ресурсы в функции release.
Класс 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 (загрузка рисунка из ресурса) и refresh (отображение рисунка на экране). В последнем, с последовательностью вызовов SetRot, ScaleRot, SetTrans лучше не экспериментировать. Передавая в эти вызовы соответсвующие параметры, можно добиться поворота и масштабирования изображения, а также переноса его относительно начала координат.
В вызовах SetTrans и Iw2DrawImage, мы используем методы getXSize и getYSize для преобразования логических координат в экранные. Чуть позже, мы их рассмотрим. Следует отметить важное различие между ScaleRot и использованием третьего (необязательного параметра) Iw2DrawImage. Если первый из них позволяет выполнить афинное преобразование, масштабирующее изображение, то во втором случае, мы можем маштабировать исходное изображение независимо по осям X и Y (чтобы привести к требуемому aspect ratio).
Класс Background является наследником Sprite и переопределяет метод refresh. Экранный размер изображения не вычисляется на основании данных, предоставленных владельцем, а берется непосредственно из размеров desktop
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; }
Все готово. Запускаем наше приложение на выполнение… и получаем ошибку:
Дело в том, что динамическая память, на мобильных платформах, является крайне ценным ресурсом и в Marmalade, тщательно учитывается. Внесем необходимые изменения в файл настроек app.icf (заодно зафиксируем альбомную ориентацию экрана):
[S3E] DispFixRot=FixedLandscape MemSize=70000000 MemSizeDebug=70000000
На этом наши сегодняшние мытарства заканчиваются и, запустив приложение на выполнение еще раз, мы видим спрайт, отображенный поверх фоновой картинки.
ссылка на оригинал статьи http://habrahabr.ru/post/161681/
Добавить комментарий