Home » Уроки » Учимся использовать Cocos2D с Box2D в iOS, прыгающий trolface

Учимся использовать Cocos2D с Box2D в iOS, прыгающий trolface

Этот урок поможет вам разобраться в основах Box2D, научит создавать простое приложение для iOS в котором отображается прыгающий trolface отскакивающий от краев экрана вашего iPhone или другого i-устройства под управлением iOS.

Создание пустого проекта

Создайте новый проект в xCode выбрав шаблон под названием “cocos2d_box2d”, назовите его, например, firstBox2D. Конечно же сам Cocos2D должен быть уже установлен, скачать можно с официального сайта cocos2d-iphone.org.

Запустив проект, вы увидите черный экран, прикоснувшись к которому создастся объект, который тут-же устремится в низ под воздействием “гравитации”. Этот пример как раз и демонстрирует нам работу интересующего нас физического движка Box2D. Однако в нашему уроке мы будем создавать все с нуля, для того что бы вы получили представление о том как это работает.

Давайте очистим наш проект так, что-бы он соответствовал нашим требования.

Замените содержимое HelloWorldLayer.h на следующее:

1
2
3
4
5
6
7
8
9
#import "cocos2d.h"

@interface HelloWorld : CCLayer {

}

+ (id) scene;

@end

Содержимое HelloWorldLayer.mm на следующее:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import "HelloWorldLayer.h"

@implementation HelloWorld

+ (id)scene {

CCScene *scene = [CCScene node];

HelloWorld *layer = [HelloWorld node];

[scene addChild:layer];

return scene;

}

- (id)init {

if ((self=[super init])) {

}

return self;

}

@end

И последний шаг – убедитесь, что все ваши файлы классов (например HelloWorldLayer) заканчивались на .mm вместо .m .Если они заканчиваются на .m, просто переименуйте их в .mm. Это делается потому что Box2D использует с++, а это дает знать компилятору что мы в данном файле будем использовать с++.

Запустите наш проект, вы уведете пустой черный экран. Если вы так и вышло, значит вы все сделали правильно.

Box2D теория

Прежде чем идти дальше, давайте поговорим немного о том, как работает Box2D.

Первое, что нужно делать при использовании cocos2d это создание объекта мир для Box2D. Объект мир является основным объектом в cocos2d, который управляет всеми объектами и моделирование физики.

После того как мы создали объект мир, мы должны добавить в него некоторые тела.

Ними могут быть динамические объекты, монстры или самолет, могут быть и статические объекты, которые не двигаются, стены, платформы и т.д.

Существует определенный набор параметров которые нужно задать для создания объекта:

- Вначале устанавливаются параметры положения и скорости для тела.

- Затем вы описываете какой геометрической фигурой будет представляться наше тело.

- Так же необходимо задать параметры плотности и трения.

Box2D практика

Прежде чем начать, загрузите изображение trolface, и перетащите его в папку ресурсов в нашем проекте. Затем необходимо добавить в начало файла HelloWorldLayer.mm строку:

#define PTM_RATIO 32.0

Этот параметр определяет количество пикселей на метр. Box2D принимает все параметры в метрах, поэтому все координаты и размеры необходимо преобразовывать в метры. Делается это путем деления на нашу константу, например, у нас есть квадратный спрайт 64 пикселя, делим его сторону на PTM_RATIO и получаем 2 метра. Это сделано для точного моделирования физических процессов.

Добавьте следующие строки в верхней части HelloWorldLayer.h:

#import «Box2D.h»

Добавьте глобальные переменные к классу HelloWorldLayer:

b2World *_world;

b2Body *_body;

CCSprite *_ball;

Затем добавьте следующие строки в ваш init метода HelloWorldLayer.mm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
CGSize winSize = [CCDirector sharedDirector].winSize;

// Создаем спрайт и добавляем его к слою

_ball = [CCSprite spriteWithFile:@"trolFace.png" rect:CGRectMake(0, 0, 52, 52)];

_ball.position = ccp(100, 100);

[self addChild:_ball];

// Создаем мир

b2Vec2 gravity = b2Vec2(0.0f, -30.0f);

bool doSleep = true;

_world = new b2World(gravity, doSleep);

// Создаем грани вокруг екрана

b2BodyDef groundBodyDef;

groundBodyDef.position.Set(0,0);

b2Body *groundBody = _world->CreateBody(&groundBodyDef);

b2PolygonShape groundBox;

b2FixtureDef boxShapeDef;

boxShapeDef.shape = &groundBox;

groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO, 0));

groundBody->CreateFixture(&boxShapeDef);

groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(0, winSize.height/PTM_RATIO));

groundBody->CreateFixture(&boxShapeDef);

groundBox.SetAsEdge(b2Vec2(0, winSize.height/PTM_RATIO),

b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO));

groundBody->CreateFixture(&boxShapeDef);

groundBox.SetAsEdge(b2Vec2(winSize.width/PTM_RATIO,

winSize.height/PTM_RATIO), b2Vec2(winSize.width/PTM_RATIO, 0));

groundBody->CreateFixture(&boxShapeDef);

//Создаем тело мяча, задаем параметры и форму

b2BodyDef ballBodyDef;

ballBodyDef.type = b2_dynamicBody;

ballBodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);

ballBodyDef.userData = _ball;

_body = _world->CreateBody(&ballBodyDef);

b2CircleShape circle;

circle.m_radius = 26.0/PTM_RATIO;

b2FixtureDef ballShapeDef;

ballShapeDef.shape = &circle;

ballShapeDef.density = 1.0f;

ballShapeDef.friction = 0.2f;

ballShapeDef.restitution = 0.8f;

_body->CreateFixture(&ballShapeDef);

[self schedule:@selector(tick:)];

Это был большой блок кода, поэтому разобьем его на части и рассмотрим каждую по отдельности.

1
2
3
4
5
6
7
8
9
CGSize winSize = [CCDirector sharedDirector].winSize;

// Создаем спрайт и добавляем его к слою

_ball = [CCSprite spriteWithFile:@"trolFace.png" rect:CGRectMake(0, 0, 52, 52)];

_ball.position = ccp(100, 100);

[self addChild:_ball];

Во-первых, создаем спрайт из файла изображения, задаем его размер. Устанавливаем его в точку с координатами 100, 100.

1
2
3
4
5
6
7
// Создать мир

b2Vec2 gravity = b2Vec2(0.0f, -30.0f);

bool doSleep = true;

_world = new b2World(gravity, doSleep);

Сначала мы должны создать объект который будет задавать вектор и силу гравитации в наше мире. Мы устанавливаем его -30 по оси Y, это значит что все не закрепленные объекты будут падать в низ экрана. Параметр doSleep устанавливаем true. Это значит что данный объект будет находится в режиме сна, до того момента, пока не вступит во взаимодействие с каким либо объектом, например столкнется. Далее мы непосредственно создаем наш мир с выше описанными параметрами.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
b2BodyDef groundBodyDef;

groundBodyDef.position.Set(0,0);

b2Body *groundBody = _world->CreateBody(&groundBodyDef);

b2PolygonShape groundBox;

b2FixtureDef boxShapeDef;

boxShapeDef.shape = &groundBox;

groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO, 0));

groundBody->CreateFixture(&boxShapeDef);

groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(0, winSize.height/PTM_RATIO));

groundBody->CreateFixture(&boxShapeDef);

groundBox.SetAsEdge(b2Vec2(0, winSize.height/PTM_RATIO),

b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO));

groundBody->CreateFixture(&boxShapeDef);

groundBox.SetAsEdge(b2Vec2(winSize.width/PTM_RATIO,

winSize.height/PTM_RATIO), b2Vec2(winSize.width/PTM_RATIO, 0));

groundBody->CreateFixture(&boxShapeDef);

Далее мы создаем невидимые края по контуру экрана iPhone, которые будет служить преградой для нашего trolFace.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Создаем тело мяча, задаем параметры и форму

b2BodyDef ballBodyDef;

ballBodyDef.type = b2_dynamicBody;

ballBodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);

ballBodyDef.userData = _ball;

_body = _world->CreateBody(&ballBodyDef);

b2CircleShape circle;

circle.m_radius = 26.0/PTM_RATIO;

b2FixtureDef ballShapeDef;

ballShapeDef.shape = &circle;

ballShapeDef.density = 1.0f;

ballShapeDef.friction = 0.2f;

ballShapeDef.restitution = 0.8f;

_body->CreateFixture(&ballShapeDef);

Теперь создаем объект шар. Выполняем шаги аналогичные созданию мира. Но обратите внимание на следующее различия:

- тип нашего тела устанавливаем как динамическое b2_dynamicBody, что означает что наш объект будет двигаться и взаимодействовать с остальным миром.

- используем форму круга.

- и наконец степени трения и упругости нашего тела.

Теперь добавим в проект tick метод:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)tick:(ccTime) dt {

_world->Step(dt, 10, 10);

for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {

if (b->GetUserData() != NULL) {

CCSprite *ballData = (CCSprite *)b->GetUserData();

ballData.position = ccp(b->GetPosition().x * PTM_RATIO,

b->GetPosition().y * PTM_RATIO);

ballData.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());

}

}

}

Первое что мы делаем, вызываем метод “Step”, для того чтобы происходило моделирование физических процессов.

Далее мы в цикле перебираем все объекты нашего мира и изменяем их позицию и угол наклона нашего trolface.

Последнее что необходимо сделать, это очистка. Добавляем этот код в конец файла:

1
2
3
4
5
6
7
8
9
10
11
- (void)dealloc {

delete _world;

_body = NULL;

_world = NULL;

[super dealloc];

}

Пробуем запустить наш проект, вы должны увидеть отскакивающий от краев экрана trolface.

При создании обекста тролфейс, мы устанавливали значения следующим параметрам

density, friction, restitution. Давайте о них поговорим:

density масса на единицу обема. Чем больше плотность, тем больше масса и тем треднее будет сдвинуть обект.

friction сила трения обекта с другими обектами. Должна быть в диапазоне от 0 до 1. 0 – означает отсутствие трения, а 1 – означает максимально возможное трение.

restitution упругость обекта. Должна быть в диапазоне от 0 до 1. 0 – означает что обект не будет прыгать вообще, а 1 – означает что обект будет отскакивать с тойже силой что и ударился о другой обект.

Не бойтесь експерементировать с этими параметрами!

Небольшое дополнение

Было бы неплохо учитывать расположение самого устройства для изменения вектора гарвитации. Это делается очень просто, включам акселеромет:

1
self.isAccelerometerEnabled = YES;

И добавляем следующий метод:

1
2
3
4
5
6
7
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {

b2Vec2 gravity(-acceleration.y * 15, acceleration.x *15);

_world->SetGravity(gravity);

}

Если у Вас есть вопросы, не стесняйтесь, оставлять ваши комментарии здесь или пишите мне в Twitter.

Исходный код данного урока можно скачать по ссылке firstBox2D_devios.ru.

Удачи!

>
  • Yarmolchuk

    продолжение будет??

  • Dimkahr

    Будет, немного позже.

  • zloy

    Можно ли без IOS девайса проверить работу акселерометра на этом примере. Просто при повороте симулятора трольфэйс продолжает прыгать на одном месте?

  • Аноним

    К сожалению нет, с акселерометром в симуляторе работать нельзя.

  • Felix V

    нет. потому что работу акселерометра который по идее находится на девайсе, можно проверить только на девайсе

  • Foobar

    Спизжено и коряво переделано. Оригинал: http://www.raywenderlich.com/457/intro-to-box2d-with-cocos2d-tutorial-bouncing-balls

  • a elanser

    ну и пусть спизжено, зато человек перевел урок на русский, на мой взгляд не плохо. Автору респект!

  • Александр

    ^увидете^увидите

  • дабулаби

    по сути в макбуках тоже есть своебразный акселерометр может каким то хаком можно его заставить работать

©