В этой серии статей я опишу разработку небольшого Framework, предназначенного для создания 2D-игр с использованием Мармелад .
Marmalade предоставляет API для разработки кроссплатформенных приложений, позволяющий создавать их, в том числе для Android и iOS. Работать в Marmalade довольно комфортно, справочная система сопровождается большим количеством примеров, однако сам процесс разработки носит достаточно низкоуровневый характер.
Использование готового Framework может существенно облегчить жизнь начинающему разработчику.
Чтобы лучше понять, что мы собираемся делать, начнем со списка требований.
Итак, разрабатываемая нами структура должна:
- Быть ориентированным на события и, среди прочего, поддерживать отложенные события (выполняются через определенное количество миллисекунд).
- Поддержка анимации 2D-спрайтов, включая их плавное перемещение, масштабирование и циклическое перемещение изображения.
- Поддержка одновременной анимации нескольких спрайтов.
- Обрабатывать сенсорную панель (с правильной обработкой MultiTouch) и события клавиатуры.
- Поддержка управления воспроизведением фоновой музыки и звуковыми эффектами (включая одновременное воспроизведение нескольких звуков)
Ниже приведена диаграмма классов:
Основные интерфейсы и классы:
- IObject — обеспечивает обработку событий, а также базовые функции отображения и обновления объектов.
- IScreenObject — интерфейс объекта, имеющего экранное отображение.
- ISprite — спрайт-интерфейс, позволяющий отображать произвольный графический ресурс.
- IAnimatedSprite — интерфейс анимированных спрайтов, обеспечивающий доступ к множеству графических ресурсов, связанных с различными состояниями объектов.
- ISpriteOwner — интерфейс для контейнера спрайтов (сцены или составного спрайта)
- IAbstractSpriteOwner — абстрактная реализация экранного объекта, позволяющая отображать его, перемещать по экрану, масштабировать и т. д.
- Спрайт — реализация спрайта
- Фон - фоновое изображение
- AnimatedSprite — реализация анимированного спрайта, обеспечивающая обработку базовых событий (скрыть/показать объект, включить анимацию и т.д.)
- CompositeSprite — реализация составного спрайта
мкб
На данный момент вы можете игнорировать пустые разделы (они нам понадобятся позже).#!/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-файлы.
В разделе files описаны исходные файлы (имя в квадратных скобках указывает имя папки в проекте 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;
}
Это довольно шаблонный код для проектов Marmalade. В init инициализируются все подсистемы, с которыми мы будем работать, затем создается сцена и ее контент, который мы будем отображать и передавать на рабочий стол в качестве основной сцены (это делается в операторном блоке для того, чтобы сцена объект удаляется перед вызовом функции выпуска).
В основном цикле приложения проверяем условие выхода нажатием кнопки «s3eKeyAbsBSK» (работу с клавиатурой мы рассмотрим в последующих статьях), после чего обновляем рабочий стол, передавая ему текущее значение временной метки, очищаем screen, вызываем перерисовку рабочего стола, отображаем изменения на экране вызовом Iw2DSurfaceShow, после чего передаем управление операционной системе вызовом s3eDeviceYield. По завершении основного цикла мы очищаем ресурсы в функции выпуска.
Класс 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. Рабочий стол.
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;
}
Перейдем к реализации спрайтов.
Спрайт.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_
Спрайт.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 (чтобы привести к требуемому соотношению сторон).
Класс Background является потомком Sprite и переопределяет метод обновления.
ЭРазмер экрана изображения не рассчитывается на основе данных, предоставленных владельцем, а берется непосредственно из размеров рабочего стола.
Фон.
ч #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_
Фон.
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: Сцена.
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_
Сцена.
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 (заодно исправим альбомную ориентацию экрана): app.icf [S3E]
DispFixRot=FixedLandscape
MemSize=70000000
MemSizeDebug=70000000
На этом наши сегодняшние мытарства заканчиваются и, запустив приложение еще раз, мы видим спрайт, отображенный поверх фонового изображения.
В следующий статья мы поговорим об обработке событий.
Исходные тексты текущей версии Здесь Теги: #мармелад #Android #iOS #разработка для iOS #разработка для Android
-
Терминал В Сбербанке
19 Oct, 24 -
О Чем Говорят Мужчины? Подарочная Версия
19 Oct, 24