Привет! Сегодня мы покажем, как легко интегрировать физический движок Box2D в любое игровое приложение, написанное на стандартных фреймворках Apple. Примером может служить интерактивная книга, выпущенная нашей студией полгода назад. Эта книга была нашим первым приложением для детей, и когда мы начинали над ней работать, у нас было мало опыта в создании анимации, поэтому мы выбрали мощные и хорошо документированные стандартные фреймворки Apple, с которыми мы были знакомы — на тот момент это было проще.
Книга была готова в течение двух месяцев.
Однако некоторые идеи не были реализованы.
Список этих пожеланий оставлен на будущее, чтобы, когда у меня будет время и знания, я смогу вернуться к проекту.
Физика
Одним из пунктов была симуляция физического мира, чтобы у пользователя была возможность играть с объектами: создавать их, бросать, перебрасывать из угла в угол с помощью акселерометра и так далее.Для реализации этой возможности необходимо было интегрировать в проект физический движок.
И вот, когда на новом проекте осваивались Cocos2D и Box2D, возник резонный вопрос: если Box2D по своей сути независим от графической реализации программы, то почему бы не использовать его в самой первой книге? Недолгий поиск в Интернете привел к замечательному и лаконичному статья в блоге http://www.cocoanetics.com , который просто и понятно объясняет, как использовать Box2D в стандартном приложении UIKit. И мы приступили к делу.
Больше всего нас беспокоило то, что работа движка, написанного на C++, потребует масштабных изменений в текущем коде проекта.
Но, к счастью, было всего пара небольших изменений — тип файлов классов страниц изменен на Objective-C++ и проведена легкая оптимизация кода.
Эти изменения заняли около 4 часов.
Принцип действия
Принцип интеграции движка прост (см.упомянутую статью).
Когда страница загружается, создается физический мир.
Далее ему присваиваются нужные границы (в данном случае весь экран) и создаются нужные тела.
Поскольку созданные тела не видны пользователю, необходимые объекты UIKit, например, картинки класса UIImageView, присваиваются им с помощью свойства body userData. Далее при вызове метода viewDidAppear с необходимой периодичностью запускается таймер, который, обрабатывая положения всех тел в физическом мире, перемещает соответствующие картинки, связанные с телами, в нужные позиции.
Это создает иллюзию того, что сами картинки напрямую сталкиваются.
При вызове метода viewDidDissappear таймер останавливается, все созданные пользователем тела и соответствующие им изображения удаляются из мира и из self.view. Несмотря на опасения, что эта схема не обеспечит приемлемую частоту кадров, рендеринг был быстрым даже на старых устройствах, таких как iPhone 3G, визуально на одном уровне с рендерингом в приложении на основе Cocos2D.
Тела
Метод, описанный в оригинальной статье, позволял создавать только прямоугольные тела по размерам вида, переданным в сообщении создания тела.Для нас это был недостаточно гибкий метод, поскольку наши тела могли иметь любую форму.
При работе над проектом на базе Cocos2D мы использовали удобную программу для Mac OS — СпрайтХелпер от индивидуального застройщика Богдана Владу.
Платная лицензия позволяет делать текстурные карты из готовых изображений и задавать параметры физических тел: форму, плотность, коэффициент трения, упругость и т. д. — все, что вам нужно.
Для работы с полученными файлами автор написал класс SpriteHelperLoader, который позволяет в одном сообщении создать нужное тело в нужном физическом мире и слое Cocos2D. Одна проблема — этот класс был строго ориентирован на совместную работу с Cocos2D. Пришлось потратить некоторое время и «вырезать» из него все упоминания о «кокосе».
По сути, нам это нужно было только для получения одного параметра тела — формы (текстурные карты в этом случае не используются).
Теперь у нас в руках удобный, а главное, привычный способ добавить физику в наши первые книги, дать им второе дыхание и, надеемся, добавить больше положительных отзывов.
Оригинальный метод добавления тел из оригинальной статьи был переписан:
- (void) addPhysicalBodyForView:(UIImageView *)physicalImageView ofType:(NSString *)type { // get image's center coordinates CGPoint position = physicalImageView.center; // Define the dynamic body. b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; bodyDef.position.Set(position.x/PTM_RATIO, (screenSize.height - position.y)/PTM_RATIO); // convert into Box2D coordinates bodyDef.userData = physicalImageView; // Tell the physics world to create the body b2Body *body = world->CreateBody(&bodyDef); position = CGPointMake(bodyDef.position.x, bodyDef.position.y); // use modified SpriteHelperLoader to get shape tempBody = [bodyLoader bodyWithUniqueName:type atPosition:position world:world]; b2Fixture* fixture = tempBody->GetFixtureList(); b2Shape *shape = fixture->GetShape(); // Define the dynamic body fixture. b2FixtureDef fixtureDef; fixtureDef.shape = shape; fixtureDef.density = fixture->GetDensity(); fixtureDef.friction = fixture->GetFriction(); fixtureDef.restitution = fixture->GetRestitution(); body->CreateFixture(&fixtureDef); // a dynamic body reacts to forces right away body->SetType(b2_dynamicBody); world->DestroyBody(tempBody); fixture = nil; shape = nil; }
Сенсорное управление
Для обработки касаний мы использовали встроенный механизм Бокс2D и стандартные методы UIKit сенсорное ведение журнала.Когда касание регистрируется на главном представлении, координаты точки касания преобразуются из системы координат UIKit в систему координат Box2D. Далее идет проверка, не задевает ли он какое-либо тело в физическом мире.
А если происходит попадание, то между этим телом и groundBody создается физическая связь, позволяющая перетаскивать тело пальцем по экрану.
Когда прикосновение завершено, тело переходит в свободный полет (путем разрушения связи) согласно импульсу, полученному при движении.
Это выглядит вполне естественно.
Стоит отметить, что для того, чтобы ваши перетаскиваемые тела не вылетали за пределы экрана, необходимо включить расчет столкновений для создаваемой связи между этими телами.
Для этого установите для свойства соединенияcollideConnected значение YES. class QueryCallback : public b2QueryCallback
{
public:
QueryCallback(const b2Vec2& point)
{
m_point = point;
m_fixture = NULL;
}
bool ReportFixture(b2Fixture* fixture)
{
b2Body* body = fixture->GetBody();
if (body->GetType() == b2_dynamicBody)
{
bool inside = fixture->TestPoint(m_point);
if (inside)
{
m_fixture = fixture;
// We are done, terminate the query.
return false;
}
}
// Continue the query.
return true;
}
b2Vec2 m_point;
b2Fixture* m_fixture;
};
[.
]
#pragma mark - Drag and Drop
// source - http://iphonedev.net/2009/08/05/how-to-grab-a-sprite-with-cocos2d-and-box2d/
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView: [myTouch view]];
location = CGPointMake(location.x, screenSize.height-location.y);
m_mouseWorld.Set(location.x/PTM_RATIO, location.y/PTM_RATIO);
if (m_mouseJoint != NULL)
{
NSLog(@"m_mouseJoint != NULL");
return;
}
b2AABB aabb;
b2Vec2 d;
d.Set(0.001f, 0.001f);
aabb.lowerBound = m_mouseWorld - d;
aabb.upperBound = m_mouseWorld + d;
// Query the world for overlapping shapes.
QueryCallback callback(m_mouseWorld);
world->QueryAABB(&callback, aabb);
b2Body* nbody = NULL;
if (callback.m_fixture)
{
nbody = callback.m_fixture->GetBody();
}
if (nbody)
{
b2MouseJointDef md;
md.bodyA = groundBody; //
md.bodyB = nbody;
md.target = m_mouseWorld;
md.collideConnected = YES;
#ifdef TARGET_FLOAT32_IS_FIXED
md.maxForce = (nbody->GetMass() < 16.0)? (1000.0f * nbody->GetMass()) : float32(16000.0);
#else
md.maxForce = 1000.0f * nbody->GetMass();
#endif
m_mouseJoint = (b2MouseJoint*)world->CreateJoint(&md);
nbody->SetAwake(YES);
}
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView: [myTouch view]];
// translate uikit coordinates into box2d coordinates
location = CGPointMake(location.x, screenSize.height-location.y);
m_mouseWorld.Set(location.x/PTM_RATIO, location.y/PTM_RATIO);
if (m_mouseJoint)
{
m_mouseJoint->SetTarget(m_mouseWorld);
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"touches ended");
if (m_mouseJoint)
{
AudioServicesPlaySystemSound (soundThrust);
world->DestroyJoint(m_mouseJoint);
m_mouseJoint = NULL;
}
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event ];
}
Акселерометр
Пытаясь создать реалистичную коробку с предметами, нужно помнить о гравитации, и здесь есть несколько подводных камней.
Можно, конечно, пойти по простому пути и получить компоненты вектора силы тяжести по показаниям акселерометра, принимая значения по двум осям — X и Y, как это было сделано в оригинальной статье:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
b2Vec2 gravity;
gravity.Set( acceleration.x * 9.81, acceleration.y * 9.81 );
world->SetGravity(gravity);
}
Но этот метод позволяет случайно создать нежелательную невесомость, если устройство расположено на горизонтальной поверхности, поскольку проекции земной силы тяжести на оси X и Y устройства станут практически равными нулю.
В случае такой виртуальной невесомости предметы будут неестественно плавать в воздухе.
Будет естественнее, если вектор гравитации всегда будет равен 1 и предметы будут находиться под действием силы постоянной величины, независимо от расположения устройства: на коленях или столе, вертикально в руках или даже над головой экраном вниз.
Мы реализовали этот чуть более сложный алгоритм расчета направления силы тяжести, но сохраним его в секрете, как наше маленькое ноу-хау.
Также стоит отметить, что для более точной установки вектора силы тяжести и более тонкого управления им на новых устройствах можно использовать акселерометр, а от просто использовать протокол UIAccelerometerDelegate (который уже не стоит использовать в iOS 5) стоит перейти на комплексный CMMotionManager из платформы CoreMotion.
Видео демонстрация результата
Вместо заключения
За интеграцию физического движка на 4 страницы нашего книги потребовалось всего несколько дней, включая продумывание небольшого игрового сюжета на каждой странице, создание соответствующей графики и кодирования — общая стоимость невелика, а новых возможностей много.Дерзайте и вы! Спасибо за внимание.
Теги: #iOS #iphone #ipad #uikit #box2d #физика #движок #разработка #разработка iOS
-
Домашние Услуги Ноутбука
19 Oct, 24 -
Пиджак И Рубашка – Лучший Наряд
19 Oct, 24 -
Агентства Веб-Разработки: Информация
19 Oct, 24 -
Игра Мотивации. Часть 1
19 Oct, 24 -
Письмо Стартапам
19 Oct, 24 -
Как Попасть В Топ Apple App Store
19 Oct, 24