[Нам пишут] Использование физического движка Chipmunk в iOS разработке

13

uinta-chipmunk-10

Нам пишут разработчики из Westwood

Разработка игр — занятие непростое. Помимо графики, идеи, сюжета, звука и многих других факторов, влияющих на результат, игре нужна реалистичная физическая модель. Хорошо, что в наше время есть немало готовых физических движков для iOS, в том числе и Chipmunk, о котором и пойдет речь в этой статье, из нашей традиционной рубрики по письмам читателей.

Нашу сегодняшнюю статью мы хотим посвятить обзору физического движка Chipmunk, который мы активно использовали при создании нашего приложения Pullastic Calculator.

Chipmunk вместе со своим главным конкурентом Box2D являются наиболее распространенными на мобильных устройствах физическими движками реального времени, предназначенными для симуляции физики твердых тел. В данной статье мы не ставим перед собой целью их сравнения, но перечислим некоторые особенности, которые выделяют каждую из этих технологий. Стоит отметить, что все написанное актуально для операционной системы iOS.

Box2D

  • Бесплатный.
  • Открытый исходный код, распространяется под лицензией zlib.
  • Написан на мультиплатформенном языке C++.
  • Имеет более обширное комьюнити и на данный момент большую распространенность.

Chipmunk

  • Бесплатный (язык: Plain C) / платный (язык: Objective C, ценник может варьироваться в зависимости от выбранного варианта).
  • Открытый исходный код, распространяется под лицензией MIT.

В свое время, когда мы встали перед выбором, какую из технологий использовать, ключевым критерием выступила простота. Разрабатываемое приложение не представляло из себя ничего сложного, поэтому крайне не хотелось усложнять его кодовую базу. Была выбрана версия движка Chipmunk Pro, позволяющая написать весь код на нативном Objective C. Помимо этой особенности, версия Pro также (согласно замерам компании-разработчика) обладает гораздо более высокой производительностью. Впрочем, справедливости ради, заметить подобные различия можно при гораздо более сложных симуляциях с использованием значительно большего числа объектов. Также написание кода на нативном ObjC вместе с грамотным API позволило отобразить все особенности, связанные с физикой без использования сторонних графических библиотек (например, Cocos2D) на голом UIKit, что тоже стало приятным бонусом.

Вообще, суть физического движка сводится к созданию некоего виртуального пространства (Space), обладающего определенными физ.параметрами (гравитация, вязкость и т.д.) и наполнением его виртуальными объектами-телами (Bodies), также имеющими определенные свойства. Расчёт взаимодействия всех объектов данной системы и есть суть физического движка. Если же по какой-то причине требуется наложить на определенные ограничения на одно или ряд тел, используются так называемые связи (Joints). Далее полученные расчёты можно использовать для визуализации, поскольку это не входит в задачи движка.

Теперь мы разберем главные понятия, использующиеся в большинстве подобных движков на примере Chipmunk Pro (полную версию API можно посмотреть по этому адресу).

Пространство (Space) — основной объект, задачей которого является моделирование всех физических процессов.
В Chipmunk пространство представлено классом ChipmunkSpace (в версии Pro рекомендуется использовать класс ChipmunkHastySpace, который обладает рядом оптимизаций). Среди обязательных атрибутов можно выделить следующие:

  • gravity — гравитация. Является векторной величиной, где направление гравитации совпадает с направлением вектора, а сила равна его длине
  • damping — затухание. Числовое выражение. Отвечает за гашение кинетической энергии тел, находящихся внутри системы

Основной принцип работы с пространством состоит в добавлении/удалении объектов к нему. В Chipmunk Pro для этого рекомендуется использовать методы smartAdd и smartRemove.

ChipmunkHastySpace *space = [[ChipmunkHastySpace alloc] init];
space.gravity = cpv(0, 100) //гравитация направлена строго вниз
space.damping = 0.5 //задаем степень затухания (по умолчанию 1 - затухания нет)
[space smartAdd:chipmunkObject] //добавляем объект в пространство

При создании объекта в системе Chipmunk, используется симбиоз «тела» одного или нескольких объектов «формы».
Тело (Body) содержит в себе информацию о массе объекта, его скорости, положении и т.д. В Chipmunk Pro эта роль отведена для класса ChipmunkBody, который имеет следующие параметры:

  • mass — масса тела. Числовая величина.
  • moment — момент инерции. Числовая величина. Если масса показывает, насколько тяжело сдвинуть объект, то момент инерции показывает, какая сила потребуется для кручения тела. Для вычисления значения в Chipmunk существует ряд методов, зависящих от формы тела cpMomentFor*()
  • pos — положение центра тяжести твердого тела. Векторная величина.
  • vel — текущая скорость объекта. Векторная величина.
  • force — сила, которая приложена в данный момент к телу. Векторная величина.
  • angle — угол поворота. Числовое значение в радианах.
  • angleVel — угловая скорость. Числовое значение в радианах в секунду.
  • torque — крутящий момент
  • velLimit и angVelLimit — максимально возможные значения линейной и угловых скоростей данного тела. По умолчанию равны бесконечности (INFINITY).
cpFloat width = 25.0;
cpFloat height = 5.0;
cpFloat mass = 10.0;
//рассчитаем момент инерции для тела с заданными размерами и массой, 
//имеющего форму прямоугольника
cpFloat moment = cpMomentForBox(mass, width, height);
//создаем тело с заданной массой и моментом инерции 
ChipmunkBody body = [[ChipmunkBody alloc] initWithMass:mass andMoment:moment];
body.pos = cpv(0, 100); //задаем начальную позицию
[space smartAdd:body];  //добавляем тело в пространство

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

Обратите внимание, на данный момент у тела не задана форма. В Chipmunk за форму отвечает абстрактный класс ChipmunkShape, который имеет три потомка для разных типов формы:

  • ChipmunkCircleShape — круг.
  • ChipmunkPolyShape — выпуклый многоугольник.
  • ChipmunkSegmentShape — прямоугольник со скругленными краями.

Общими для всех типов форм являются следующие параметры:

  • elasticity — эластичность. Числовое значение. Отвечает за поведение объекта при соударении с другими объектами.
  • friction — трение. Числовое значение.
  • collisionType — тип соударения. Используется при обработке взаимодействий между объектами.
    Также у каждого класса есть набор уникальных параметров, характерных только для данного типа форм. По этой причине отличаются и методы инициализации для каждого из классов.
ChipmunkPolyShape *shape = [[ChipmunkPolyShape alloc] initBoxWithBody:body width:width height:height];
shape.elasticity = 1.0;
shape.collisionType = [ChipmunkObject class];
[space smartAdd:shape];

Стоит отметить, что одному телу можно назначать сразу несколько форм. Таким образом можно формировать достаточно сложные фигуры, которые все еще корректно обрабатываются физической системой. Для доступа к объектам формы у класса ChipmunkBody есть для этого атрибут shapes, возвращающий массив всех объектов формы, прикрепленных к данному телу.

Для задания определенных ограничений во взаимодействиях между двумя конкретными объектами используются так называемые связи (Joints), которые объединяют два тела определенным образом. Наиболее наглядно будет сравнить связи с гвоздями/веревками/пружинами, при помощи которых можно соединить две детали (объекта).

В Chipmunk роль такого объединения отводится потомкам абстрактного класса ChipmunkConstraint. Разных типов связей, а следовательно и потомков указанного класса в Chipmunk насчитывается с десяток, поэтому мы не будем останавливаться на подробном рассмотрении каждого из них, но рекомендуем ознакомиться с ними самостоятельно на портале разработчиков. Наиболее наглядно все типы связей изображены в данном видеоролике.

Общими свойствами для всех типов связей являются тела A и B (bodyA/bodyB), возвращающие соответствующие тела на каждом из концов связи. Инициализация связи строится по тому же принципу: указываются оба тела, которые требуется объединить, а также конкретные точки на каждом из этих тел. Приведем пример на связи под названием «булавка» (pin).

//создаем связь между объектами bodyA и bodyB, 
//скрепленными в центрах
ChipmunkPinJoint *pin = [ChipmunkPinJoint pinJointWithBodyA:bodyA bodyB:bodyB anchr1:cpvzero anchr2:cpvzero];
[space smartAdd:pin];

Также в Chipmunk имеется обработчик столкновений между объектами. Реализован он при помощи колбеков и универсального метода, позволяющего обработать каждую стадию столкновения отельно:

[space addCollisionHandler:self
    typeA:[ChipmunkObject class]		
    typeB:@"borderType"
    begin:@selector(beginCollision:space:)
    preSolve:nil
    postSolve:@selector(postSolveCollision:space:)
    separate:@selector(separateCollision:space:)
];

typeA и typeB это те самые collisionType, которые мы задавали ранее. Они используются обработчиком для фильтрации строго определенных типов взаимодействия. Допустим, borderType у нас используется на границах пространства. В этом случае обработчик будет вызываться каждый раз, когда тело вступает в соприкосновение с границей пространства, но не будет работать в случае соударения тел друг с другом. Далее указываются 4 стадии столкновения, отвечающие за моменты начала столкновения, непосредственного столкновения, момента, следующего прямиком за столкновением и моментом разъединения двух тел. На каждый момент можно задать отдельный обработчик. Естественно, каждый шаг можно и проигнорировать. Кстати, по заявлениям разработчика, именно в обработке столкновений Chipmunk оставляет Box2D далеко позади, однако мы не ставили перед собой задачи проверить правдивость данного утверждения.

Как уже было показано выше, тело и форма представляются разными сущностями. Chipmunk позволяет создать комплексный объект, который будет содержать сразу тело, форму и некую информацию для графического отображения тела. В случае использования стандартного UIKit можно связать в один объект ChipmunkBody, ChipmunkShape и UIView. Для этого класс должен поддерживать протокол . Приведем пример подобного класса:

#import <foundation /Foundation.h>
#import "ObjectiveChipmunk.h"

@interface MyChipmunkObject : NSObject <chipmunkobject>

@property (readonly) NSArray *chipmunkObjects;
@property (readonly) UIView *view;

- (void)updatePosition;
- (id)initWithView:(UIView *)view;

@end

#import "MyChipmunkObject.h"

@implementation MyChipmunkObject

- (id)initWithView:(UIView *)view {

    if (self = [self init]) {
    
        _view = view;
        
        CGFloat width   = _view.frame.size.width;
        CGFloat height  = _view.frame.size.height;
        CGPoint position = _view.center;
        
        _view.center = CGPointZero;
        
        cpFloat mass = 100;
        ChipmunkBody *body = [[ChipmunkBody alloc] initWithMass:mass
                                                      andMoment:cpMomentForBox(mass, width, height)];
        body.pos = position;
        body.data = self;
        
        ChipmunkPolyShape *shape = [[ChipmunkPolyShape alloc] initBoxWithBody:body width:width height:height];
        shape.elasticity = 1.0;
        shape.collisionType = [MyChipmunkObject class];
        shape.data = self;
        
        _chipmunkObjects = [NSArray arrayWithObjects:body, shape, nil];
        
        [self updatePosition];
    }
    return self;
}


- (void)updatePosition {
    
    self.view.transform = self.body.affineTransform;
//    NSLog(@"body position %@", NSStringFromCGPoint(self.body.pos));
}

@end

Данный класс позволяет добавить UIView через Storyboard, назначить ему все необходимые параметры, а затем при помощи метода initWithView: создать для него тело и форму соответствующих размеров. В результате мы получим комплексный объект, содержащий сразу UIView, а также тело и форму, которые могут быть обработаны физическим движком. Благодаря тому, что объект поддерживает протокол, добавить его в пространство становится очень просто. Метод [space smartAdd:chipmunkObject], где chipmunkObject является экземпляром нашего класса MyChipmunkObject, будет прекрасно работать. Для синхронизации графического отображения и расчётов физического движка в классе создан метод updatePosition, который обновляет параметры UIView в соответствии с параметрами физического тела. Его нужно вызывать при каждом обновлении экрана для достижения наиболее плавного эффекта.

В данной статье мы описали основные особенности работы с физическим движком Chipmunk Pro. Напоследок мы создали небольшой демо-проект, наглядно показывающий многие возможности данной библиотеки.

Статья, которую мы сегодня публикуем, отчасти выделяется на общем фоне наших статей, но от себя хочется сказать, что я рад тому, что такие статьи появляются у нас. Огромное спасибо представителям компании Westwood за этот рассказ, основанный на личном опыте.

Pages_ Если вам есть, чем поделиться с другими читателями нашего сайта, пишите на advert@appleinsider.ru и не забудьте указать свое имя или ник. Мы внимательно читаем входящие письма и публикуем ваши самые интересные истории.

13 комментариев

  1. 0

    прикольно

  2. 0

    Нечего добавить -_- (отправлено из приложения AppleInsider.ru)

  3. 0

    Memorize
    (отправлено из приложения AppleInsider.ru)

  4. 0
    Boris Dzhuguryan

    Крутяк:) Полезная информация для программистов и не только (отправлено из приложения AppleInsider.ru)

  5. 0

    Спасибо за материал! Прошел на Гит к вашему демо-проекту, как его скачать, чтоб посмотреть в Xcode?

  6. 0
    Andrey Titarenko

    А что за бред? Ночью сервера вообще не работают (отправлено из приложения AppleInsider.ru)

  7. 0

    Спасибо

  8. 0

    Главное что-бы все летало! Удачи (отправлено из приложения AppleInsider.ru)

  9. 0

    Когда-то экспериментировал с физическим движком Bullet. Он бесплатен, его можно скомпилировать под iOS, вполне работает. При желании, нужную вам часть можно обернуть в objC, это нетрудно и недолго. Плюсы — полноценное 3D, обработка «мягких тел», оптимизирован (в том числе под OpenCL, правда я не проверял это на iOS), наиболее продвинутый из свободных. ЕМНИП, использовался для расчета физики при рендере «Шрека», а также некоторых других известных фильмов. Вообще, можно скачать их архив демок (есть project для XCode), собрать и лично увидеть все возможности.

    • 0

      Hemml, «При желании, нужную вам часть можно обернуть в objC, это нетрудно и недолго.»
      Chipmunk на Plain C тоже бесплатен, и при желании можно обернуть нужные функции в ObjC самостоятельно. Платная версия делает это за вас, экономя кучу времени. Вот и вся разница. + интеграция с Cocos2d. Но никакого 3д, это да

      • 0

        laGrave, Заинтересовался движками, не подскажете, в cocos3d можно как-то модели из Cinema 4d импортировать?

      • 0

        laGrave, Весь вопрос в стоимости времени. Для бесплатного приложения/просто для попробовать лучше обернуть самому. Кроме того, не обязательно покрывать 100% функционала, все равно в реальном приложении используется небольшая его часть. Дополнительный профит — в процессе оборачивания можно изучить библиотеку вдоль и поперек, что пригодится в дальнейшем и даже, парадоксально, может в итоге сократить общее время разработки.

Авторизуйтесь Чтобы оставить комментарий