首页 > 代码库 > 笔记:利用 Cocos2dx 3.2 与 Box2D制作一个跑酷游戏

笔记:利用 Cocos2dx 3.2 与 Box2D制作一个跑酷游戏

最近写lua写得没有力气了,所以想让脑袋放松一下,刚好看到有人在用swift做游戏:

Swift游戏实战-跑酷熊猫 

于是脑子一短路,就想到了利用这些素材来做一个游戏。

本来不想记笔记的,但是由于选择物理引擎的时候遇到诸多问题,所以选择记录下来,目前只做了个雏形,需要再完善一点。

 

需要知识:

1 cocos2dx-3.2 基本知识

2 box2d有一定的了解。

 

由于比较简单,所以把所有代码给上了先,然后再简单介绍下遇到的问题之类的东西。

首先是主角,熊猫类:

Panda.h
 1 #ifndef __PANDA_H__ 2 #define __PANDA_H__ 3  4 #include "cocos2d.h" 5 USING_NS_CC; 6  7 class Panda : public cocos2d::Node 8 { 9 public:10     Vector<SpriteFrame*> run_frames;11     Vector<SpriteFrame*> jump_effect_frames;12     Vector<SpriteFrame*> roll_frames;13     Vector<SpriteFrame*> jump_frames;14     Animation * run_anim;15     Animation * jump_anim;16     Animation * jump_effect_anim;17     Animation * roll_anim;18     Sprite * shape;19     Animation * anim;20     int cur_state;//current state21     // Here‘s a difference. Method ‘init‘ in cocos2d-x returns bool, instead of returning ‘id‘ in cocos2d-iphone22     virtual bool init();  23     //play status RUM||ROLL||JUMP etc24     void play(unsigned int state);25 26     void updateState();27     // implement the "static create()" method manually28     CREATE_FUNC(Panda);29 30     int getCurrentState();31     //panda 4 states32     enum {33         RUN = 1,34         JUMP = 2,35         JUMP2 = 336     };37 };38 39 #endif // __PANDA_H__

 1 #include "Panda.h" 2 #include "cocos2d.h" 3  4 USING_NS_CC; 5  6 bool Panda::init() 7 { 8     if(!Node::init()) 9         return false;10     11     run_frames = Vector<SpriteFrame*>(8);12     roll_frames = Vector<SpriteFrame*>(8);13     jump_effect_frames = Vector<SpriteFrame*>(4);14     jump_frames = Vector<SpriteFrame*>(8);15     char str[40] = {0};16     for(int i=1; i<=8;i++)17     {18         sprintf(str,"jump.atlas/panda_jump_0%d.png",i);19         auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str);20         if( frame != NULL )21         {22             jump_frames.pushBack(frame);23         }24 25         sprintf(str,"roll.atlas/panda_roll_0%d.png",i);26         auto frame_0 = SpriteFrameCache::getInstance()->getSpriteFrameByName(str);27         if( frame_0 != NULL )28         {29             roll_frames.pushBack(frame_0);30         }31 32         sprintf(str,"run.atlas/panda_run_0%d.png",i);33         auto frame_1 = SpriteFrameCache::getInstance()->getSpriteFrameByName(str);34         if( frame_1 != NULL )35         {36             run_frames.pushBack(frame_1);37         }38 39         sprintf(str,"jump_effect.atlas/jump_effect_0%d.png",i);40         auto frame_2 = SpriteFrameCache::getInstance()->getSpriteFrameByName(str);41         if( frame_2 != NULL )42         {43             jump_effect_frames.pushBack(frame_2);44         }45     }46 47     run_anim = Animation::createWithSpriteFrames(run_frames,0.05f);48     run_anim->retain();49     roll_anim = Animation::createWithSpriteFrames(roll_frames,0.05f);50     roll_anim->retain();51     jump_anim = Animation::createWithSpriteFrames(jump_frames,0.05f);52     jump_anim->retain();53     jump_effect_anim = Animation::createWithSpriteFrames(jump_effect_frames,0.05f);54     jump_effect_anim->retain();55     shape= Sprite::createWithSpriteFrameName("run.atlas/panda_run_01.png");56     addChild(shape);57     return true;58 }59 60 void Panda::play(unsigned int state)61 {62     Action * animate;63     switch(state)64     {65     case RUN:66         cur_state = Panda::RUN;67         animate = RepeatForever::create(Animate::create(run_anim));68         animate->setTag(100);69         shape->runAction(animate);70         break;71     case JUMP:72         cur_state = Panda::JUMP;73         animate = Animate::create(jump_anim);74         animate->setTag(200);75         shape->stopActionByTag(100);76         shape->runAction(animate);77         break;78     case JUMP2:79         animate = Sequence::create(Animate::create(jump_effect_anim),Animate::create(roll_anim));80         animate->setTag(300);81         cur_state = Panda::JUMP2;82         shape->runAction(animate);83         break;84     }85 }86 87 int Panda::getCurrentState()88 {89     return cur_state;90 }91 92 void Panda::updateState()93 {94     if (shape->getActionByTag(200) == nullptr && shape->getActionByTag(300) == nullptr)95     {96         play(Panda::RUN);97     }98 }
Panda.cpp

然后是场景类,里面有一些逻辑处理:

 1 #ifndef __GAMEMAIN_H__ 2 #define __GAMEMAIN_H__ 3  4 #include "cocos2d.h" 5 #include "Panda.h" 6 #include "Box2D\Box2D.h" 7 #include "GLES-Render.h" 8  9 USING_NS_CC;10 //whether show assets11 #define SHOW_DEBUG true12 //draw scale of physics world13 #define DRAW_SCALE 30.0f14 #define ALL_ALHA 0.5f15 16 class GameMain : public cocos2d::Layer17 {18 public:19     //physic engine related20     b2World * world;21     Map<b2Body,Sprite> * map;22     b2Body * panda_body;23     //objects move speed24     float speed;25     Sprite * far_bg;26     Sprite * middle_bg_0;27     Sprite * middle_bg_1;28     Size cSize;29     Size visibleSize;30     Layer * platform_container;31     Panda * panda;32     GLESDebugDraw * debugDraw;33     // there‘s no ‘id‘ in cpp, so we recommend returning the class instance pointer34     static cocos2d::Scene* createScene();35 36     // Here‘s a difference. Method ‘init‘ in cocos2d-x returns bool, instead of returning ‘id‘ in cocos2d-iphone37     virtual bool init();  38 39     virtual void onEnter();40     41     virtual void onExit();42 43     void update(float dt);44 45     virtual bool onTouchBegan(Touch * touch, Event * event);46     virtual void onTouchEnded(Touch * touch, Event * event);47     virtual void onTouchMoved(Touch * touch, Event * event);48 49     virtual bool onContactBegin(const PhysicsContact& contact);50     // implement the "static create()" method manually51     CREATE_FUNC(GameMain);52 53      //54     // Overrides55     //56     virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;57 private:58     void create_ground();59     void gen_step(const std::string& res, float posX, float posY);60     void init_physics_world();61 };62 63 #endif // __HELLOWORLD_SCENE_H__
GameMain.h
  1 #include "GameMain.h"  2 #include "cocos2d.h"  3 #include "Box2D\Box2D.h"  4 #include "GLES-Render.h"  5 USING_NS_CC;  6   7 Scene* GameMain::createScene()  8 {  9     auto scene = Scene::create(); 10     auto layer = GameMain::create(); 11     scene->addChild(layer); 12     return scene; 13 } 14  15  16 bool GameMain::init() 17 { 18     if ( !Layer::init() ) 19     { 20         return false; 21     } 22     speed = 5.0f; 23     auto listener = EventListenerTouchOneByOne::create(); 24     listener->onTouchBegan = CC_CALLBACK_2(GameMain::onTouchBegan,this); 25     listener->onTouchEnded = CC_CALLBACK_2(GameMain::onTouchEnded,this); 26     listener->onTouchMoved = CC_CALLBACK_2(GameMain::onTouchMoved,this); 27  28     Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this); 29  30     visibleSize = Director::getInstance()->getVisibleSize(); 31     Vec2 origin = Director::getInstance()->getVisibleOrigin(); 32     SpriteFrameCache::getInstance()->addSpriteFramesWithFile("panda.plist");//add panda animate 33     SpriteFrameCache::getInstance()->addSpriteFramesWithFile("scene.plist");//add scene resources 34  35     middle_bg_0 = Sprite::create("bbg_snow_bone.jpg"); 36     cSize = middle_bg_0->getContentSize(); 37     middle_bg_0->setPosition(visibleSize.width*0.5, visibleSize.height*0.5); 38     middle_bg_0->setVisible(SHOW_DEBUG); 39     addChild(middle_bg_0); 40  41     init_physics_world(); 42  43     //gen_step(); 44     create_ground(); 45  46     //init panda and its physics data 47     Point p(visibleSize.width/2,visibleSize.height/2); 48     b2BodyDef bodyDef; 49     bodyDef.type = b2_dynamicBody; 50     bodyDef.position.Set(p.x/DRAW_SCALE, p.y/DRAW_SCALE); 51     panda_body = world->CreateBody(&bodyDef); 52     b2MassData massdata; 53     massdata.mass = 100.0f; 54     panda_body->SetMassData(&massdata); 55  56     b2PolygonShape shape; 57     shape.SetAsBox(50.0f/DRAW_SCALE, 100.0f/DRAW_SCALE); 58  59     b2FixtureDef fixtureDef; 60     fixtureDef.shape = &shape; 61     fixtureDef.density = 1.0f; 62     fixtureDef.friction = 0.3f; 63     panda_body->CreateFixture(&fixtureDef); 64  65     panda = Panda::create(); 66     panda->setPosition(visibleSize.width/2, visibleSize.height/2); 67     Size panda_size = Size(50.0f,100.0f); 68     panda->play(Panda::RUN); 69     this->addChild(panda); 70     panda_body->SetUserData(panda); 71     panda->setVisible(SHOW_DEBUG); 72      73     gen_step("platform_l.png",51.0,100.0f); 74     gen_step("platform_m.png",196.0f,100.0f); 75     gen_step("platform_r.png",392.0f,100.0f); 76     return true; 77 } 78  79 void GameMain::init_physics_world() 80 { 81     b2Vec2 gravity; 82     gravity.Set(0.0f,-10.0f); 83     world = new b2World(gravity); 84     world->SetAllowSleeping(true); 85     world->SetContinuousPhysics(true); 86     debugDraw = new GLESDebugDraw(DRAW_SCALE); 87     uint32 flags = 0; 88     flags += GLESDebugDraw::e_shapeBit; 89     flags += GLESDebugDraw::e_aabbBit; 90     flags += GLESDebugDraw::e_centerOfMassBit; 91     debugDraw->SetFlags(flags); 92     world->SetDebugDraw(debugDraw); 93 } 94  95 void GameMain::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) 96 { 97     world->DrawDebugData(); 98     Layer::draw(renderer,transform,flags); 99 }100 101 void GameMain::create_ground()102 {103     b2BodyDef ground_def;104     ground_def.position.Set(visibleSize.width/2/DRAW_SCALE, visibleSize.height/2/DRAW_SCALE);105 106     b2Body * ground_body = world->CreateBody(&ground_def);107 108     /***/109     //bottom110     b2PolygonShape ground_shape;111     ground_shape.SetAsBox(visibleSize.width/2/DRAW_SCALE,0,b2Vec2(0,-visibleSize.height/2/DRAW_SCALE),0);112     ground_body->CreateFixture(&ground_shape,0);113     //top114     ground_shape.SetAsBox(visibleSize.width/2/DRAW_SCALE,0,b2Vec2(0,visibleSize.height/2/DRAW_SCALE),0);115     ground_body->CreateFixture(&ground_shape,0);116     //left117     ground_shape.SetAsBox(0,visibleSize.height/2/DRAW_SCALE,b2Vec2(-visibleSize.width/2/DRAW_SCALE,0),0);118     ground_body->CreateFixture(&ground_shape,0);119     //right 120     ground_shape.SetAsBox(0,visibleSize.height/2/DRAW_SCALE,b2Vec2(visibleSize.width/2/DRAW_SCALE,0),0);121     ground_body->CreateFixture(&ground_shape,0);122 }123 124 bool GameMain::onTouchBegan(Touch * touch, Event * event)125 {126     Sprite * ball = Sprite::create("apple.png");127     ball->setVisible(SHOW_DEBUG);128     Size contentSize = ball->getContentSize();129     addChild(ball);130     b2BodyDef ballDef;131     ballDef.type = b2_dynamicBody;132     ballDef.position.Set(touch->getLocation().x/DRAW_SCALE, touch->getLocation().y/DRAW_SCALE);133     b2Body * ballBody = world->CreateBody(&ballDef);134     ballBody->SetUserData(ball);135     b2MassData massData;136     massData.center = b2Vec2(contentSize.width/2/DRAW_SCALE,contentSize.height/2/DRAW_SCALE);137     massData.mass = 50;138     b2CircleShape ballShape;139     ballShape.ComputeMass(&massData,1.5f);140     ballShape.m_radius=45.0f/DRAW_SCALE;141     b2FixtureDef ballFixture;142     ballFixture.density = 10.0f;143     ballFixture.friction = 1.0f;144     ballFixture.restitution = 0.6f;145     ballFixture.shape = &ballShape;146 147     ballBody->CreateFixture(&ballFixture);148     ballBody->SetGravityScale(10);149     /**150     float vx = touch->getLocation().x > panda->getPositionX() ? 100.0f : -100.0f;151     panda->updateState();152     int cur_state = panda->getCurrentState();153     if( cur_state == Panda::RUN )154     {155         panda_body->ApplyForce(b2Vec2(vx,1000),panda_body->GetPosition(),true);156         panda->play(Panda::JUMP);157     }else if(cur_state == Panda::JUMP)158     {159         panda_body->ApplyForce(b2Vec2(vx,800),panda_body->GetPosition(),true);160         panda->play(Panda::JUMP2);161     }*/162     return true;163 }164 165 void GameMain::update(float dt)166 {167     world->Step(dt,10,10);168     b2Body* b = world->GetBodyList();169     while (b)170     {171         b2Body* bNext = b->GetNext();172         Sprite * sp = (Sprite *)b->GetUserData();173         if( sp != nullptr )174             sp->setPosition(b->GetPosition().x*DRAW_SCALE,b->GetPosition().y*DRAW_SCALE);175 176         b = bNext;177     }178 }179 180 //generate floors181 void GameMain::gen_step(const std::string& res, float posX, float posY)182 {183     Sprite * sp = Sprite::create(res);184     sp->setPosition(posX, posY);185     addChild(sp);186 187     Size contentSize = sp->getContentSize();188 189     b2BodyDef bodyDef;190     bodyDef.position.Set(posX/DRAW_SCALE, posY/DRAW_SCALE);191     b2Body * body = world->CreateBody(&bodyDef);192 193     b2PolygonShape shape;194     shape.SetAsBox(contentSize.width/DRAW_SCALE, contentSize.height/DRAW_SCALE);195 196     b2FixtureDef fixtureDef;197     fixtureDef.shape = &shape;198     fixtureDef.density = 1.0f;199     fixtureDef.friction = 0.3f;200     body->CreateFixture(&fixtureDef);201     sp->setVisible(SHOW_DEBUG);202 }203 204 bool GameMain::onContactBegin(const PhysicsContact& contact)205 {206     log("Contacted...");207     return true;208 }209 210 void GameMain::onTouchEnded(Touch * touch, Event * event)211 {212 213 }214 215 void GameMain::onTouchMoved(Touch * touch, Event * event)216 {217 218 }219 220 void GameMain::onEnter()221 {222     Layer::onEnter();223     this->schedule(schedule_selector(GameMain::update),0.03f);224 }225 226 void GameMain::onExit()227 {228     delete world;229     delete debugDraw;230     Layer::onExit();231     this->unschedule(schedule_selector(GameMain::update));232 }
GameMain.cpp

 

 

需要注意:

1 所有的熊猫动作打包成一个plist,所有的场景素材打包到另一个plist

这就是我们在场景类的init方法里面预加载的两个文件:

1 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("panda.plist");//add panda animate2     SpriteFrameCache::getInstance()->addSpriteFramesWithFile("scene.plist");//add scene resources

资源目录应该是这样的:

2 去cocos2d-x-3.2\tests\cpp-tests\Classes\Box2DTestBed下面将GLES-Render.hGLES-Render.cpp拷到项目里面来,因为Box2d没有b2Debug的实现,这个是我们手头上需要的实现。

3 使用Box2d的时候极有可能会出现编译连接错误,找不到Box2d相关的类,这个时候需要将cocos2d-x-3.2\external\Box2D编译一下,然后在项目的连接器中加入引用:

 

4 通过b2World::SetDebugDraw(b2Draw * draw)GLESDebugDraw实例设置为worlddebugDraw之后,每帧调用world->DrawDebugData()是不会生效的,只有复写Layer::draw()方法并在其中调用此方法才会显示debugDraw

由于debugDraw是调用显卡去渲染的,所以debugDraw是位于场景最底层的,当你把所有场景内的显示对象的通过setVisible设置为false的时候,你就可以看到了。

可以设置SHOW_DEBUG的值来设置显示对象的visible

如下:

 

 可以看到下面的debugDraw有点测漏了。

 

5 cocos2dx 3.2内置了chipmunk物理引擎,用起来比较简单,但是我之前学习ActionScript的时候有用过Box2d,并且Box2d的功能比较强大一些,所以选择了Box2dActionScript还有一个效率比Box2d稍强的无力引擎:Nape

以下是一开始用chipmunk实现的一些功能的代码:

  1 #include "GameMain.h"  2 #include "cocos2d.h"  3   4 USING_NS_CC;  5   6 Scene* GameMain::createScene()  7 {  8     auto scene = Scene::createWithPhysics();  9     scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL); 10     scene->getPhysicsWorld()->setUpdateRate(0.5); 11     auto layer = GameMain::create(); 12     scene->addChild(layer); 13     return scene; 14 } 15  16  17 bool GameMain::init() 18 { 19     if ( !Layer::init() ) 20     { 21         return false; 22     } 23      24     speed = 5.0f; 25      26     auto listener = EventListenerTouchOneByOne::create(); 27     listener->onTouchBegan = CC_CALLBACK_2(GameMain::onTouchBegan,this); 28     listener->onTouchEnded = CC_CALLBACK_2(GameMain::onTouchEnded,this); 29     listener->onTouchMoved = CC_CALLBACK_2(GameMain::onTouchMoved,this); 30  31     auto contact_listener = EventListenerPhysicsContact::create(); 32     contact_listener->onContactBegin = CC_CALLBACK_1(GameMain::onContactBegin,this); 33  34     Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this); 35     Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(contact_listener,1); 36  37     visibleSize = Director::getInstance()->getVisibleSize(); 38     Vec2 origin = Director::getInstance()->getVisibleOrigin(); 39     SpriteFrameCache::getInstance()->addSpriteFramesWithFile("panda.plist");//add panda animate 40     SpriteFrameCache::getInstance()->addSpriteFramesWithFile("scene.plist");//add scene resources 41  42     middle_bg_0 = Sprite::create("bbg_snow_bone.jpg"); 43     cSize = middle_bg_0->getContentSize(); 44     middle_bg_0->setPosition(visibleSize.width*0.5, visibleSize.height*0.5); 45     middle_bg_1 = Sprite::create("bbg_snow_bone.jpg"); 46     middle_bg_1->setPosition(visibleSize.width*1.5, visibleSize.height*0.5); 47     addChild(middle_bg_0); 48     addChild(middle_bg_1); 49  50     gen_floor(); 51  52     panda = Panda::create(); 53     panda->setPosition(visibleSize.width/2, visibleSize.height/2); 54     Size panda_size = Size(50.0f,100.0f); 55     panda->play(Panda::RUN); 56     auto panda_body = PhysicsBody::createBox(panda_size); 57     panda_body->retain(); 58     panda->setPhysicsBody(panda_body); 59     this->addChild(panda); 60      61     auto edge_sp = Sprite::create(); 62     auto edge_body = PhysicsBody::createEdgeBox(visibleSize,PHYSICSBODY_MATERIAL_DEFAULT,3); 63     edge_sp->setPosition(visibleSize.width/2, visibleSize.height/2); 64     edge_sp->setPhysicsBody(edge_body); 65     edge_sp->setTag(0); 66     addChild(edge_sp); 67     return true; 68 } 69  70 bool GameMain::onContactBegin(const PhysicsContact& contact) 71 { 72     log("Contacted..."); 73     return true; 74 } 75  76 bool GameMain::onTouchBegan(Touch * touch, Event * event) 77 { 78     log("Touched....."); 79     int cur_state = panda->getCurrentState(); 80     panda->getPhysicsBody()->applyForce(Vect(500.0f,500.0f)); 81     if( cur_state == Panda::RUN ) 82     { 83         panda->play(Panda::JUMP); 84     }else if(cur_state == Panda::JUMP) 85     { 86         panda->play(Panda::JUMP2); 87     } 88     return true; 89 } 90  91 void GameMain::onTouchEnded(Touch * touch, Event * event) 92 { 93  94 } 95  96 void GameMain::onTouchMoved(Touch * touch, Event * event) 97 { 98  99 }100 101 void GameMain::update(float dt)102 {103     /**104     middle_bg_0->setPositionX(middle_bg_0->getPositionX()-speed);105 106     if(middle_bg_0->getPositionX() < -cSize.width*0.5)107     {108         middle_bg_0->setPositionX(cSize.width*1.5);109     }110 111     middle_bg_1->setPositionX(middle_bg_1->getPositionX()-speed);112     if(middle_bg_1->getPositionX() < -cSize.width*0.5)113     {114         middle_bg_1->setPositionX(cSize.width*1.5);115     }*/116 }117 118 void GameMain::onEnter()119 {120     Layer::onEnter();121     this->schedule(schedule_selector(GameMain::update),0.05f);122 }123 124 void GameMain::onExit()125 {126     Layer::onExit();127     this->unschedule(schedule_selector(GameMain::update));128 }129 130 void GameMain::createFloor(int posx,int posy,int w,int h)131 {132     133 }134 //generate floors135 void GameMain::gen_floor()136 {137     Sprite * sp_l = Sprite::create("platform_l.png");138     auto body_l = PhysicsBody::createBox(sp_l->getContentSize());139     body_l->setDynamic(false);140     sp_l->setPhysicsBody(body_l);141     sp_l->setPosition(50.0f,150);142     addChild(sp_l);143     144     Sprite * sp_m = Sprite::create("platform_m.png");145     auto body_m = PhysicsBody::createBox(sp_m->getContentSize());146     body_m->setDynamic(false);147     sp_m->setPhysicsBody(body_m);148     sp_m->setPosition(sp_l->getPositionX() + (sp_l->getContentSize().width+sp_m->getContentSize().width)/2,150);149     addChild(sp_m);150 151     Sprite * sp_r = Sprite::create("platform_r.png");152     auto body_r = PhysicsBody::createBox(sp_r->getContentSize());153     body_r->setDynamic(false);154     sp_r->setPhysicsBody(body_r);155     sp_r->setPosition(sp_m->getPositionX() + (sp_m->getContentSize().width+sp_r->getContentSize().width)/2,150);156     addChild(sp_r);157 }
GameMain.cpp

 

建议:

不想吐槽搜索引擎,建议大家遇到问题先尝试自己解决,解决不了的然后可以用国外的搜索引擎搜索一下,或者可以直接去StackFlow去搜索

 

参考文章:

(翻译)介绍Box2D的Cocos2D 2.X教程:弹球[Cocos2D-x For WP8]Box2D物理引擎How to enable Box2d debug draw with Coco2d-x 3.0 beta 2

笔记:利用 Cocos2dx 3.2 与 Box2D制作一个跑酷游戏