- 怎么用旋转关节(rotation joints)
- 怎么用连接关节(weld joints)
- 怎么让视角跟随抛射物
- 怎么根据碰撞检测判断力量来消除敌人
- 和很多其他的
- CCSprite *sprite = CCSprite::spriteWithFile("bg.png"); //背景图
- sprite->setAnchorPoint(CCPointZero);
- this->addChild(sprite, -1);
- CCSprite *sprite = CCSprite::spriteWithFile("catapult_base_2.png"); //投射器底部后面那块
- sprite->setAnchorPoint(CCPointZero);
- sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT));
- this->addChild(sprite, 0);
- sprite = CCSprite::spriteWithFile("squirrel_1.png"); //左边松鼠
- sprite->setAnchorPoint(CCPointZero);
- sprite->setPosition(CCPointMake(11.0, FLOOR_HEIGHT));
- this->addChild(sprite, 0);
- sprite = CCSprite::spriteWithFile("catapult_base_1.png"); //投射器底部前面那块
- sprite->setAnchorPoint(CCPointZero);
- sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT));
- this->addChild(sprite, 9);
- sprite = CCSprite::spriteWithFile("squirrel_2.png"); //右边松鼠
- sprite->setAnchorPoint(CCPointZero);
- sprite->setPosition(CCPointMake(240.0, FLOOR_HEIGHT));
- this->addChild(sprite, 9);
- sprite = CCSprite::spriteWithFile("fg.png"); //带冰的地面
- sprite->setAnchorPoint(CCPointZero);
- this->addChild(sprite, 10);
- #define FLOOR_HEIGHT 62.0f
- private:
- b2World* m_world;
- b2Body* m_groundBody;
- b2Vec2 gravity;
- gravity.Set(0.0f, -10.0f);
- bool doSleep = true;
- m_world = new b2World(gravity);
- m_world->SetAllowSleeping(doSleep);
- m_world->SetContinuousPhysics(true);
- // Define the ground body.
- b2BodyDef groundBodyDef;
- groundBodyDef.position.Set(0, 0); // bottom-left corner
- // Call the body factory which allocates memory for the ground body
- // from a pool and creates the ground box shape (also from a pool).
- // The body is also added to the world.
- m_groundBody = m_world->CreateBody(&groundBodyDef);
- b2EdgeShape groundBox;
- // bottom
- groundBox.Set(b2Vec2(0,FLOOR_HEIGHT/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,FLOOR_HEIGHT/PTM_RATIO));
- m_groundBody->CreateFixture(&groundBox, 0);
- // top
- groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,screenSize.height/PTM_RATIO));
- m_groundBody->CreateFixture(&groundBox, 0);
- // left
- groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(0,0));
- m_groundBody->CreateFixture(&groundBox, 0);
- // right
- groundBox.Set(b2Vec2(screenSize.width*1.5f/PTM_RATIO,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*1.5f/PTM_RATIO,0));
- m_groundBody->CreateFixture(&groundBox, 0);
- private:
- b2Fixture *m_armFixture;
- b2Body *m_armBody;
- // Create the catapult's arm
- CCSprite *arm = CCSprite::spriteWithFile("catapult_arm.png");
- this->addChild(arm, 1);
- b2BodyDef armBodyDef;
- armBodyDef.type = b2_dynamicBody;
- armBodyDef.linearDamping = 1;
- armBodyDef.angularDamping = 1;
- armBodyDef.position.Set(230.0f/PTM_RATIO, (FLOOR_HEIGHT+91.0f)/PTM_RATIO);
- armBodyDef.userData = arm;
- m_armBody = m_world->CreateBody(&armBodyDef);
- b2PolygonShape armBox;
- b2FixtureDef armBoxDef;
- armBoxDef.shape = &armBox;
- armBoxDef.density = 0.3F;
- armBox.SetAsBox(11.0f/PTM_RATIO, 91.0f/PTM_RATIO);
- m_armFixture = m_armBody->CreateFixture(&armBoxDef);
- void tick(cocos2d::ccTime dt);
- void HelloWorld::tick(ccTime dt)
- {
- int velocityIterations = 8;
- int positionIterations = 1;
- m_world->Step(dt, velocityIterations, positionIterations);
- //Iterate over the bodies in the physics world
- for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext())
- {
- if (b->GetUserData() != NULL) {
- //Synchronize the AtlasSprites position and rotation with the corresponding body
- CCSprite* myActor = (CCSprite*)b->GetUserData();
- myActor->setPosition( CCPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO) );
- myActor->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()) );
- }
- }
- }
- schedule(schedule_selector(Catapult::tick));
- b2RevoluteJoint *m_armJoint;
- b2RevoluteJointDef armJointDef;
- armJointDef.Initialize(m_groundBody, m_armBody, b2Vec2(233.0f/PTM_RATIO, FLOOR_HEIGHT/PTM_RATIO));
- armJointDef.enableMotor = true;
- armJointDef.enableLimit = true;
- armJointDef.motorSpeed = -10; //-1260;
- armJointDef.lowerAngle = CC_DEGREES_TO_RADIANS(9);
- armJointDef.upperAngle = CC_DEGREES_TO_RADIANS(75);
- armJointDef.maxMotorTorque = 700;
- m_armJoint = (b2RevoluteJoint*)m_world->CreateJoint(&armJointDef);
- private:
- b2MouseJoint *m_mouseJoint;
- public:
- virtual void ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
- virtual void ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
- virtual void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
- void Catapult::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
- {
- if (m_mouseJoint != NULL) { return; }
- CCTouch *touch = (CCTouch *)touches->anyObject();
- CCPoint location = touch->locationInView(touch->view());
- location = CCDirector::sharedDirector()->convertToGL(location);
- b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
- if (locationWorld.x < m_armBody->GetWorldCenter().x + 150.0/PTM_RATIO)
- {
- b2MouseJointDef md;
- md.bodyA = m_groundBody;
- md.bodyB = m_armBody;
- md.target = locationWorld;
- md.maxForce = 2000;
- m_mouseJoint = (b2MouseJoint *)m_world->CreateJoint(&md);
- }
- }
- m_mouseJoint = NULL;
- void Catapult::ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
- {
- if (m_mouseJoint == NULL) { return; }
- CCTouch *touch = (CCTouch *)touches->anyObject();
- CCPoint location = touch->locationInView(touch->view());
- location = CCDirector::sharedDirector()->convertToGL(location);
- b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
- m_mouseJoint->SetTarget(locationWorld);
- }
- void Catapult::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
- {
- if (m_mouseJoint != NULL)
- {
- m_world->DestroyJoint(m_mouseJoint);
- m_mouseJoint = NULL;
- return;
- }
- }
预备!瞄准!开火!
是时候了,填充橡子。
我们将要在游戏开头创建每一个子弹物体,然后在一个个实用。因此我们需要1个空间来存储他们。在头文件类声明中加入:
- std::vector<b2Body *> m_bullets;
- int m_currentBullet;
增加一个方法用来生产子弹:(头文件中加声明)
- void Catapult::createBullets(int count)
- {
- m_currentBullet = 0;
- float pos = 62.0f;
- if (count > 0)
- {
- // delta is the spacing between corns
- // 62 is the position o the screen where we want the corns to start appearing
- // 165 is the position on the screen where we want the corns to stop appearing
- // 30 is the size of the corn
- float delta = (count > 1)?((165.0f - 62.0f - 30.0f) / (count - 1)):0.0f;
- for (int i=0; i<count; i++, pos += delta)
- {
- // Create the bullet
- CCSprite *sprite = CCSprite::spriteWithFile("acorn.png");
- this->addChild(sprite, 1);
- b2BodyDef bulletBodyDef;
- bulletBodyDef.type = b2_dynamicBody;
- bulletBodyDef.bullet = true;
- bulletBodyDef.position.Set(pos/PTM_RATIO,(FLOOR_HEIGHT+15.0f)/PTM_RATIO);
- bulletBodyDef.userData = sprite;
- b2Body *bullet = m_world->CreateBody(&bulletBodyDef);
- bullet->SetActive(false);
- b2CircleShape circle;
- circle.m_radius = 15.0/PTM_RATIO;
- b2FixtureDef ballShapeDef;
- ballShapeDef.shape = &circle;
- ballShapeDef.density = 0.8f;
- ballShapeDef.restitution = 0.2f;
- ballShapeDef.friction = 0.99f;
- bullet->CreateFixture(&ballShapeDef);
- m_bullets.push_back(bullet);
- }
- }
- }
这个方法的大部分你现在应该很熟悉啦。我们的方法将创建几个子弹和在第一个松鼠和投射器身体之间的平均的空隙。
bulletBodyDef的bullet参数可能是你之前没有见过的细节之一。这告诉box2d这个物体是一个高速物体,在模拟期间box2d会小心的模拟它。
box2d的官方文档解释了为什么我们要把这些物体设置为子弹子弹物体:
“Game simulation usually generates a sequence of images that are played at some frame
rate. This is called discrete simulation. In discrete simulation, rigid bodies can move
by a large amount in one time step. If a physics engine doesn't account for the large
motion, you may see some objects incorrectly pass through each other. This effect is
called tunneling."
“游戏模拟通常是以一定的帧速播放一系列图。这被称为离散模拟。在离散模拟中,刚体可以在一个时间步中移动一大段距离。如果物理引擎没有对高速移动的解决方案,你可能看到某些物体穿越了,被称作隧道效应”。
默认情况下,box2d应用持续碰撞检测(continuous collision detection (CCD))来防止动态物体贯穿静态物体。通过从旧的坐标到新坐标来扫描形状从而完成前述功能。引擎通过扫描和计算冲撞的TOI(time of impact)寻找新的碰撞。物体在第一个TOI移动并在接下来的时间步中停止。
正常情况下,持续碰撞检测并不用于动态物体间。这是为了让性能更加优化。但在某些游戏中你需要动态物体应用持续碰撞检测。例如,你想做个发射高速子弹打一堆砖块的游戏。没有持续碰撞检测,子弹可能会因为隧道效应贯穿砖块。
接下来我们会创建一个用来将子弹粘在投射器上的方法。我们还需要2个额外的类属性,在头文件中增加代码:
- b2Body *m_bulletBody;
- b2WeldJoint *m_bulletJoint;
子弹关节将会保持对我们创建的在子弹和投射器臂直接关节的引用。
现在回到实现文件,在createBullets方法后增加下面的方法:
- bool Catapult::attachBullet()
- {
- if (m_currentBullet < m_bullets.size())
- {
- m_bulletBody = (b2Body*)m_bullets.at(m_currentBullet++);
- m_bulletBody->SetTransform(b2Vec2(230.0f/PTM_RATIO, (155.0f+FLOOR_HEIGHT)/PTM_RATIO), 0.0f);
- m_bulletBody->SetActive(true);
- b2WeldJointDef weldJointDef;
- weldJointDef.Initialize(m_bulletBody, m_armBody, b2Vec2(230.0f/PTM_RATIO,(155.0f+FLOOR_HEIGHT)/PTM_RATIO));
- weldJointDef.collideConnected = false;
- m_bulletJoint = (b2WeldJoint*)m_world->CreateJoint(&weldJointDef);
- return true;
- }
- return false;
- }
- void Catapult::resetGame( )
- {
- this->createBullets(4);
- this->attachBullet();
- }
- CCCallFunc *callSelectorAction = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame));
- this->runAction(CCSequence::actions(
- callSelectorAction,
- NULL));
- CCDelayTime *delayAction = CCDelayTime::actionWithDuration(0.2f);
- CCCallFunc *callSelectorAction = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame));
- this->runAction(CCSequence::actions(delayAction,
- callSelectorAction,
- NULL));
- bool m_releasingArm;
- void Catapult::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
- {
- if (m_mouseJoint != NULL)
- {
- if (m_armJoint->GetJointAngle() >= CC_DEGREES_TO_RADIANS(5))
- {
- m_releasingArm = true;
- }
- m_world->DestroyJoint(m_mouseJoint);
- m_mouseJoint = NULL;
- return;
- }
- }
- m_releasingArm = false;
- // Arm is being released
- if (m_releasingArm && m_bulletJoint != NULL)
- {
- // Check if the arm reached the end so we can return the limits
- if (m_armJoint->GetJointAngle() <= CC_DEGREES_TO_RADIANS(10))
- {
- m_releasingArm = false;
- // Destroy joint so the bullet will be free
- m_world->DestroyJoint(m_bulletJoint);
- m_bulletJoint = NULL;
- }
- }
- armJointDef.maxMotorTorque = 700;//4800
- // Bullet is moving.
- if (m_bulletBody && m_bulletJoint == NULL)
- {
- b2Vec2 position = m_bulletBody->GetPosition();
- CCPoint myPosition = this->getPosition();
- CCSize screenSize = CCDirector::sharedDirector()->getWinSize();
- // Move the camera.
- if (position.x > screenSize.width / 2.0f / PTM_RATIO)
- {
- myPosition.x = -MIN(screenSize.width * 2.0f - screenSize.width, position.x * PTM_RATIO - screenSize.width / 2.0f);
- this->setPosition(myPosition);
- }
- }