Этот урок поможет вам разобраться в основах 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.
Удачи!






