📜 ⬆️ ⬇️

Integration of the physics engine Box2D into the UIKit application for iOS

Hello!


Today we will show how easy it is to integrate the Box2D physics engine into any gaming application written in standard Apple frameworks. An example would be an interactive book released by our studio six months ago. This book was our first application for children, and when we started working on it, we had little experience in creating animations, so we chose the standard Apple frameworks that we knew were powerful and well-documented - it was easier at that time. The book was ready in two months. However, some ideas were not implemented. Of these wishes, a list was left for the future, so that when there is time and knowledge, return to the project.

Physics


One of the points was the simulation of the physical world, so that the user had the opportunity to play with objects: create them, throw them, throw them from corner to corner with accelerometer means, and so on. To realize this possibility, integration into the project of a physics engine was required. And now, when Cocos2D and Box2D were mastered on the new project, a reasonable question arose: if Box2D is inherently independent of the graphical implementation of the program, then why not use it in the very first book? Short searches in the open spaces of the Web led to a wonderful and concise blog article http://www.cocoanetics.com , which explains how to use Box2D in a standard application on UIKit. And we set to work. We were most concerned about the fact that the work of the engine written in C ++ will require large-scale changes in the current project code. But, fortunately, it only cost a couple of small changes - the type of the page class files was changed to Objective-C ++, and the code was easily optimized. These changes took about 4 hours.

Principle of operation


The principle of integration of the engine is simple (see the specified article). When you load the page creates a physical world. Next, he is assigned the required boundaries (in this case, the entire screen), and the required bodies are created. Since the created bodies are not visible to the user, they are assigned the necessary UIKit objects, for example, pictures of the UIImageView class, using the body property userData. Further, when calling the viewDidAppear method, a timer is started with the desired frequency, which, processing the positions of all bodies in the physical world, moves the corresponding images associated with the bodies to the desired positions. This creates the illusion that the images themselves directly collide. When the call to viewDidDissapear method is called, the timer stops, all the bodies created by the user and the corresponding images are removed from the world and from self.view. Contrary to the fears that this scheme will not give an acceptable frame rate, the rendering was fast even on older devices like the iPhone 3G, not worse than rendering in an application based on Cocos2D.
')

Bodies


The method described in the original article allowed to create only rectangular bodies according to the size of the view transmitted in the message about the creation of the body. For us, this was not a flexible way, since we could have any form of body. In our work on the Cocos2D-based project, we used a handy program for Mac OS - SpriteHelper from the individual developer Bogdan Vladu. A paid license allows you to make texture maps from pre-fabricated images and set parameters for physical bodies: shape, density, friction coefficient, elasticity, etc. - all you need. To work with the received files, the author wrote the SpriteHelperLoader class, which allows you to create the desired body in the desired physical world and the Cocos2D layer in one message. One problem - this class was rigidly focused on collaboration with Cocos2D. I had to spend some time and “cut out” all the references to “coconut” from it. In fact, we only needed it to get one body parameter — a form (texture maps in this case are not used). Now we have a comfortable, and most importantly familiar way in our hands to add physics to our first books, give them a second wind and, we hope, add some positive feedback. The original method of adding bodies from the original article was rewritten:
- (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; } 


Touch processing


For touch processing, we used Box2D's built-in mechanism and standard UIKit touch registration methods . When a touch is registered on the main view, the coordinates of the touch point are transformed from the UIKit coordinate system to the Box2D coordinate system. Next, there is a check for hit in any body in the physical world. And if there is a hit, then a physical connection with the body-ground (groundBody) is created between this body, which allows you to drag the body with your finger across the screen. When the touch is completed, the body is sent to free flight (by destroying the connection) according to the impulse acquired during the movement. It looks quite natural. It is worth noting that in order for your dragged bodies to not fly out of the screen, it is necessary to allow the calculation of collisions for the created connection between the bodies. For this, the collideConnected property must be set to 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]; } 


Accelerometer


When you try to create a realistic box with objects, you must not forget about gravity, and there are several pitfalls. You can, of course, follow a simple path and get the components of the gravity vector from the accelerometer readings, taking the values ​​along two axes - X and Y, as was done in the original article:
 - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { b2Vec2 gravity; gravity.Set( acceleration.x * 9.81, acceleration.y * 9.81 ); world->SetGravity(gravity); } 

But this method allows you to accidentally create unwanted weightlessness in the case of the device on a horizontal surface, since the projections of the earth's gravity on the X and Y axes of the device will become almost zero. In the case of such virtual weightlessness, objects will float unnaturally in the air. It will be more natural if the gravity vector is always equal to 1 and the objects will be under the influence of a constant force regardless of the device's position: on the knees or on the table, vertically in the hands or even above the head with the screen facing down. We have implemented this somewhat more complicated algorithm for calculating the direction of gravity, but we will keep it secret as our small know-how. It is also worth mentioning that for more accurate setting of the gravity vector and more subtle control of it on new devices, you can use the accelerometer, and from simple use of the UIAccelerometerDelegate protocol (which is no longer used in iOS 5), you should switch to the integrated CMMotionManager from CoreMotion framework.

Video demonstration of the result




Instead of conclusion


It took only a few days to integrate the physics engine on 4 pages of our book , including inventing a small game plot on each page, creating appropriate graphics and coding — the total costs are small, and there are many new possibilities. Dare and you!
Thanks for attention.

Source: https://habr.com/ru/post/137414/


All Articles