Маленький отважный арканоид (часть 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.