Маленький отважный арканоид (часть 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 косметические исправления из разряда «сделаем компилятор счастливым»:

Collision\b2BroadPhase.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) 	{            ... 	} 

Common\b2Math.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; }; 

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

image

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

image

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

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 метру. Даже при разрешении экрана 320×480, получается, что мы пытаемся смоделировать арканоид каких-то совершенно невообразимо эпических размеров (более того, моделируемая физика будет зависеть от размеров экрана устройства, а это уже совсем никуда не годится). Кроме того, 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 будет вызыван однократно. Вполне возможна ситуация когда он сработает, например, дважды для одного «кирпича». Если мы внесем объект в briken без предварительной проверки, мы попытаемся разрушить его дважды, что неизбежно приведет к разрушению памяти. Поскольку в 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.

ссылка на оригинал статьи http://habrahabr.ru/post/166929/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *