现在的位置: 首页 > 综合 > 正文

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

2013年10月16日 ⁄ 综合 ⁄ 共 7343字 ⁄ 字号 评论关闭

文章原版为英文版,地址链接在文章尾部给出。原文代码版本为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.        CCSprite *sprite = CCSprite::spriteWithFile("catapult_base_2.png"); //投射器底部后面那块

  5.        sprite->setAnchorPoint(CCPointZero);

  6.        sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT));

  7. this->addChild(sprite, 0);

  8.        sprite = CCSprite::spriteWithFile("squirrel_1.png");        //左边松鼠

  9.        sprite->setAnchorPoint(CCPointZero);

  10.        sprite->setPosition(CCPointMake(11.0, FLOOR_HEIGHT));

  11. this->addChild(sprite, 0);

  12.        sprite = CCSprite::spriteWithFile("catapult_base_1.png");   //投射器底部前面那块

  13.        sprite->setAnchorPoint(CCPointZero);

  14.        sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT));

  15. this->addChild(sprite, 9);

  16.        sprite = CCSprite::spriteWithFile("squirrel_2.png");    //右边松鼠

  17.        sprite->setAnchorPoint(CCPointZero);

  18.        sprite->setPosition(CCPointMake(240.0, FLOOR_HEIGHT));

  19. this->addChild(sprite, 9);

  20.        sprite = CCSprite::spriteWithFile("fg.png");    //带冰的地面

  21.        sprite->setAnchorPoint(CCPointZero);

  22. 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. // Define the ground body.

  8. b2BodyDef groundBodyDef;

  9. groundBodyDef.position.Set(0, 0); // bottom-left corner

  10. // Call the body factory which allocates memory for the ground body

  11. // from a pool and creates the ground box shape (also from a pool).

  12. // The body is also added to the world.

  13. 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. // top

  6.    groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,screenSize.height/PTM_RATIO));

  7.    m_groundBody->CreateFixture(&groundBox, 0);

  8. // left

  9.    groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(0,0));

  10.    m_groundBody->CreateFixture(&groundBox, 0);

  11. // right

  12.    groundBox.Set(b2Vec2(screenSize.width*1.5f/PTM_RATIO,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*1.5f/PTM_RATIO,0));

  13.    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.    b2BodyDef armBodyDef;

  5.    armBodyDef.type = b2_dynamicBody;

  6.    armBodyDef.linearDamping = 1;

  7.    armBodyDef.angularDamping = 1;

  8.    armBodyDef.position.Set(230.0f/PTM_RATIO, (FLOOR_HEIGHT+91.0f)/PTM_RATIO);

  9.    armBodyDef.userData = arm;

  10.    m_armBody = m_world->CreateBody(&armBodyDef);

  11.    b2PolygonShape armBox;

  12.    b2FixtureDef armBoxDef;

  13.    armBoxDef.shape = &armBox;

  14.    armBoxDef.density = 0.3F;

  15.    armBox.SetAsBox(11.0f/PTM_RATIO, 91.0f/PTM_RATIO);

  16.    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. {

  3. int velocityIterations = 8;

  4. int positionIterations = 1;

  5.    m_world->Step(dt, velocityIterations, positionIterations);

  6. //Iterate over the bodies in the physics world

  7. for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext())

  8.    {

  9. if (b->GetUserData() != NULL) {

  10. //Synchronize the AtlasSprites position and rotation with the corresponding body

  11.            CCSprite* myActor = (CCSprite*)b->GetUserData();

  12.            myActor->setPosition( CCPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO) );

  13.            myActor->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()) );

  14.        }    

  15.    }

  16. }

在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. public:

  4. virtualvoid ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event);

  5. virtualvoid ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event);

  6. virtualvoid ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);

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

  1. void Catapult::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event)

  2. {

  3. if (m_mouseJoint != NULL)   {       return;     }

  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. if (locationWorld.x < m_armBody->GetWorldCenter().x + 150.0/PTM_RATIO)

  9.    {

  10.        b2MouseJointDef md;

  11.        md.bodyA = m_groundBody;

  12.        md.bodyB = m_armBody;

  13.        md.target = locationWorld;

  14.        md.maxForce = 2000;

  15.        m_mouseJoint = (b2MouseJoint *)m_world->CreateJoint(&md);

抱歉!评论已关闭.