(转)Cocos2d-x加Box2D制作弹弓类游戏

2023-11-20 15:59

本文主要是介绍(转)Cocos2d-x加Box2D制作弹弓类游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://mssyy2010.blog.51cto.com/4595971/847000

文章原版为英文版,地址链接在文章尾部给出。原文代码版本为object-c版,本文代码版本为C++版。对原文大部分内容进行了翻译,并将对oc版的说明更改为C++版。文章cocos2d-x版本cocos2d-1.0.1-x-0.11.0。

如何用Box2D和cocos2d-x制作弹弓类游戏 第一部分

这是一篇由ios教程团队成员Gustavo Ambrozio上传的博客。一位拥有超过20年软件开发经验,超过3年ios开发经验的软件工程师,CodeCrop软件创始人。

在这个教程系列中我们将会通过使用cocos2d-x和Box2D创建一个很COOL的弹弓类型游戏。

我们将使用Ray的可爱而富有天赋的老婆Vicki创作的弹弓,栗子,狗,猫和愤怒的松鼠素材来创建游戏。(素材我会在上传附件)

 

在这个教程系列,你将学到:

  • 怎么用旋转关节(rotation joints)
  • 怎么用连接关节(weld joints)
  • 怎么让视角跟随抛射物
  • 怎么根据碰撞检测判断力量来消除敌人
  • 和很多其他的

 

这个教程系列假设你已经掌握了  Intro to Box2D with Cocos2D Tutorial: Bouncing Balls Tutorial或者已经掌握了相关知识。

教程中还会使用很多制作撞球游戏中的概念。

 

开始吧

新建HelloWorld项目,清空项目。记得选择需要Box2d支持的cocos2d-x工程。声明一个catapult类。和HelloWorld类除了名字全都一样。

 

加入些精灵

 

首先我们先添加项目将用的资源。


现在我们来加入些不会被物理模拟的精灵。默认的CCSprite的锚点是中心,我将锚点改到了左下角为了更容易的放置它们。

在init方法中// add your codes below...下面添加代码:

  
  1. CCSprite *sprite = CCSprite::spriteWithFile("bg.png");  //背景图 
  2.         sprite->setAnchorPoint(CCPointZero); 
  3.         this->addChild(sprite, -1); 
  4.      
  5.         CCSprite *sprite = CCSprite::spriteWithFile("catapult_base_2.png"); //投射器底部后面那块 
  6.         sprite->setAnchorPoint(CCPointZero); 
  7.         sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT)); 
  8.         this->addChild(sprite, 0); 
  9.      
  10.         sprite = CCSprite::spriteWithFile("squirrel_1.png");        //左边松鼠 
  11.         sprite->setAnchorPoint(CCPointZero); 
  12.         sprite->setPosition(CCPointMake(11.0, FLOOR_HEIGHT)); 
  13.         this->addChild(sprite, 0); 
  14.      
  15.         sprite = CCSprite::spriteWithFile("catapult_base_1.png");   //投射器底部前面那块 
  16.         sprite->setAnchorPoint(CCPointZero); 
  17.         sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT)); 
  18.         this->addChild(sprite, 9); 
  19.      
  20.         sprite = CCSprite::spriteWithFile("squirrel_2.png");    //右边松鼠 
  21.         sprite->setAnchorPoint(CCPointZero); 
  22.         sprite->setPosition(CCPointMake(240.0, FLOOR_HEIGHT)); 
  23.         this->addChild(sprite, 9); 
  24.      
  25.         sprite = CCSprite::spriteWithFile("fg.png");    //带冰的地面 
  26.         sprite->setAnchorPoint(CCPointZero); 
  27.         this->addChild(sprite, 10); 

你也许注意到了很多使用Y坐标的地方用了宏FLOOR_HEIGHT,但我们并未define它。

  
  1. #define FLOOR_HEIGHT    62.0f 

定义了这个宏之后,如果我们改变了地板高度,我们可以更加简便的放置精灵。

上效果图。


看起来不错!

上面就是非物理模拟的部分。

 

 

增加弹弓臂

是时候给世界加些物理属性了,接下来的代码就是加世界边框的模板式的代码了,让我们改变一点来描述我们的世界。

类声明中添加:

  
  1. private
  2.     b2World* m_world; 
  3.     b2Body* m_groundBody; 

init方法尾部添加:

  
  1. b2Vec2 gravity; 
  2. gravity.Set(0.0f, -10.0f); 
  3. bool doSleep = true
  4. m_world = new b2World(gravity); 
  5. m_world->SetAllowSleeping(doSleep); 
  6. m_world->SetContinuousPhysics(true); 
  7.  
  8. // Define the ground body. 
  9. b2BodyDef groundBodyDef; 
  10. groundBodyDef.position.Set(0, 0); // bottom-left corner 
  11.  
  12. // Call the body factory which allocates memory for the ground body 
  13. // from a pool and creates the ground box shape (also from a pool). 
  14. // The body is also added to the world. 
  15. m_groundBody = m_world->CreateBody(&groundBodyDef); 

默认是世界的尺寸是iphone屏幕尺寸。因为我们场景的宽度是世界宽度的2被。完成这个任务我们只需要让宽度乘以1.5.

另外,由于我们世界的地板并不是在屏幕的底部,所以我们需要编写相应的代码。

边界代码:

  
  1. b2EdgeShape groundBox; 
  2.     // bottom 
  3.     groundBox.Set(b2Vec2(0,FLOOR_HEIGHT/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,FLOOR_HEIGHT/PTM_RATIO)); 
  4.     m_groundBody->CreateFixture(&groundBox, 0); 
  5.      
  6.     // top 
  7.     groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,screenSize.height/PTM_RATIO)); 
  8.     m_groundBody->CreateFixture(&groundBox, 0); 
  9.      
  10.     // left 
  11.     groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(0,0)); 
  12.     m_groundBody->CreateFixture(&groundBox, 0); 
  13.      
  14.     // right 
  15.     groundBox.Set(b2Vec2(screenSize.width*1.5f/PTM_RATIO,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*1.5f/PTM_RATIO,0)); 
  16.     m_groundBody->CreateFixture(&groundBox, 0); 

说明:box2d某次更新后以前的SetAsEdge函数被删除了,但是可以使用b2EdgeShape类型对象来生成边界,函数名也变为Set。

现在让我们增加弹弓臂,首先增加物体(body)和夹具(fixture)的指针。打开HelloWorld.h把下面的代码加入到类中。

  
  1. private
  2.     b2Fixture *m_armFixture; 
  3.     b2Body *m_armBody; 

进入到HelloWorld.cpp文件中的init函数的底部:

  
  1. // Create the catapult's arm 
  2.     CCSprite *arm = CCSprite::spriteWithFile("catapult_arm.png"); 
  3.     this->addChild(arm, 1); 
  4.      
  5.     b2BodyDef armBodyDef; 
  6.     armBodyDef.type = b2_dynamicBody; 
  7.     armBodyDef.linearDamping = 1; 
  8.     armBodyDef.angularDamping = 1; 
  9.     armBodyDef.position.Set(230.0f/PTM_RATIO, (FLOOR_HEIGHT+91.0f)/PTM_RATIO); 
  10.     armBodyDef.userData = arm; 
  11.     m_armBody = m_world->CreateBody(&armBodyDef); 
  12.      
  13.     b2PolygonShape armBox; 
  14.     b2FixtureDef armBoxDef; 
  15.     armBoxDef.shape = &armBox; 
  16.     armBoxDef.density = 0.3F; 
  17.     armBox.SetAsBox(11.0f/PTM_RATIO, 91.0f/PTM_RATIO); 
  18.     m_armFixture = m_armBody->CreateFixture(&armBoxDef); 

 

 

你如果看过之前的Box2D教程那么这些代码对你而言应该很熟悉。

我们先读取弹弓臂精灵并把它加入到层中。注意z轴索引。当我们向scene中加入静态精灵时候我们使用Z轴索引。

让我们的弹弓臂位于2块弹弓底部之间看起来不错!

类声明中增加:

  
  1. void tick(cocos2d::ccTime dt); 

cpp文件增加:

  
  1. void HelloWorld::tick(ccTime dt) 
  2.     int velocityIterations = 8; 
  3.     int positionIterations = 1; 
  4.  
  5.     m_world->Step(dt, velocityIterations, positionIterations); 
  6.  
  7.     //Iterate over the bodies in the physics world 
  8.     for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext()) 
  9.     { 
  10.         if (b->GetUserData() != NULL) { 
  11.             //Synchronize the AtlasSprites position and rotation with the corresponding body 
  12.             CCSprite* myActor = (CCSprite*)b->GetUserData(); 
  13.             myActor->setPosition( CCPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO) ); 
  14.             myActor->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()) ); 
  15.         }    
  16.     } 
  17. }

在init方法尾部加入:

  
  1. schedule(schedule_selector(Catapult::tick)); 

看到没?我们并没有设置精灵的位置,因为tick方法会更正精灵的位置到box2D物体的位置。

接下来我们创建box2d物体作为一个动态物体。userData属性在这里很重要,因为正如我上段提到的,精灵会跟随物体。

另外注意到坐标被设置到在FLOOR_HEIGHT之上。因为我们这里使用的坐标是物体的中心,我们不能用左下角在使用Box2d时候。

接下来就是创建物体物理特性的夹具,一个简单的矩形。

我们设置物体夹具的大小比精灵尺寸小一点,因为精灵尺寸比实际弹弓臂图案尺寸大一点。


在这幅图中,黑色矩形框是精灵尺寸,红色矩形框是夹具尺寸。

运行你会看到机器臂直立着。


 

 

旋转关节

我们需要某种约束来限制投射器的转动在一定角度内。借助关节(joints)你可以约束Box2D关联物体运动.

有一种特殊的关节可以完美解决我们的问题——旋转关节(revolute joint)。想象一个钉子将2个物体钉在一个特殊的点,但仍然允许他们转动。

让我们试试吧!回到HelloWorld.h在类中加入属性:

  
  1. b2RevoluteJoint *m_armJoint; 

回到类实现文件在生成发射器臂之后加入下面的代码:

  
  1. b2RevoluteJointDef armJointDef; 
  2. armJointDef.Initialize(m_groundBody, m_armBody, b2Vec2(233.0f/PTM_RATIO, FLOOR_HEIGHT/PTM_RATIO)); 
  3. armJointDef.enableMotor = true
  4. armJointDef.enableLimit = true
  5. armJointDef.motorSpeed  = -10; //-1260; 
  6. armJointDef.lowerAngle  = CC_DEGREES_TO_RADIANS(9); 
  7. armJointDef.upperAngle  = CC_DEGREES_TO_RADIANS(75); 
  8. armJointDef.maxMotorTorque = 700; 
  9. m_armJoint = (b2RevoluteJoint*)m_world->CreateJoint(&armJointDef); 

 

 

当我们创建关节时你不得不修改2个物体和连接点。你可能会想:“我们不应该把投射器臂连接到投射器底部吗》”。在现实世界中,没错。

但是在Box2D中这可不是必要的。你可以这样做但你不得不再为投射器底部创建另一个物体并增加了模拟的复杂性。

由于投射器底部在何时都是静态的并且在Box2d中枢纽物体(hinge body)不必要在其他的任何物体中,我们可以只使用我们已拥有的大地物体(groundBody)。

角度限制外加马达(motor)然我们的投射器更加像真实世界中的投射器。

你也许会注意到我们在关节上设置一个马达激活,通过设置“enableMotor”,“motorSpeed”,和“maxMotorTorque”。

通过设置马达速度为负,可以使投射器臂持续的顺时针转动。

然而,我们还需要通过设置”enableLimit“,”lowerAngle“,”upperAngle“激活关节。这让关节活动范围角度9到75°。这如我们所想的那样模拟投射器运动。

然后我们为了向后拉动投射器将增加另一个关节。当我们释放这个力后马达会让投射器臂向前运动,更像真实的投射器啦!

马达的速度值是每秒弧度值。没错,不是很直观,我知道。我所做的就是不听修改值直到获得了我想的效果。你可以从小的值开始增加知道你获得了期望的速度。最大马达扭矩(maxMotorTorque)是马达可达的最大扭矩。你可以改变这个值来看看物体的反应。那么你会清楚他的作用。

运行app你会看到投射器臂位置现在偏左了:

 


 

 

推动投射器臂吧!

好的,现在是时候移动这个投射器臂啦!为了完成这个任务我们将会使用鼠标关节(mouse joint)。如果你读了雷的弹球游戏教程你就一定已经知道鼠标关节是什么了。

但你没读过,这里是Ray的定义:

 

“In Box2D, a mouse joint is used to make a body move toward a specified point.”

那就是我们正需要的,所以,然我们先声明一个鼠标关节变量在类定义中:

  
  1. private:     
  2.     b2MouseJoint *m_mouseJoint; 
  3.  
  4. public
  5.     virtual void ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event); 
  6.     virtual void ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event); 
  7.     virtual void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event); 

 

接下来在类实现文件增加CCTouchesBegan方法:

  
  1. void Catapult::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint != NULL)   {       return;     } 
  3.  
  4.     CCTouch *touch = (CCTouch *)touches->anyObject(); 
  5.     CCPoint location = touch->locationInView(touch->view()); 
  6.     location = CCDirector::sharedDirector()->convertToGL(location); 
  7.     b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO); 
  8.  
  9.     if (locationWorld.x < m_armBody->GetWorldCenter().x + 150.0/PTM_RATIO) 
  10.     { 
  11.         b2MouseJointDef md; 
  12.         md.bodyA = m_groundBody; 
  13.         md.bodyB = m_armBody; 
  14.         md.target = locationWorld; 
  15.         md.maxForce = 2000; 
  16.  
  17.         m_mouseJoint = (b2MouseJoint *)m_world->CreateJoint(&md); 
  18.     } 

在init方法中调用tick方法代码前:

  
  1. m_mouseJoint = NULL; 

 

又是引用Ray的话:

"When you set up a mouse joint, you have to give it two bodies. The first isn’t used, but

the convention is to use the ground body. The second is the body you want to move”.

当你创建一个鼠标关节,你不得不给他2个物体,第一个实际上并不会被使用,但为了方便就把ground body赋给它吧,第二个是你想移动的物体。

目标就是我们想要用关节来移动物体。我们首先要将触摸点左边转换成cocos2d-x坐标在转换为Box2D坐标。我们只在触摸在投射器臂物体左边时才会创建关节。50像素的偏移让我们可以触摸投射器臂稍微偏右的位置。

最大力参数将决定应用在投射器臂上跟随目标点移动的最大力。就我们而言,我们不得不确保它足够强壮来抵消被应用与旋转关节的马达的扭矩。

In our case we have to make it strong enough to counteract the torque applied by the motor of the revolute joint.

如果你把这个值设置的太小,那么你将不能拉回投射器臂,因为它的扭矩过大。你可以减小我们的转动关节的最大马达扭矩(maxMotorTorque)或增大最大力(maxForce)参数。

为了确定什么值合适我建议你用下鼠标关节的最大力参数和旋转关节的最大马达扭矩参数。减小maxForce到500并试验,你将看到你不能推动投射器臂。那么减小maxMotorTroque到1000你将看到你又可以推动它了。但让我们先完成这些的实现。。

 

我们现在不得不实现CCTouchesMoved方法,这样鼠标关节会跟随你的触摸:

  
  1. void Catapult::ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint == NULL)   {   return; } 
  3.  
  4.     CCTouch *touch = (CCTouch *)touches->anyObject(); 
  5.     CCPoint location = touch->locationInView(touch->view()); 
  6.     location = CCDirector::sharedDirector()->convertToGL(location); 
  7.     b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO); 
  8.  
  9.     m_mouseJoint->SetTarget(locationWorld); 

这个方法足够简单。它只是把屏幕坐标转换为世界坐标,在将鼠标关节的目标点转换为这个点。

为了完成它我们不得不通过销毁鼠标关节释放投射器臂。让我们在CCTouchesEnded方法中实现它:

  
  1. void Catapult::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint != NULL) 
  3.     { 
  4.         m_world->DestroyJoint(m_mouseJoint); 
  5.         m_mouseJoint = NULL; 
  6.         return
  7.     } 

 

 

很简单!只是销毁了关节清理了变量。试试看程序吧!用你的鼠标拉动投射器臂。当你放开左键时你会看到投射器臂很快的恢复到初始位置。


这真是太快了。正如你记得的,控制它的速度的,就是旋转关节(revolute joint)的马达速度(motorSpeed)和最大马达扭矩(maxMotorTorque)。让我们减小马达速度来看看会发生什么。

回到init方法改小值。一个让我的程序工作的不错的值是-10.改变这个值然后你会看到速度是用来让投射器看起来更真实的参数。

预备!瞄准!开火!
是时候了,填充橡子。
我们将要在游戏开头创建每一个子弹物体,然后在一个个实用。因此我们需要1个空间来存储他们。在头文件类声明中加入:
   
  1. std::vector<b2Body *> m_bullets; 
  2.     int m_currentBullet; 
增加一个方法用来生产子弹:(头文件中加声明)
    
  1. void Catapult::createBullets(int count) 
  2.     m_currentBullet = 0; 
  3.     float pos = 62.0f; 
  4.      
  5.     if (count > 0) 
  6.     { 
  7.         // delta is the spacing between corns 
  8.         // 62 is the position o the screen where we want the corns to start appearing 
  9.         // 165 is the position on the screen where we want the corns to stop appearing 
  10.         // 30 is the size of the corn 
  11.          
  12.         float delta = (count > 1)?((165.0f - 62.0f - 30.0f) / (count - 1)):0.0f; 
  13.                  
  14.         for (int i=0; i<count; i++, pos += delta) 
  15.         { 
  16.             // Create the bullet 
  17.             CCSprite *sprite = CCSprite::spriteWithFile("acorn.png"); 
  18.             this->addChild(sprite, 1); 
  19.              
  20.             b2BodyDef bulletBodyDef; 
  21.             bulletBodyDef.type = b2_dynamicBody; 
  22.             bulletBodyDef.bullet = true
  23.             bulletBodyDef.position.Set(pos/PTM_RATIO,(FLOOR_HEIGHT+15.0f)/PTM_RATIO); 
  24.             bulletBodyDef.userData = sprite; 
  25.             b2Body *bullet = m_world->CreateBody(&bulletBodyDef); 
  26.             bullet->SetActive(false); 
  27.              
  28.             b2CircleShape circle; 
  29.             circle.m_radius = 15.0/PTM_RATIO; 
  30.              
  31.             b2FixtureDef ballShapeDef; 
  32.             ballShapeDef.shape = &circle; 
  33.             ballShapeDef.density = 0.8f; 
  34.             ballShapeDef.restitution = 0.2f; 
  35.             ballShapeDef.friction = 0.99f; 
  36.             bullet->CreateFixture(&ballShapeDef); 
  37.              
  38.             m_bullets.push_back(bullet); 
  39.         } 
  40.     } 
这个方法的大部分你现在应该很熟悉啦。我们的方法将创建几个子弹和在第一个松鼠和投射器身体之间的平均的空隙。
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个额外的类属性,在头文件中增加代码:
   
  1. b2Body *m_bulletBody; 
  2. b2WeldJoint *m_bulletJoint; 
子弹关节将会保持对我们创建的在子弹和投射器臂直接关节的引用。
现在回到实现文件,在createBullets方法后增加下面的方法:
    
  1. bool Catapult::attachBullet() 
  2.     if (m_currentBullet < m_bullets.size()) 
  3.     { 
  4.         m_bulletBody = (b2Body*)m_bullets.at(m_currentBullet++); 
  5.         m_bulletBody->SetTransform(b2Vec2(230.0f/PTM_RATIO, (155.0f+FLOOR_HEIGHT)/PTM_RATIO), 0.0f); 
  6.         m_bulletBody->SetActive(true); 
  7.          
  8.         b2WeldJointDef weldJointDef; 
  9.         weldJointDef.Initialize(m_bulletBody, m_armBody, b2Vec2(230.0f/PTM_RATIO,(155.0f+FLOOR_HEIGHT)/PTM_RATIO)); 
  10.         weldJointDef.collideConnected = false
  11.          
  12.         m_bulletJoint = (b2WeldJoint*)m_world->CreateJoint(&weldJointDef); 
  13.         return true
  14.     } 
  15.      
  16.     return false

我们首先获得当前子弹的指针(之后我们有一个方法来循环遍历它们)。SetTramsform方法改变了物体中心的位置。坐标就是投射器的尖端坐标。我们接下来激活子弹物体来让box2d开始进行对它的物理模拟。

然后我们创建了一个连接关节(weld joint)。连接关节通过初始化时指定的点来连接2个物体并且不允许在它们之间的连接点向前方的其他移动。

我们设置碰撞连接为假,因为我们不想子弹和投射器臂之间碰撞。

注意,如果还有可用子弹我们返回真。对于之后检查关卡是否因为我们发射完所有子弹而结束很有用。

让我们再创建一个方法在连接子弹后调用初始化方法。

  
  1. void Catapult::resetGame( ) 
  2.     this->createBullets(4); 
  3.     this->attachBullet(); 

现在增加一个函数调用在init方法末尾:(调用tick前)

  
  1. CCCallFunc *callSelectorAction = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame)); 
  2.     this->runAction(CCSequence::actions(
  3.                                         callSelectorAction, 
  4.                                         NULL)); 

 


运行程序你会发现有些诡异的事情发生了!

橡子在投射器放弹位置偏左。这是因为我设置橡子的位置在投射器臂顶部偏9度。但在init方法最后投射器仍然是0度,那么子弹实际上粘到了错误的位置。

为了修补这个bug我们只能给引擎些时间来放置投射器臂。让我们来改变调用resetGame方法的时间:

  
  1. CCDelayTime *delayAction = CCDelayTime::actionWithDuration(0.2f); 
  2.     CCCallFunc *callSelectorAction = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame)); 
  3.     this->runAction(CCSequence::actions(delayAction, 
  4.                                         callSelectorAction, 
  5.                                         NULL)); 

这样使得调用推迟了半秒。我们之后会有更好的解决方法,但现在就用这个办法。现在你会看到橡子到了正确的位置。


 

如果我们现在拉动投射器臂并释放,橡子并不会飞出去,因为它仍被焊在投射器上。我们需要释放子弹。那么久销毁关节吧。但何时何处来销毁关节呢?

最佳方法就是在每个模拟步中被调用的tick方法中确认坐标。

首先我们需要一种方法来知道投射器臂是否被释放。在类声明中增加代码:

  
  1. bool m_releasingArm; 

现在回到CCTouchesEnded方法中在销毁鼠标关节前的条件:

  
  1. void Catapult::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint != NULL) 
  3.     { 
  4.         if (m_armJoint->GetJointAngle() >= CC_DEGREES_TO_RADIANS(5)) 
  5.         { 
  6.             m_releasingArm = true
  7.         } 
  8.          
  9.         m_world->DestroyJoint(m_mouseJoint); 
  10.         m_mouseJoint = NULL; 
  11.         return
  12.     } 

在init方法中添加:

  
  1. m_releasingArm = false

这让我们仅在投射器臂被拉的角度大于20度时才设置释放的变量为真。如果我们只是拉动投射器臂一点点,子弹并不会被释放。

增加下面代码在tick方法尾部:

  
  1. // Arm is being released 
  2.     if (m_releasingArm && m_bulletJoint != NULL) 
  3.     { 
  4.         // Check if the arm reached the end so we can return the limits 
  5.         if (m_armJoint->GetJointAngle() <= CC_DEGREES_TO_RADIANS(10)) 
  6.         { 
  7.             m_releasingArm = false
  8.  
  9.             // Destroy joint so the bullet will be free 
  10.             m_world->DestroyJoint(m_bulletJoint); 
  11.             m_bulletJoint = NULL; 
  12.  
  13.         } 
  14.     } 

很简单,是吧?我们等到投射器臂几乎回到了初始位置然后我们释放了子弹。

运行工程你将看到子弹飞的很快!

在我看来有点太快了,通过减小旋转关节的最大扭矩来让子弹慢点吧。

回到init方法,将最大扭矩从4800减小到700。你也可以试试其他的值。

  
  1. armJointDef.maxMotorTorque = 700;//4800 

 

移动的镜头

要是场景跟随子弹移动,那么我们就能看到全部运动过程。这实在是不错。

我们可以很容易的做到,只要适当的改变场景的坐标属性。增加下面的代码到tick方法末端

  
  1. // Bullet is moving. 
  2.     if (m_bulletBody && m_bulletJoint == NULL) 
  3.     { 
  4.         b2Vec2 position = m_bulletBody->GetPosition(); 
  5.         CCPoint myPosition = this->getPosition(); 
  6.         CCSize screenSize = CCDirector::sharedDirector()->getWinSize(); 
  7.  
  8.         // Move the camera. 
  9.         if (position.x > screenSize.width / 2.0f / PTM_RATIO) 
  10.         { 
  11.             myPosition.x = -MIN(screenSize.width * 2.0f - screenSize.width, position.x * PTM_RATIO - screenSize.width / 2.0f); 
  12.             this->setPosition(myPosition); 
  13.         } 
  14.     } 

条件语句判断子弹如果不是粘在投射器上,那它一定在运动。我们获得坐标然后确认它是否在屏幕正中右侧。如果是就改变屏幕坐标来使子弹遗址位于屏幕正中。

注意MIN的负号,我们这样做是让屏幕朝相反方向(左)移动。

现在程序很COOL!


资源已经打包到附件

原文地址:

http://www.raywenderlich.com/4756/how-to-make-a-catapult-shooting-game-with-cocos2d-and-box2d-part-1

 


本文出自 “Ghost” 博客,请务必保留此出处http://mssyy2010.blog.51cto.com/4595971/847000



注:图片资源版权为Ghost(http://mssyy2010.blog.51cto.com/4595971/847000)所有,本文转载时为防止原文图片链接改变所以下载到本地再次上传的,本人不具有对该文图片的版权

这篇关于(转)Cocos2d-x加Box2D制作弹弓类游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/395783

相关文章

使用Python制作一个PDF批量加密工具

《使用Python制作一个PDF批量加密工具》PDF批量加密‌是一种保护PDF文件安全性的方法,通过为多个PDF文件设置相同的密码,防止未经授权的用户访问这些文件,下面我们来看看如何使用Python制... 目录1.简介2.运行效果3.相关源码1.简介一个python写的PDF批量加密工具。PDF批量加密

Python开发围棋游戏的实例代码(实现全部功能)

《Python开发围棋游戏的实例代码(实现全部功能)》围棋是一种古老而复杂的策略棋类游戏,起源于中国,已有超过2500年的历史,本文介绍了如何用Python开发一个简单的围棋游戏,实例代码涵盖了游戏的... 目录1. 围棋游戏概述1.1 游戏规则1.2 游戏设计思路2. 环境准备3. 创建棋盘3.1 棋盘类

国产游戏崛起:技术革新与文化自信的双重推动

近年来,国产游戏行业发展迅猛,技术水平和作品质量均得到了显著提升。特别是以《黑神话:悟空》为代表的一系列优秀作品,成功打破了过去中国游戏市场以手游和网游为主的局限,向全球玩家展示了中国在单机游戏领域的实力与潜力。随着中国开发者在画面渲染、物理引擎、AI 技术和服务器架构等方面取得了显著进展,国产游戏正逐步赢得国际市场的认可。然而,面对全球游戏行业的激烈竞争,国产游戏技术依然面临诸多挑战,未来的

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

火柴游戏java版

代码 /*** 火柴游戏* <p>* <li>有24根火柴</li>* <li>组成 A + B = C 等式</li>* <li>总共有多少种适合方式?</li>* <br>* <h>分析:</h>* <li>除去"+"、"="四根,最多可用火柴根数20根。</li>* <li>全部用两根组合成"1",最大数值为1111。使用枚举法,A和B范围在0~1111,C为A+B。判断</li>** @

国产游戏行业的崛起与挑战:技术创新引领未来

国产游戏行业的崛起与挑战:技术创新引领未来 近年来,国产游戏行业蓬勃发展,技术水平不断提升,许多优秀作品在国际市场上崭露头角。从画面渲染到物理引擎,从AI技术到服务器架构,国产游戏已实现质的飞跃。然而,面对全球游戏市场的激烈竞争,国产游戏技术仍然面临诸多挑战。本文将探讨这些挑战,并展望未来的机遇,深入分析IT技术的创新将如何推动行业发展。 国产游戏技术现状 国产游戏在画面渲染、物理引擎、AI

第四次北漂----挣个独立游戏的素材钱

第四次北漂,在智联招聘上,有个小公司主动和我联系。面试了下,决定入职了,osg/osgearth的。月薪两万一。 大跌眼镜的是,我入职后,第一天的工作内容就是接手他的工作,三天后他就离职了。 我之所以考虑入职,是因为 1,该公司有恒歌科技的freex平台源码,可以学学,对以前不懂的解解惑。 2,挣点素材钱,看看张亮002的视频,他用了6000多,在虚幻商城买的吸血鬼游戏相关的素材,可以玩两年。我

OpenStack离线Train版安装系列—0制作yum源

本系列文章包含从OpenStack离线源制作到完成OpenStack安装的全部过程。 在本系列教程中使用的OpenStack的安装版本为第20个版本Train(简称T版本),2020年5月13日,OpenStack社区发布了第21个版本Ussuri(简称U版本)。 OpenStack部署系列文章 OpenStack Victoria版 安装部署系列教程 OpenStack Ussuri版

OpenStack镜像制作系列5—Linux镜像

本系列文章主要对如何制作OpenStack镜像的过程进行描述记录 CSDN:OpenStack镜像制作教程指导(全) OpenStack镜像制作系列1—环境准备 OpenStack镜像制作系列2—Windows7镜像 OpenStack镜像制作系列3—Windows10镜像 OpenStack镜像制作系列4—Windows Server2019镜像 OpenStack镜像制作

OpenStack镜像制作系列4—Windows Server2019镜像

本系列文章主要对如何制作OpenStack镜像的过程进行描述记录  CSDN:OpenStack镜像制作教程指导(全) OpenStack镜像制作系列1—环境准备 OpenStack镜像制作系列2—Windows7镜像 OpenStack镜像制作系列3—Windows10镜像 OpenStack镜像制作系列4—Windows Server2019镜像 OpenStack镜像制作系