Cocos2d notes(7) physics engines
星期五, 六月 17th, 2011Chipmunk SpaceManager
Chipmunk SpaceManager Documentation
Chipmunk Physics
Chipmunk Documentation
Box2D Documents
Box2D API Reference
Box2D
header定义,几乎没变化,只是增加了Box2D的头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #import "cocos2d.h" #import "Box2D.h" #import "GLES-Render.h" enum { kTagBatchNode, }; @interface HelloWorld : CCLayer { b2World* world; } // returns a Scene that contains the HelloWorld as the only child +(id) scene; @end |
初始化Box2D世界
b2Vec2 gravity = b2Vec2(0.0f, -10.0f);
bool allowBodiesToSleep = true;
world = new b2World(gravity, allowBodiesToSleep);
销毁时
1 2 3 4 5 | -(void) dealloc { delete world; [super dealloc]; } |
关于什么是Sleeping body
It’s a trick that allows the physics simulation to quickly skip over
objects that do not need processing. A dynamic body goes to sleep when the forces
applied to it have been below a threshold for a certain amount of time. In other words, if
268 CHAPTER 12: Physics Engines
the dynamic body is moving and rotating very slowly or not at all, the physics engine will
flag it as sleeping and won’t apply forces to it anymore—that is, unless an impulse or force
applied to the body is strong enough to make the body move or rotate again. This trick
allows the physics engine to save time by not processing the bodies that are at rest.
Unless all of your game’s dynamic bodies are in constant motion, you should enable this
feature by setting the allowBodiesToSleep variable to true,
限制对象运动的屏幕
(Box2D的单位全是公制单位,即长度用米,重量用千克,时间用秒)
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 | // Define the static container body, which will provide the collisions at screen borders. b2BodyDef containerBodyDef; b2Body* containerBody = world->CreateBody(&containerBodyDef); // for the ground body we'll need these values // PTM pixel转换成公制长度的产量 CGSize screenSize = [CCDirector sharedDirector].winSize; float widthInMeters = screenSize.width / PTM_RATIO; float heightInMeters = screenSize.height / PTM_RATIO; b2Vec2 lowerLeftCorner = b2Vec2(0, 0); b2Vec2 lowerRightCorner = b2Vec2(widthInMeters, 0); b2Vec2 upperLeftCorner = b2Vec2(0, heightInMeters); b2Vec2 upperRightCorner = b2Vec2(widthInMeters, heightInMeters); // Create the screen box' sides by using a polygon assigning each side individually. b2PolygonShape screenBoxShape; int density = 0; // bottom screenBoxShape.SetAsEdge(lowerLeftCorner, lowerRightCorner); containerBody->CreateFixture(&screenBoxShape, density); // top screenBoxShape.SetAsEdge(upperLeftCorner, upperRightCorner); containerBody->CreateFixture(&screenBoxShape, density); // left side screenBoxShape.SetAsEdge(upperLeftCorner, lowerLeftCorner); containerBody->CreateFixture(&screenBoxShape, density); // right side screenBoxShape.SetAsEdge(upperRightCorner, lowerRightCorner); containerBody->CreateFixture(&screenBoxShape, density); |
#define PTM_RATIO 32
代表32像素代表Box2D中的1米
转换
1 2 3 4 5 6 7 8 | -(b2Vec2) toMeters:(CGPoint)point { return b2Vec2(point.x / PTM_RATIO, point.y / PTM_RATIO); } -(CGPoint) toPixels:(b2Vec2)vec { return ccpMult(CGPointMake(vec.x, vec.y), PTM_RATIO); } |
创建Box2D Sprite
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 | // Use the orthogonal tileset for the little boxes CCSpriteBatchNode* batch = [CCSpriteBatchNode batchNodeWithFile:@"dg_grounds32.png" capacity:TILESET_ROWS * TILESET_COLUMNS]; [self addChild:batch z:0 tag:kTagBatchNode]; // Add a few objects initially for (int i = 0; i < 11; i++) { [self addNewSpriteAt:CGPointMake(screenSize.width / 2, screenSize.height / 2)]; } [self scheduleUpdate]; self.isTouchEnabled = YES; -(void) addNewSpriteAt:(CGPoint)pos { CCSpriteBatchNode* batch = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode]; int idx = CCRANDOM_0_1() * TILESET_COLUMNS; int idy = CCRANDOM_0_1() * TILESET_ROWS; CGRect tileRect = CGRectMake(TILESIZE * idx, TILESIZE * idy, TILESIZE, TILESIZE); CCSprite* sprite = [CCSprite spriteWithBatchNode:batch rect:tileRect]; sprite.position = pos; [batch addChild:sprite]; // Create a body definition and set it to be a dynamic body b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; // position must be converted to meters bodyDef.position = [self toMeters:pos]; // assign the sprite as userdata so it's easy to get to the sprite when working with the body bodyDef.userData = sprite; b2Body* body = world->CreateBody(&bodyDef); // Define another box shape for our dynamic body. b2PolygonShape dynamicBox; float tileInMeters = TILESIZE / PTM_RATIO; dynamicBox.SetAsBox(tileInMeters * 0.5f, tileInMeters * 0.5f); // Define the dynamic body fixture. b2FixtureDef fixtureDef; fixtureDef.shape = &dynamicBox; fixtureDef.density = 0.3f; fixtureDef.friction = 0.5f; fixtureDef.restitution = 0.6f; body->CreateFixture(&fixtureDef); } |
让sprite动起来
事实上,通过以上代码还不能让每个sprite运动起来,还是需要通过sheduleUpdate触发每次计算world里存在的各个body,计算一定timeStep时间内他们的运动情况,然后给sprite设置属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | -(void) update:(ccTime)delta { // The number of iterations influence the accuracy of the physics simulation. With higher values the // body's velocity and position are more accurately tracked but at the cost of speed. // Usually for games only 1 position iteration is necessary to achieve good results. float timeStep = 0.03f; int32 velocityIterations = 8; int32 positionIterations = 1; world->Step(timeStep, velocityIterations, positionIterations); // for each body, get its assigned sprite and update the sprite's position for (b2Body* body = world->GetBodyList(); body != nil; body = body->GetNext()) { CCSprite* sprite = (CCSprite*)body->GetUserData(); if (sprite != NULL) { // update the sprite's position to where their physics bodies are sprite.position = [self toPixels:body->GetPosition()]; float angle = body->GetAngle(); sprite.rotation = CC_RADIANS_TO_DEGREES(angle) * -1; } } } |
碰撞检测
* 如果把.m文件后缀改成.mm则可以在其中同时使用objc和c++语法,box2d是c++写的,所以。。
b2ContactListener类支持两个事件,注意一下都是c++代码了
ContactListener.h
1 2 3 4 5 6 7 8 | #import "Box2D.h" class ContactListener : public b2ContactListener { private: void BeginContact(b2Contact* contact); void EndContact(b2Contact* contact); }; |
.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 | #import "ContactListener.h" #import "cocos2d.h" void ContactListener::BeginContact(b2Contact* contact) { b2Body* bodyA = contact->GetFixtureA()->GetBody(); b2Body* bodyB = contact->GetFixtureB()->GetBody(); CCSprite* spriteA = (CCSprite*)bodyA->GetUserData(); CCSprite* spriteB = (CCSprite*)bodyB->GetUserData(); if (spriteA != NULL && spriteB != NULL) { spriteA.color = ccMAGENTA; spriteB.color = ccMAGENTA; } } void ContactListener::EndContact(b2Contact* contact) { b2Body* bodyA = contact->GetFixtureA()->GetBody(); b2Body* bodyB = contact->GetFixtureB()->GetBody(); CCSprite* spriteA = (CCSprite*)bodyA->GetUserData(); CCSprite* spriteB = (CCSprite*)bodyB->GetUserData(); if (spriteA != NULL && spriteB != NULL) { spriteA.color = ccWHITE; spriteB.color = ccWHITE; } } |
在layer中加入listener
1 2 | contactListener = new ContactListener(); world->SetContactListener(contactListener); |
Joint
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 | // Create a body definition and set it to be a dynamic body b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; // position must be converted to meters bodyDef.position = [self toMeters:pos]; bodyDef.position = bodyDef.position + b2Vec2(-1, -1); bodyDef.userData = [self addRandomSpriteAt:pos]; b2Body* bodyA = world->CreateBody(&bodyDef); [self bodyCreateFixture:bodyA]; bodyDef.position = [self toMeters:pos]; bodyDef.userData = [self addRandomSpriteAt:pos]; b2Body* bodyB = world->CreateBody(&bodyDef); [self bodyCreateFixture:bodyB]; bodyDef.position = [self toMeters:pos]; bodyDef.position = bodyDef.position + b2Vec2(1, 1); bodyDef.userData = [self addRandomSpriteAt:pos]; b2Body* bodyC = world->CreateBody(&bodyDef); [self bodyCreateFixture:bodyC]; b2RevoluteJointDef jointDef; jointDef.Initialize(bodyA, bodyB, bodyB->GetWorldCenter()); bodyA->GetWorld()->CreateJoint(&jointDef); jointDef.Initialize(bodyB, bodyC, bodyC->GetWorldCenter()); bodyA->GetWorld()->CreateJoint(&jointDef); // create an invisible static body to attach to bodyDef.type = b2_staticBody; bodyDef.position = [self toMeters:pos]; b2Body* staticBody = world->CreateBody(&bodyDef); jointDef.Initialize(staticBody, bodyA, bodyA->GetWorldCenter()); bodyA->GetWorld()->CreateJoint(&jointDef); |
Chipmunk
.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #import "cocos2d.h" #import "chipmunk.h" enum { kTagBatchNode = 1, }; @interface HelloWorld : CCLayer { cpSpace* space; } +(id) scene; @end |
space等同于box2d中的world
初始化
1 2 3 4 5 | cpInitChipmunk(); space = cpSpaceNew(); space->iterations = 8; space->gravity = CGPointMake(0, -100); |
销毁
1 2 3 4 5 | -(void) dealloc { cpSpaceFree(space); [super dealloc]; } |
设置ground body(容器)
1 2 3 4 5 6 7 8 9 10 11 | // for the ground body we'll need these values CGSize screenSize = [CCDirector sharedDirector].winSize; CGPoint lowerLeftCorner = CGPointMake(0, 0); CGPoint lowerRightCorner = CGPointMake(screenSize.width, 0); CGPoint upperLeftCorner = CGPointMake(0, screenSize.height); CGPoint upperRightCorner = CGPointMake(screenSize.width, screenSize.height); // Create the static body that keeps objects within the screen area float mass = INFINITY; float inertia = INFINITY; cpBody* staticBody = cpBodyNew(mass, inertia); |
创建容器边缘
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 | cpShape* shape; float elasticity = 1.0f; float friction = 1.0f; float radius = 0.0f; // bottom shape = cpSegmentShapeNew(staticBody, lowerLeftCorner, lowerRightCorner, radius); shape->e = elasticity; shape->u = friction; cpSpaceAddStaticShape(space, shape); // top shape = cpSegmentShapeNew(staticBody, upperLeftCorner, upperRightCorner, radius); shape->e = elasticity; shape->u = friction; cpSpaceAddStaticShape(space, shape); // left shape = cpSegmentShapeNew(staticBody, lowerLeftCorner, upperLeftCorner, radius); shape->e = elasticity; shape->u = friction; cpSpaceAddStaticShape(space, shape); // right shape = cpSegmentShapeNew(staticBody, lowerRightCorner, upperRightCorner, radius); shape->e = elasticity; shape->u = friction; cpSpaceAddStaticShape(space, shape); |
添加Sprite
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 | -(CCSprite*) addRandomSpriteAt:(CGPoint)pos { CCSpriteBatchNode* batch = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode]; int idx = CCRANDOM_0_1() * TILESET_COLUMNS; int idy = CCRANDOM_0_1() * TILESET_ROWS; CGRect tileRect = CGRectMake(TILESIZE * idx, TILESIZE * idy, TILESIZE, TILESIZE); CCSprite* sprite = [CCSprite spriteWithBatchNode:batch rect:tileRect]; sprite.position = pos; [batch addChild:sprite]; return sprite; } -(void) addNewSpriteAt:(CGPoint)pos { float mass = 0.5f; float moment = cpMomentForBox(mass, TILESIZE, TILESIZE); cpBody* body = cpBodyNew(mass, moment); body->p = pos; cpSpaceAddBody(space, body); float halfTileSize = TILESIZE * 0.5f; int numVertices = 4; CGPoint vertices[] = { CGPointMake(-halfTileSize, -halfTileSize), CGPointMake(-halfTileSize, halfTileSize), CGPointMake(halfTileSize, halfTileSize), CGPointMake(halfTileSize, -halfTileSize), }; CGPoint offset = CGPointZero; float elasticity = 0.3f; float friction = 0.7f; cpShape* shape = cpPolyShapeNew(body, numVertices, vertices, offset); shape->e = elasticity; shape->u = friction; shape->data = [self addRandomSpriteAt:pos]; cpSpaceAddShape(space, shape); } |
update
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // C method that updates sprite position and rotation: static void forEachShape(void* shapePointer, void* data) { cpShape* shape = (cpShape*)shapePointer; CCSprite* sprite = (CCSprite*)shape->data; if (sprite != nil) { cpBody* body = shape->body; sprite.position = body->p; sprite.rotation = CC_RADIANS_TO_DEGREES(body->a) * -1; } } -(void) update:(ccTime)delta { float timeStep = 0.03f; cpSpaceStep(space, timeStep); // call forEachShape C method to update sprite positions cpSpaceHashEach(space->activeShapes, &forEachShape, nil); cpSpaceHashEach(space->staticShapes, &forEachShape, nil); } |
检测碰撞
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 | // C callback methods for collision handling static int contactBegin(cpArbiter* arbiter, struct cpSpace* space, void* data) { bool processCollision = YES; cpShape* shapeA; cpShape* shapeB; cpArbiterGetShapes(arbiter, &shapeA, &shapeB); CCSprite* spriteA = (CCSprite*)shapeA->data; CCSprite* spriteB = (CCSprite*)shapeB->data; if (spriteA != nil && spriteB != nil) { spriteA.color = ccMAGENTA; spriteB.color = ccMAGENTA; } return processCollision; } static void contactEnd(cpArbiter* arbiter, cpSpace* space, void* data) { cpShape* shapeA; cpShape* shapeB; cpArbiterGetShapes(arbiter, &shapeA, &shapeB); CCSprite* spriteA = (CCSprite*)shapeA->data; CCSprite* spriteB = (CCSprite*)shapeB->data; if (spriteA != nil && spriteB != nil) { spriteA.color = ccWHITE; spriteB.color = ccWHITE; } } |
添加listener
1 2 3 4 | // Add the collision handlers unsigned int defaultCollisionType = 0; cpSpaceAddCollisionHandler(space, defaultCollisionType, defaultCollisionType, &contactBegin, NULL, NULL, &contactEnd, NULL); |
