MyTetra Share
Делитесь знаниями!
Маленький отважный арканоид. Использование Box2D
26.11.2017
16:44
Автор: GlukKazan
Текстовые метки: box2d, arcanoid, арканоид
Раздел: Компьютер - Программирование - Физические движки - Box2D

Маленький отважный арканоид (часть 3 — Box2D)


Сегодня, как я и обещал, мы вдохнем в наш Arcanoid жизнь. Заставим шарик двигаться, сталкиваясь с кирпичами, а кирпичи, при этом, разбиваться. В принципе, игровая физика в arcanoid не так чтобы очень сложна и вполне реализуема собственными силами. Единственный нетривиальный момент в ней — отслеживание столкновений. Но это именно то, что «взрослые» физические движки умеют лучше всего!

Так почему бы их не использовать? К тому-же, если мы оформим Box2D в виде модуля Marmalade, впоследствии, мы сможем использовать его и в других приложениях, возможно требующих более изощренной «физики». Давайте этим займемся.

Методика оформления Box2D в виде подпроекта совершенно аналогична той, которую мы использовали по отношению к LibYAML в предыдущей статье. Единственное отличие в том, что в Box2D гораздо больше исходных файлов. Поэтому, если нет желания повторять рутинное переписывание их имен в mkf-файл, уже выполненное мной, можно взять готовый модуль непосредственно с GitHub. Дистрибутив Box2D взят отсюда.

Итак, добавляем Box2D в наш проект:

arcanoid.mkb

#!/usr/bin/env mkb

options

{

module_path="../yaml"

+ module_path="../box2d"

}

subprojects

{

iwgl

yaml

+ box2d

}


includepath

{

./source/Main

./source/Model

}

files

{

[Main]

(source/Main)

Main.cpp

Main.h

Quads.cpp

Quads.h

Desktop.cpp

Desktop.h

IO.cpp

IO.h


[Model]

(source/Model)

Bricks.cpp

Bricks.h

Ball.cpp

Ball.h

Board.cpp

Board.h

}

assets

{

(data)

level.json

}


… и пытаемся все это скомпилировать, попутно внося в Box2D косметические исправления из разряда «сделаем компилятор счастливым»:

Collisionb2BroadPhase.h

- for (int32 i = 0; i < m_moveCount; ++i)

+ for (int32 j = 0; j < m_moveCount; ++j)

{

- m_queryProxyId = m_moveBuffer[i];

+ m_queryProxyId = m_moveBuffer[j];

...

}


...

while (i < m_pairCount)

{

...

}


Commonb2Math.h

/// A 2D column vector.

struct b2Vec2

{

/// Default constructor does nothing (for performance).

- b2Vec2() {}

+ b2Vec2(): x(0.0f), y(0.0f) {}


/// Construct using coordinates.

b2Vec2(float32 x, float32 y) : x(x), y(y) {}


...

float32 x, y;

};


Если после этого вы получаете ошибку связывания:

… то это, скорее всего означает, что вам также как и мне, нравится MSVS 2003. GCC, при этом, собирает проект без ошибок, но нам, конечно, хотелось бы иметь возможность запускать его и под отладчиком тоже. Как бы там ни было, от MSVS 2003 придется отказаться. В принципе, достаточно переключиться на MSVS 2005, но я сразу поставил MSVS 2010, благо она была под рукой. Само переключение осуществляется при помощи Marmalade Configuration Utility.

Ну что-же, пора браться за дело. Если в первой статье мы имели дело с «миром иллюзий», во втором с «миром идей», то теперь пришла пора создать «реальный мир», который у нас будет отвечать за физические взаимодействия объектов. Добавим новые файлы в проект:

arcanoid.mkb

#!/usr/bin/env mkb

options

{

module_path="../yaml"

module_path="../box2d"

}

subprojects

{

iwgl

yaml

box2d

}


includepath

{

./source/Main

./source/Model

}

files

{

[Main]

(source/Main)

Main.cpp

Main.h

Quads.cpp

Quads.h

Desktop.cpp

Desktop.h

IO.cpp

IO.h

+ World.cpp

+ World.h


[Model]

(source/Model)

Bricks.cpp

Bricks.h

Ball.cpp

Ball.h

Board.cpp

Board.h

+ IBox2DItem.h

}

assets

{

(data)

level.json

}


Интерфейс IBox2DItem будет отвечать за передачу событий из Box2D в нашу модель данных. Для наших целей, пока достаточно всего двух методов:

IBox2DItem.h

#ifndef _I_BOX2D_ITEM_H_

#define _I_BOX2D_ITEM_H_


#include <Box2D.h>


class IBox2DItem {

public:

virtual void setXY(int X, int Y) {}

virtual bool impact(b2Body* b) {return false;}

};


#endif // _I_BOX2D_ITEM_H_


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

Метод setXY позволит нам передавать изменения координат движущихся объектов (для того, чтобы эти изменения можно было отобразить на экране), а метод impact позволит нам отслеживать соударения объектов, чуть позже.

World.h

#ifndef _WORLD_H_

#define _WORLD_H_


#include <vector>

#include <Box2D.h>

#include "Desktop.h"

#include "IBox2DItem.h"


const int HALF_MARGIN = 10;

const int V_ITERATIONS = 10;

const int P_ITERATIONS = 10;


const float FRICTION = 0.0f;

const float RESTITUTION = 1.0f;

const float DYN_DENSITY = 0.0f;

const float R_INVIS = 0.0f;

const float EPS = 1.0f;

const float SPEED_SQ = 10.0f;


using namespace std;


class World {

private:

bool isStarted;

int HandleX, HandleH, HandleW;

uint64 timestamp;

int width, height;

b2World* wp;

b2Body* ground;

b2Body* ball;

b2Body* handle;

b2Body* createBox(int x, int y, int hw, int hh, IBox2DItem* userData = NULL);

float32 getTimeStep();

void start();

public:

World(): width(0), height(0), wp(NULL) {}

void init();

void release();

void update();

void refresh();

b2Body* addBrick(int x, int y, int hw, int hh, IBox2DItem* userData) {return createBox(x, y, hw, hh, userData);}

b2Body* addBall(int x, int y, int r, IBox2DItem* userData);

b2Body* addHandle(int x, int y, int hw, int hh, IBox2DItem* userData);

void moveHandle(int x, int y);


typedef vector<b2Body*>::iterator BIter;

};


extern World world;


#endif // _WORLD_H_


Для этого модуля, рассмотрим реализацию подробнее:

World.cpp

#include "s3e.h"

#include "World.h"

#include "Ball.h"


World world;


void World::init() {

isStarted = false;

width = desktop.getWidth();

height = desktop.getHeight();

b2Vec2 gravity(0.0f, 0.0f);

wp = new b2World(gravity);

ground = createBox(width/2, -HALF_MARGIN, width/2, HALF_MARGIN);

createBox(-HALF_MARGIN, height/2, HALF_MARGIN, height/2);

createBox(width/2, height + HALF_MARGIN, width/2, HALF_MARGIN);

createBox(width + HALF_MARGIN, height/2, HALF_MARGIN, height/2);

ball = NULL;

handle = NULL;

}


void World::release() {

if (wp != NULL) {

delete wp;

wp = NULL;

ball = NULL;

handle = NULL;

}

}

...


Методы init и release занимаются корректным созданием и уничтожением основных объектов «мира». Обращаю внимание, что гравитацию мы выставляем в 0 (у нас будет невесовмость), а игровое поле окружаем четырьмя «стенами» (одну из них потом можно будет легко убрать).

Далее определяем методы для создания игровых объектов:

World.cpp

...

b2Body* World::createBox(int x, int y, int hw, int hh, IBox2DItem* userData) {

b2BodyDef def;

def.type = b2_staticBody;

def.position.Set(x, y);

b2Body* r = wp->CreateBody(&def);

b2PolygonShape box;

box.SetAsBox(hw, hh);

b2FixtureDef fd;

fd.shape = &box;

fd.density = 0;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

r->CreateFixture(&fd);

r->SetUserData(userData);

return r;

}


b2Body* World::addBall(int x, int y, int r, IBox2DItem* userData) {

if (ball != NULL) {

wp->DestroyBody(ball);

}

b2BodyDef def;

def.type = b2_dynamicBody;

def.linearDamping = 0.0f;

def.angularDamping = 0.0f;

def.position.Set(x, y);

ball = wp->CreateBody(&def);

b2CircleShape shape;

shape.m_p.SetZero();

shape.m_radius = r + R_INVIS;

b2FixtureDef fd;

fd.shape = &shape;

fd.density = DYN_DENSITY;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

ball->CreateFixture(&fd);

ball->SetBullet(true);

ball->SetUserData(userData);

return ball;

}

...


Здесь мы создаем прямоугольный объект (стена или кирпич) и шарик. Кроме формы они отличаются типом. Кирпичи являются статическими (неподвижными) объектами, а шарик динамическим. Box2D требует разделять игровые объекты на два этих типа, из соображений производительности. Также, мы задаем такие физические свойства объектов как упругость, коэффициент трения и т.п. Для удобства, они определены константами в h-файле.

В нашем случае, моделируются абсолютно упругие соударения (RESTITUTION = 1), при отсутствии трения (FRICTION = 0). Также в ноль выставляем параметры linearDamping и angularDamping, отвечающие за торможение движущегося объекта «средой». Первоначально, была идея выставить ненулевое значение FRICTION, чтобы была возможность «подкручивать» шарик ракеткой, но от нее пришлось отказаться. При установке FRICTION в любое ненулевое значение, движение шарика очень быстро вырождается в чистое движение по вертикали или горизонтали.

В userData для body и fixture можно хранить любой указатель. Мы будем хранить там указатель на интерфейс IBox2DItem соответствующих объектов в нашей модели.

World.cpp

...

float32 World::getTimeStep() {

uint64 t = s3eTimerGetMs();

int r = (int)(t - timestamp);

timestamp = t;

return (float32)r / 1000.0f;

}


void World::start() {

if (ball != NULL) {

ball->ApplyLinearImpulse(ball->GetWorldVector(b2Vec2(-10.0f, -10.0f)),

ball->GetWorldPoint(b2Vec2(0.0f, 0.0f)));

}

}


void World::update() {

if (!isStarted) {

isStarted = true;

start();

timestamp = s3eTimerGetMs();

srand((unsigned int)timestamp);

} else {

float32 timeStep = getTimeStep();

wp->Step(timeStep, V_ITERATIONS, P_ITERATIONS);

}

}


void World::refresh() {

if (ball != NULL) {

b2Vec2 pos = ball->GetPosition();

Ball* b = (Ball*)ball->GetUserData();

if (b != NULL) {

b->setXY(pos.x, pos.y);

}

}

}


В методе update мы рассчитываем очередную итерацию существования «мира» методом Step, в который передается три аргумента. Первый аргумент — интервал времени на который производится рассчет. В руководстве пользователя Box2D рекомендуется использовать интервал ~1/60 секунды. Также, настоятельно рекомендуется чтобы он был константным. Следующие два параметра определяют количество итераций при выполнении расчетов и напрямую влияют на качество моделирования. Я передаю в оба параметра значение 10.

При первом вызове метода update, мы придаем шарику начальную скорость. Поскольку все соударения идеально упруги, скорость движения шарика после соударений не уменьшается и однократного задания начальной скорости нам вполне достаточно. При необходимости, мы можем скорректировать скорость между вызовами метода update (ни в коем случае не следует выполнять каких либо манипуляций с объектами в контексте вызова b2World.Step, это, скорее всего, приведет к немедленному разрушению памяти).

Задачей метода refresh является получение измененных координат шарика (после очередного шага расчетов) и передача измененных координат интерфейсу IBox2DItem.

Внесем необходимые изменения в модель:

Bricks.h

#ifndef _BRICKS_H_

#define _BRICKS_H_


#include "IwGL.h"

#include "s3e.h"

#include "Desktop.h"

+#include "World.h"

+#include "IBox2DItem.h"


#define BRICK_COLOR_1 0xffffff00

#define BRICK_COLOR_2 0xff50ff00

#define BRICK_HALF_WIDTH 20

#define BRICK_HALF_HEIGHT 10


#include <vector>


using namespace std;


-class Bricks {

+class Bricks: public IBox2DItem {

private:

struct SBrick {

SBrick(int x, int y): x(x),

y(y),

+ body(NULL),

+ isBroken(false),

hw(BRICK_HALF_WIDTH),

hh(BRICK_HALF_HEIGHT),

ic(BRICK_COLOR_1),

oc(BRICK_COLOR_2) {}

SBrick(const SBrick& p): x(p.x),

y(p.y),

+ body(p.body),

+ isBroken(p.isBroken),

hw(p.hw),

hh(p.hh),

ic(p.ic),

oc(p.oc) {}

int x, y, hw, hh, ic, oc;

+ int isBroken;

+ b2Body* body;

};

vector<SBrick> bricks;

public:

Bricks(): bricks() {}

+ void init() {}

+ void release() {}

void refresh();

void clear(){bricks.clear();}

void add(SBrick& b);


typedef vector<SBrick>::iterator BIter;


friend class Board;

};


#endif // _BRICKS_H_


Bricks.cpp

#include "Bricks.h"

#include "Quads.h"


void Bricks::refresh() {

for (BIter p = bricks.begin(); p != bricks.end(); ++p) {

+ if (p->isBroken) continue;

CIwGLPoint point(p->x, p->y);

point = IwGLTransform(point);


int16* quadPoints = quads.getQuadPoints();

uint32* quadCols = quads.getQuadCols();

if ((quadPoints == NULL) || (quadCols == NULL)) break;


*quadPoints++ = point.x - p->hw;

*quadPoints++ = point.y + p->hh;

*quadCols++ = p->ic;


*quadPoints++ = point.x + p->hw;

*quadPoints++ = point.y + p->hh;

*quadCols++ = p->oc;


*quadPoints++ = point.x + p->hw;

*quadPoints++ = point.y - p->hh;

*quadCols++ = p->ic;


*quadPoints++ = point.x - p->hw;

*quadPoints++ = point.y - p->hh;

*quadCols++ = p->oc;

}

}


void Bricks::add(SBrick& b) {

+ b.body = world.addBrick(b.x, b.y, b.hw, b.hh, (IBox2DItem*)this);

bricks.push_back(b);

}


Ball.h

#ifndef _BALL_H_

#define _BALL_H_


#include <vector>

#include "IwGL.h"

#include "s3e.h"

#include "Desktop.h"

+#include "World.h"

+#include "IBox2DItem.h"


#define MAX_SEGMENTS 7

#define BALL_COLOR_1 0x00000000

#define BALL_COLOR_2 0xffffffff

#define BALL_RADIUS 15


using namespace std;


-class Ball {

+class Ball: public IBox2DItem {

private:

struct Offset {

Offset(int dx, int dy): dx(dx), dy(dy) {}

Offset(const Offset& p): dx(p.dx), dy(p.dy) {}

int dx, dy;

};

vector<Offset> offsets;

int x;

int y;

+ b2Body* body;

public:

void init();

void release() {}

void refresh();

virtual void setXY(int X, int Y);


typedef vector<Offset>::iterator OIter;

};


#endif // _BALL_H_


Ball.cpp

#include "Ball.h"

#include "Quads.h"

#include "Desktop.h"

#include <math.h>


#define PI 3.14159265f


void Ball::init(){

x = desktop.getWidth() / 2;

y = desktop.getHeight()/ 2;


float delta = PI / (float)MAX_SEGMENTS;

float angle = delta / 2.0f;

float r = (float)desktop.toRSize(BALL_RADIUS);

for (int i = 0; i < MAX_SEGMENTS; i++) {

offsets.push_back(Offset((int16)(cos(angle) * r), (int16)(sin(angle) * r)));

angle = angle + delta;

offsets.push_back(Offset((int16)(cos(angle) * r), (int16)(sin(angle) * r)));

angle = angle + delta;

offsets.push_back(Offset((int16)(cos(angle) * r), (int16)(sin(angle) * r)));

}

+ body = world.addBall(x, y, (int)r, (IBox2DItem*)this);

}


void Ball::setXY(int X, int Y) {

x = X;

y = Y;

}


void Ball::refresh() {

CIwGLPoint point(x, y);

point = IwGLTransform(point);

OIter o = offsets.begin();

int r = desktop.toRSize(BALL_RADIUS);

for (int i = 0; i < MAX_SEGMENTS; i++) {


int16* quadPoints = quads.getQuadPoints();

uint32* quadCols = quads.getQuadCols();

if ((quadPoints == NULL) || (quadCols == NULL)) break;


*quadPoints++ = point.x + (r / 4);

*quadPoints++ = point.y + (r / 4);

*quadCols++ = BALL_COLOR_2;


*quadPoints++ = point.x + o->dx;

*quadPoints++ = point.y + o->dy;

*quadCols++ = BALL_COLOR_1;

o++;


*quadPoints++ = point.x + o->dx;

*quadPoints++ = point.y + o->dy;

*quadCols++ = BALL_COLOR_1;

o++;


*quadPoints++ = point.x + o->dx;

*quadPoints++ = point.y + o->dy;

*quadCols++ = BALL_COLOR_1;

o++;

}

}


Здесь все изменения очевидны. Далее вносим изменения в Main:

Main.cpp

#include "Main.h"


#include "s3e.h"

#include "IwGL.h"


#include "Desktop.h"

+#include "World.h"

#include "IO.h"

#include "Quads.h"

#include "Board.h"


Board board;


void init() {

desktop.init();

io.init();

quads.init();

+ world.init();

board.init();

}


void release() {

+ world.release();

io.release();

desktop.release();

}


int main() {

init(); {

while (!s3eDeviceCheckQuitRequest()) {

io.update();

if (io.isKeyDown(s3eKeyAbsBSK) || io.isKeyDown(s3eKeyBack)) break;

+ world.update();

quads.update();

desktop.update();

board.update();

board.refresh();

+ world.refresh();

quads.refresh();

io.refresh();

desktop.refresh();

}

}

release();

return 0;

}


Теперь программу можно запустить на выполнение. Что мы видим? Шарик движется, но как-то очень медленно. Отскоков после соударений не наблюдается. Манипуляции с начальной скоростью не изменяют видимой скорости движения шарика. Все это говорит о том, что мы что-то делаем не так.

Подумаем, что бы это могло быть? Мы задаем все размеры в масштабе экранных координат. Для себя, я обычно считаю, единицу измерения в Box2D равной 1 метру. Даже при разрешении экрана 320x480, получается, что мы пытаемся смоделировать арканоид каких-то совершенно невообразимо эпических размеров (более того, моделируемая физика будет зависеть от размеров экрана устройства, а это уже совсем никуда не годится). Кроме того, Box2D не очень хорошо производит рассчеты с объектами таких размеров. Обычно, рекомендуемые размеры мира не должны превышать десятков метров. Внесем коррективы:

World.h

#ifndef _WORLD_H_

#define _WORLD_H_


#include <vector>

#include <Box2D.h>

#include "Desktop.h"

#include "IBox2DItem.h"


+const float W_WIDTH = 10.0f;


const int HALF_MARGIN = 10;

const int V_ITERATIONS = 10;

const int P_ITERATIONS = 10;


const float FRICTION = 0.0f;

const float RESTITUTION = 1.0f;

const float DYN_DENSITY = 0.0f;

const float R_INVIS = 0.0f;

const float EPS = 1.0f;

const float SPEED_SQ = 10.0f;


using namespace std;


class World {

private:

bool isStarted;

int HandleX, HandleH, HandleW;

uint64 timestamp;

int width, height;

b2World* wp;

b2Body* ground;

b2Body* ball;

b2Body* handle;

b2Body* createBox(int x, int y, int hw, int hh, IBox2DItem* userData = NULL);

float32 getTimeStep();

void start();

+ float toWorld(int x);

+ int fromWorld(float x);

public:

World(): width(0), height(0), wp(NULL) {}

void init();

void release();

void update();

void refresh();

b2Body* addBrick(int x, int y, int hw, int hh, IBox2DItem* userData) {return createBox(x, y, hw, hh, userData);}

b2Body* addBall(int x, int y, int r, IBox2DItem* userData);


typedef vector<b2Body*>::iterator BIter;

};


extern World world;


#endif // _WORLD_H_


World.cpp

#include "s3e.h"

#include "World.h"

#include "Ball.h"


World world;


void World::init() {

isStarted = false;

width = desktop.getWidth();

height = desktop.getHeight();

b2Vec2 gravity(0.0f, 0.0f);

wp = new b2World(gravity);

ground = createBox(width/2, -HALF_MARGIN, width/2, HALF_MARGIN);

createBox(-HALF_MARGIN, height/2, HALF_MARGIN, height/2);

createBox(width/2, height + HALF_MARGIN, width/2, HALF_MARGIN);

createBox(width + HALF_MARGIN, height/2, HALF_MARGIN, height/2);

ball = NULL;

handle = NULL;

}


void World::release() {

if (wp != NULL) {

delete wp;

wp = NULL;

ball = NULL;

handle = NULL;

}

}


+float World::toWorld(int x) {

+ return ((float)x * W_WIDTH) / (float)desktop.getWidth();

+}


+int World::fromWorld(float x) {

+ return (int)((x * (float)desktop.getWidth()) / W_WIDTH);

+}


b2Body* World::createBox(int x, int y, int hw, int hh, IBox2DItem* userData) {

b2BodyDef def;

def.type = b2_staticBody;

- def.position.Set(x, y);

+ def.position.Set(toWorld(x), toWorld(y));

b2Body* r = wp->CreateBody(&def);

b2PolygonShape box;

- box.SetAsBox(hw, hh);

+ box.SetAsBox(toWorld(hw), toWorld(hh));

b2FixtureDef fd;

fd.shape = &box;

fd.density = 0;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

r->CreateFixture(&fd);

r->SetUserData(userData);

return r;

}


b2Body* World::addBall(int x, int y, int r, IBox2DItem* userData) {

if (ball != NULL) {

wp->DestroyBody(ball);

}

b2BodyDef def;

def.type = b2_dynamicBody;

def.linearDamping = 0.0f;

def.angularDamping = 0.0f;

- def.position.Set(x, y);

+ def.position.Set(toWorld(x), toWorld(y));

ball = wp->CreateBody(&def);

b2CircleShape shape;

shape.m_p.SetZero();

- shape.m_radius = r + R_INVIS;

+ shape.m_radius = toWorld(r) + R_INVIS;

b2FixtureDef fd;

fd.shape = &shape;

fd.density = DYN_DENSITY;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

ball->CreateFixture(&fd);

ball->SetBullet(true);

ball->SetUserData(userData);

return ball;

}


float32 World::getTimeStep() {

uint64 t = s3eTimerGetMs();

int r = (int)(t - timestamp);

timestamp = t;

return (float32)r / 1000.0f;

}


void World::start() {

if (ball != NULL) {

ball->ApplyLinearImpulse(ball->GetWorldVector(b2Vec2(-10.0f, -10.0f)),

ball->GetWorldPoint(b2Vec2(0.0f, 0.0f)));

}

}


void World::update() {

if (!isStarted) {

isStarted = true;

start();

timestamp = s3eTimerGetMs();

srand((unsigned int)timestamp);

} else {

float32 timeStep = getTimeStep();

wp->Step(timeStep, V_ITERATIONS, P_ITERATIONS);

}

}


void World::refresh() {

if (ball != NULL) {

b2Vec2 pos = ball->GetPosition();

Ball* b = (Ball*)ball->GetUserData();

if (b != NULL) {

- b->setXY(pos.x, pos.y);

+ b->setXY(fromWorld(pos.x), fromWorld(pos.y));

}

}

}


Теперь, независимо от размеров экрана, «ширина» нашего мира будет составлять 10 (метров). Запускаем и убеждаемся, что шарик начал летать с нормальной скоростью и отскакивать от стен. Теперь, добьемся того, чтобы «кирпичи» исчезали после столкновения с ними шарика.

Bricks.h

#ifndef _BRICKS_H_

#define _BRICKS_H_


#include "IwGL.h"

#include "s3e.h"

#include "Desktop.h"

#include "World.h"

#include "IBox2DItem.h"


#define BRICK_COLOR_1 0xffffff00

#define BRICK_COLOR_2 0xff50ff00

#define BRICK_HALF_WIDTH 20

#define BRICK_HALF_HEIGHT 10


#include <vector>


using namespace std;


class Bricks: public IBox2DItem {

private:

struct SBrick {

SBrick(int x, int y): x(x),

y(y),

body(NULL),

isBroken(false),

hw(BRICK_HALF_WIDTH),

hh(BRICK_HALF_HEIGHT),

ic(BRICK_COLOR_1),

oc(BRICK_COLOR_2) {}

SBrick(const SBrick& p): x(p.x),

y(p.y),

body(p.body),

isBroken(p.isBroken),

hw(p.hw),

hh(p.hh),

ic(p.ic),

oc(p.oc) {}

int x, y, hw, hh, ic, oc;

int isBroken;

b2Body* body;

};

vector<SBrick> bricks;

+ virtual bool impact(b2Body* b);

public:

Bricks(): bricks() {}

void init() {}

void release() {}

void refresh();

void clear(){bricks.clear();}

void add(SBrick& b);


typedef vector<SBrick>::iterator BIter;


friend class Board;

};


#endif // _BRICKS_H_


Bricks.cpp

#include "Bricks.h"

#include "Quads.h"


void Bricks::refresh() {

for (BIter p = bricks.begin(); p != bricks.end(); ++p) {

if (p->isBroken) continue;

CIwGLPoint point(p->x, p->y);

point = IwGLTransform(point);


int16* quadPoints = quads.getQuadPoints();

uint32* quadCols = quads.getQuadCols();

if ((quadPoints == NULL) || (quadCols == NULL)) break;


*quadPoints++ = point.x - p->hw;

*quadPoints++ = point.y + p->hh;

*quadCols++ = p->ic;


*quadPoints++ = point.x + p->hw;

*quadPoints++ = point.y + p->hh;

*quadCols++ = p->oc;


*quadPoints++ = point.x + p->hw;

*quadPoints++ = point.y - p->hh;

*quadCols++ = p->ic;


*quadPoints++ = point.x - p->hw;

*quadPoints++ = point.y - p->hh;

*quadCols++ = p->oc;

}

}


+bool Bricks::impact(b2Body* b) {

+ for (BIter p = bricks.begin(); p != bricks.end(); ++p) {

+ if (p->body == b) {

+ p->isBroken = true;

+ return true;

+ }

+ }

+ return false;

+}


void Bricks::add(SBrick& b) {

b.body = world.addBrick(b.x, b.y, b.hw, b.hh, (IBox2DItem*)this);

bricks.push_back(b);

}


World.h

#ifndef _WORLD_H_

#define _WORLD_H_


#include <vector>

#include <Box2D.h>

#include "Desktop.h"

#include "IBox2DItem.h"


const float W_WIDTH = 10.0f;


const int HALF_MARGIN = 10;

const int V_ITERATIONS = 10;

const int P_ITERATIONS = 10;


const float FRICTION = 0.0f;

const float RESTITUTION = 1.0f;

const float DYN_DENSITY = 0.0f;

const float R_INVIS = 0.0f;

const float EPS = 1.0f;

const float SPEED_SQ = 10.0f;


using namespace std;


-class World {

+class World: public b2ContactListener {

private:

bool isStarted;

int HandleX, HandleH, HandleW;

uint64 timestamp;

int width, height;

b2World* wp;

b2Body* ground;

b2Body* ball;

b2Body* handle;

b2Body* createBox(int x, int y, int hw, int hh, IBox2DItem* userData = NULL);

float32 getTimeStep();

+ vector<b2Body*>* broken;

void start();

+ void impact(b2Body* b);

+ virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);

float toWorld(int x);

int fromWorld(float x);

public:

World(): broken(), width(0), height(0), wp(NULL) {}

void init();

void release();

void update();

void refresh();

b2Body* addBrick(int x, int y, int hw, int hh, IBox2DItem* userData) {

return createBox(x, y, hw, hh, userData);

}

b2Body* addBall(int x, int y, int r, IBox2DItem* userData);


+ typedef vector<b2Body*>::iterator BIter;

};


extern World world;


#endif // _WORLD_H_


World.cpp

#include "s3e.h"

#include "World.h"

#include "Ball.h"


World world;


void World::init() {

+ broken = new vector<b2Body*>();

isStarted = false;

width = desktop.getWidth();

height = desktop.getHeight();

b2Vec2 gravity(0.0f, 0.0f);

wp = new b2World(gravity);

+ wp->SetContactListener(this);

ground = createBox(width/2, -HALF_MARGIN, width/2, HALF_MARGIN);

createBox(-HALF_MARGIN, height/2, HALF_MARGIN, height/2);

createBox(width/2, height + HALF_MARGIN, width/2, HALF_MARGIN);

createBox(width + HALF_MARGIN, height/2, HALF_MARGIN, height/2);

ball = NULL;

handle = NULL;

}


void World::release() {

if (wp != NULL) {

delete wp;

wp = NULL;

ball = NULL;

handle = NULL;

}

+ delete broken;

}


float World::toWorld(int x) {

return ((float)x * W_WIDTH) / (float)desktop.getWidth();

}


int World::fromWorld(float x) {

return (int)((x * (float)desktop.getWidth()) / W_WIDTH);

}


b2Body* World::createBox(int x, int y, int hw, int hh, IBox2DItem* userData) {

b2BodyDef def;

def.type = b2_staticBody;

def.position.Set(toWorld(x), toWorld(y));

b2Body* r = wp->CreateBody(&def);

b2PolygonShape box;

box.SetAsBox(toWorld(hw), toWorld(hh));

b2FixtureDef fd;

fd.shape = &box;

fd.density = 0;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

r->CreateFixture(&fd);

r->SetUserData(userData);

return r;

}


b2Body* World::addBall(int x, int y, int r, IBox2DItem* userData) {

if (ball != NULL) {

wp->DestroyBody(ball);

}

b2BodyDef def;

def.type = b2_dynamicBody;

def.linearDamping = 0.0f;

def.angularDamping = 0.0f;

def.position.Set(toWorld(x), toWorld(y));

ball = wp->CreateBody(&def);

b2CircleShape shape;

shape.m_p.SetZero();

shape.m_radius = toWorld(r) + R_INVIS;

b2FixtureDef fd;

fd.shape = &shape;

fd.density = DYN_DENSITY;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

ball->CreateFixture(&fd);

ball->SetBullet(true);

ball->SetUserData(userData);

return ball;

}


float32 World::getTimeStep() {

uint64 t = s3eTimerGetMs();

int r = (int)(t - timestamp);

timestamp = t;

return (float32)r / 1000.0f;

}


void World::start() {

if (ball != NULL) {

ball->ApplyLinearImpulse(ball->GetWorldVector(b2Vec2(-10.0f, -10.0f)),

ball->GetWorldPoint(b2Vec2(0.0f, 0.0f)));

}

}


+void World::impact(b2Body* b) {

+ IBox2DItem* it = (IBox2DItem*)b->GetUserData();

+ if (it != NULL) {

+ if (it->impact(b)) {

+ for (BIter p = broken->begin(); p != broken->end(); ++p) {

+ if (*p == b) return;

+ }

+ broken->push_back(b);

+ }

+ }

+}


+void World::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) {

+ impact(contact->GetFixtureA()->GetBody());

+ impact(contact->GetFixtureB()->GetBody());

+}


void World::update() {

if (!isStarted) {

isStarted = true;

start();

timestamp = s3eTimerGetMs();

srand((unsigned int)timestamp);

} else {

float32 timeStep = getTimeStep();

wp->Step(timeStep, V_ITERATIONS, P_ITERATIONS);

}

}


void World::refresh() {

+ for (BIter p = broken->begin(); p != broken->end(); ++p) {

+ wp->DestroyBody(*p);

+ }

+ broken->clear();

if (ball != NULL) {

b2Vec2 pos = ball->GetPosition();

Ball* b = (Ball*)ball->GetUserData();

if (b != NULL) {

b->setXY(fromWorld(pos.x), fromWorld(pos.y));

}

}

}


Здесь, как я уже говорил выше, важно не пытаться удалить объект при рассчете очередной итерации b2World.Step (именно это и произойдет если попытаться удалить объект непосредственно в PostSolve). Также, не следует рассчитывать на то, что PostSolve будет вызыван однократно. Вполне возможна ситуация когда он сработает, например, дважды для одного «кирпича». Если мы внесем объект в broken без предварительной проверки, мы попытаемся разрушить его дважды, что неизбежно приведет к разрушению памяти. Поскольку в broken не может накопиться большого количества объектов, линейный поиск объекта в векторе нас вполне устраивает по производительности.

Осталось совсем немного. Добавим ракетку. Первоначально, я хотел сделать ракетку динамическим объектом, ограничив ее движение по вертикали при помощи PrismaticJoint. Перемещать ее по горизонтали, можно было-бы временно создавая MouseJoint. Но потом, я решил, что надо быть проще.

Дело в том, что решение сделать ракетку динамическим объектом не очень удачно. Box2D придется все время отслеживать столкновение динамических объектов, а задача эта настолько сложная, что даже Box2D не очень хорошо с ней справляется. Установка SetBullet помогает, но возможны случаи когда шарик будет пролетать сквозь ракетку, что, естественно, совершенно недопустимо, в нашем случае. Поэтому, ракетка будет статическим объектом. Мы просто будем уничтожать ее между шагами расчета и создавать в новом месте, при необходимости. Помимо прочего, этот способ гораздо проще в реализации.

Внесем в проект необходимые изменения:

arcanoid.mkb

#!/usr/bin/env mkb

options

{

module_path="../yaml"

module_path="../box2d"

}

subprojects

{

iwgl

yaml

box2d

}


includepath

{

./source/Main

./source/Model

}

files

{

[Main]

(source/Main)

Main.cpp

Main.h

Quads.cpp

Quads.h

Desktop.cpp

Desktop.h

IO.cpp

IO.h

+ TouchPad.cpp

+ TouchPad.h


[Model]

(source/Model)

Bricks.cpp

Bricks.h

Ball.cpp

Ball.h

Board.cpp

Board.h

+ Handle.cpp

+ Handle.h

}

assets

{

(data)

level.json

}


Немного измененный модуль TouchPad возьмем отсюда:

TouchPad.h

#ifndef _TOUCHPAD_H_

#define _TOUCHPAD_H_


#include "s3ePointer.h"

#include "Desktop.h"


#define MAX_TOUCHES 3


enum EMessageType {

emtNothing = 0x00,

emtTouchEvent = 0x10,

emtTouchIdMask = 0x03,

emtTouchMask = 0x78,

emtMultiTouch = 0x14,

emtTouchOut = 0x18,

emtTouchDown = 0x30,

emtTouchUp = 0x50,

emtTouchOutUp = 0x58,

emtTouchMove = 0x70,

emtSingleTouchDown = 0x30,

emtSingleTouchUp = 0x50,

emtSingleTouchMove = 0x70,

emtMultiTouchDown = 0x34,

emtMultiTouchUp = 0x54,

emtMultiTouchMove = 0x74

};


struct Touch {

int x, y;

bool isActive, isPressed, isMoved;

int id;

};


class TouchPad {

private:

bool IsAvailable;

bool IsMultiTouch;

Touch Touches[MAX_TOUCHES];

Touch* findTouch(int id);

static void HandleMultiTouchButton(s3ePointerTouchEvent* event);

static void HandleMultiTouchMotion(s3ePointerTouchMotionEvent* event);

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* getTouchPressed();

int getTouchCount() const;


bool init();

void release();

void update();

void clear();

};


extern TouchPad touchPad;


#endif // _TOUCHPAD_H_


TouchPad.cpp

#include "TouchPad.h"


TouchPad touchPad;


bool TouchPad::isTouchDown(int eventCode) {

return (eventCode & emtTouchMask) == emtTouchDown;

}

bool TouchPad::isTouchUp(int eventCode) {

return (eventCode & emtTouchMask) == emtTouchUp;

}


void TouchPad::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 TouchPad::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::getTouchPressed() {

for (int i = 0; i < MAX_TOUCHES; i++) {

if (Touches[i].isPressed && Touches[i].isActive)

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

}

}

}


IO.h

#ifndef _IO_H_

#define _IO_H_


#include "TouchPad.h"


class IO {

private:

bool KeysAvailable;

public:

void init();

void release();

void update();

void refresh();

bool isKeyDown(s3eKey key) const;

};


extern IO io;


#endif // _IO_H_


IO.cpp

#include "s3e.h"

#include "IO.h"


IO io;


void IO::init() {

touchPad.init();

}


void IO::release() {

touchPad.release();

}


void IO::update() {

touchPad.update();

s3eKeyboardUpdate();

}


void IO::refresh() {

touchPad.clear();

}


bool IO::isKeyDown(s3eKey key) const {

return (s3eKeyboardGetState(key) & S3E_KEY_STATE_DOWN) == S3E_KEY_STATE_DOWN;

}


Теперь, добавим модуль Handle:

Handle.h

#ifndef _HANDLE_H_

#define _HANDLE_H_


#include "IwGL.h"

#include "s3e.h"


#include "Desktop.h"

#include "World.h"

#include "IBox2DItem.h"


#define HANDLE_COLOR 0xffff3000

#define HANDLE_H_WIDTH 40

#define HANDLE_H_HEIGHT 10

#define HANDLE_H_POS 50


class Handle: public IBox2DItem {

private:

int x;

int y;

int touchId;

public:

void init();

void release() {}

void refresh();

void update();

virtual void setXY(int X, int Y);

};


#endif // _HANDLE_H_


Handle.cpp

#include "Handle.h"

#include "Quads.h"

#include "TouchPad.h"


void Handle::init() {

x = desktop.getWidth() / 2;

y = desktop.getHeight();

touchId = -1;

}


void Handle::setXY(int X, int Y) {

x = X;

y = Y;

}


void Handle::refresh() {

CIwGLPoint point(x, y);

point = IwGLTransform(point);


int16* quadPoints = quads.getQuadPoints();

uint32* quadCols = quads.getQuadCols();

if ((quadPoints == NULL) || (quadCols == NULL)) return;


*quadPoints++ = point.x - desktop.toRSize(HANDLE_H_WIDTH);

*quadPoints++ = point.y + desktop.toRSize(HANDLE_H_HEIGHT);

*quadCols++ = HANDLE_COLOR;


*quadPoints++ = point.x + desktop.toRSize(HANDLE_H_WIDTH);

*quadPoints++ = point.y + desktop.toRSize(HANDLE_H_HEIGHT);

*quadCols++ = HANDLE_COLOR;


*quadPoints++ = point.x + desktop.toRSize(HANDLE_H_WIDTH);

*quadPoints++ = point.y - desktop.toRSize(HANDLE_H_HEIGHT);

*quadCols++ = HANDLE_COLOR;


*quadPoints++ = point.x - desktop.toRSize(HANDLE_H_WIDTH);

*quadPoints++ = point.y - desktop.toRSize(HANDLE_H_HEIGHT);

*quadCols++ = HANDLE_COLOR;


world.addHandle(x, y, desktop.toRSize(HANDLE_H_WIDTH), desktop.toRSize(HANDLE_H_HEIGHT), (IBox2DItem*)this);

}


void Handle::update() {

Touch* t = NULL;

if (touchId > 0) {

t = touchPad.getTouchByID(touchId);

} else {

t = touchPad.getTouchPressed();

}

if (t != NULL) {

touchId = t->id;

world.moveHandle(t->x, t->y);

} else {

touchId = -1;

}

}


И внесем изменения в World и Board:

World.h

#ifndef _WORLD_H_

#define _WORLD_H_


#include <vector>

#include <Box2D.h>

#include "Desktop.h"

#include "IBox2DItem.h"


const float W_WIDTH = 10.0f;


const int HALF_MARGIN = 10;

const int V_ITERATIONS = 10;

const int P_ITERATIONS = 10;


const float FRICTION = 0.0f;

const float RESTITUTION = 1.0f;

const float DYN_DENSITY = 0.0f;

const float R_INVIS = 0.0f;

const float EPS = 1.0f;

const float SPEED_SQ = 10.0f;


using namespace std;


class World: public b2ContactListener {

private:

bool isStarted;

+ bool isHandleCreated;

int HandleX, HandleH, HandleW;

uint64 timestamp;

int width, height;

b2World* wp;

b2Body* ground;

b2Body* ball;

b2Body* handle;

b2Body* createBox(int x, int y, int hw, int hh, IBox2DItem* userData = NULL);

float32 getTimeStep();

vector<b2Body*>* broken;

void start();

void impact(b2Body* b);

virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);

float toWorld(int x);

int fromWorld(float x);

public:

World(): broken(), width(0), height(0), wp(NULL) {}

void init();

void release();

void update();

void refresh();

b2Body* addBrick(int x, int y, int hw, int hh, IBox2DItem* userData) {return createBox(x, y, hw, hh, userData);}

b2Body* addBall(int x, int y, int r, IBox2DItem* userData);

+ b2Body* addHandle(int x, int y, int hw, int hh, IBox2DItem* userData);

+ void moveHandle(int x, int y);


typedef vector<b2Body*>::iterator BIter;

};


extern World world;


#endif // _WORLD_H_


World.cpp

#include "s3e.h"

#include "World.h"

#include "Ball.h"


World world;


void World::init() {

broken = new vector<b2Body*>();

isStarted = false;

width = desktop.getWidth();

height = desktop.getHeight();

b2Vec2 gravity(0.0f, 0.0f);

wp = new b2World(gravity);

wp->SetContactListener(this);

ground = createBox(width/2, -HALF_MARGIN, width/2, HALF_MARGIN);

createBox(-HALF_MARGIN, height/2, HALF_MARGIN, height/2);

createBox(width/2, height + HALF_MARGIN, width/2, HALF_MARGIN);

createBox(width + HALF_MARGIN, height/2, HALF_MARGIN, height/2);

ball = NULL;

handle = NULL;

}


void World::release() {

if (wp != NULL) {

delete wp;

wp = NULL;

ball = NULL;

handle = NULL;

}

delete broken;

}


float World::toWorld(int x) {

return ((float)x * W_WIDTH) / (float)desktop.getWidth();

}


int World::fromWorld(float x) {

return (int)((x * (float)desktop.getWidth()) / W_WIDTH);

}


b2Body* World::createBox(int x, int y, int hw, int hh, IBox2DItem* userData) {

b2BodyDef def;

def.type = b2_staticBody;

def.position.Set(toWorld(x), toWorld(y));

b2Body* r = wp->CreateBody(&def);

b2PolygonShape box;

box.SetAsBox(toWorld(hw), toWorld(hh));

b2FixtureDef fd;

fd.shape = &box;

fd.density = 0;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

r->CreateFixture(&fd);

r->SetUserData(userData);

return r;

}


b2Body* World::addBall(int x, int y, int r, IBox2DItem* userData) {

if (ball != NULL) {

wp->DestroyBody(ball);

}

b2BodyDef def;

def.type = b2_dynamicBody;

def.linearDamping = 0.0f;

def.angularDamping = 0.0f;

def.position.Set(toWorld(x), toWorld(y));

ball = wp->CreateBody(&def);

b2CircleShape shape;

shape.m_p.SetZero();

shape.m_radius = toWorld(r) + R_INVIS;

b2FixtureDef fd;

fd.shape = &shape;

fd.density = DYN_DENSITY;

fd.friction = FRICTION;

fd.restitution = RESTITUTION;

ball->CreateFixture(&fd);

ball->SetBullet(true);

ball->SetUserData(userData);

return ball;

}


+b2Body* World::addHandle(int x, int y, int hw, int hh, IBox2DItem* userData) {

+ HandleW = hw; HandleH = hh;

+ if (handle != NULL) {

+ wp->DestroyBody(handle);

+ }

+ b2BodyDef def;

+ def.type = b2_staticBody;

+ def.position.Set(toWorld(x), toWorld(y));

+ handle = wp->CreateBody(&def);

+ b2PolygonShape box;

+ box.SetAsBox(toWorld(hw), toWorld(hh));

+ b2FixtureDef fd;

+ fd.shape = &box;

+ fd.density = DYN_DENSITY;

+ fd.friction = FRICTION;

+ fd.restitution = RESTITUTION;

+ handle->CreateFixture(&fd);

+ handle->SetUserData(userData);

+ return handle;

+}


+void World::moveHandle(int x, int y) {

+ isHandleCreated = true;

+ HandleX = x;

+}


float32 World::getTimeStep() {

uint64 t = s3eTimerGetMs();

int r = (int)(t - timestamp);

timestamp = t;

return (float32)r / 1000.0f;

}


void World::start() {

if (ball != NULL) {

ball->ApplyLinearImpulse(ball->GetWorldVector(b2Vec2(-10.0f, -10.0f)),

ball->GetWorldPoint(b2Vec2(0.0f, 0.0f)));

}

}


void World::impact(b2Body* b) {

IBox2DItem* it = (IBox2DItem*)b->GetUserData();

if (it != NULL) {

if (it->impact(b)) {

for (BIter p = broken->begin(); p != broken->end(); ++p) {

if (*p == b) return;

}

broken->push_back(b);

}

}

}


void World::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) {

impact(contact->GetFixtureA()->GetBody());

impact(contact->GetFixtureB()->GetBody());

}


void World::update() {

if (!isStarted) {

isStarted = true;

start();

timestamp = s3eTimerGetMs();

srand((unsigned int)timestamp);

} else {

float32 timeStep = getTimeStep();

wp->Step(timeStep, V_ITERATIONS, P_ITERATIONS);

}

}


void World::refresh() {

for (BIter p = broken->begin(); p != broken->end(); ++p) {

wp->DestroyBody(*p);

}

broken->clear();

+ if (isHandleCreated) {

+ if (handle != NULL) {

+ int y = fromWorld(handle->GetPosition().y);

+ IBox2DItem* data = (IBox2DItem*)handle->GetUserData();

+ if (HandleX < HandleW) {

+ HandleX = HandleW;

+ }

+ if (HandleX > desktop.getWidth() - HandleW) {

+ HandleX = desktop.getWidth() - HandleW;

+ }

+ handle = addHandle(HandleX, y, HandleW, HandleH, data);

+ b2Vec2 pos = handle->GetPosition();

+ data->setXY(fromWorld(pos.x), fromWorld(pos.y));

+ }

+ }

if (ball != NULL) {

b2Vec2 pos = ball->GetPosition();

Ball* b = (Ball*)ball->GetUserData();

if (b != NULL) {

b->setXY(fromWorld(pos.x), fromWorld(pos.y));

}

}

}


Board.h

#ifndef _BOARD_H_

#define _BOARD_H_


#include <yaml.h>

#include <vector>

#include <String>


#include "Bricks.h"

#include "Ball.h"

+#include "Handle.h"


#define MAX_NAME_SZ 100


using namespace std;


enum EBrickMask {

ebmX = 0x01,

ebmY = 0x02,

ebmComplete = 0x03,

ebmWidth = 0x04,

ebmHeight = 0x08,

ebmIColor = 0x10,

ebmOColor = 0x20

};


class Board {

private:

struct Type {

Type(const char* s, const char* n, const char* v): s(s), n(n), v(v) {}

Type(const Type& p): s(p.s), n(p.n), v(p.v) {}

string s, n, v;

};

Bricks bricks;

Ball ball;

+ Handle handle;

yaml_parser_t parser;

yaml_event_t event;

vector<string> scopes;

vector<Type> types;

char currName[MAX_NAME_SZ];

int brickMask;

int brickX, brickY, brickW, brickH, brickIC, brickOC;

bool isTypeScope;

void load();

void clear();

void notify();

const char* getScopeName();

void setProperty(const char* scope, const char* name, const char* value);

void closeTag(const char* scope);

int fromNum(const char* s);

public:

Board(): scopes(), types() {}

void init();

void release();

void refresh();

void update();


typedef vector<string>::iterator SIter;

typedef vector<Type>::iterator TIter;

};


#endif // _BOARD_H_


Board.cpp

#include "Board.h"

#include "Desktop.h"


const char* BOARD_SCOPE = "board";

const char* LEVEL_SCOPE = "level";

const char* TYPE_SCOPE = "types";


const char* TYPE_PROPERTY = "type";

const char* WIDTH_PROPERTY = "width";

const char* HEIGHT_PROPERTY = "height";

const char* IC_PROPERTY = "inner_color";

const char* OC_PROPERTY = "outer_color";

const char* X_PROPERTY = "x";

const char* Y_PROPERTY = "y";


void Board::init() {

ball.init();

bricks.init();

+ handle.init();

load();

}


void Board::release() {

+ handle.release();

bricks.release();

ball.release();

}


...

void Board::refresh() {

bricks.refresh();

ball.refresh();

+ handle.refresh();

}


+void Board::update() {

+ handle.update();

+}


На этом все. Теперь, у нас есть работающий прототип игры Arcanoid, который можно собрать как для Android, так и под iPhone.


 
MyTetra Share v.0.52
Яндекс индекс цитирования