首页 > 代码库 > 笔记:利用 Cocos2dx 3.2 与 Box2D制作一个跑酷游戏
笔记:利用 Cocos2dx 3.2 与 Box2D制作一个跑酷游戏
最近写lua写得没有力气了,所以想让脑袋放松一下,刚好看到有人在用swift做游戏:
Swift游戏实战-跑酷熊猫
于是脑子一短路,就想到了利用这些素材来做一个游戏。
本来不想记笔记的,但是由于选择物理引擎的时候遇到诸多问题,所以选择记录下来,目前只做了个雏形,需要再完善一点。
需要知识:
1 cocos2dx-3.2 基本知识
2 box2d有一定的了解。
由于比较简单,所以把所有代码给上了先,然后再简单介绍下遇到的问题之类的东西。
首先是主角,熊猫类:
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 }
然后是场景类,里面有一些逻辑处理:
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__
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 }
需要注意:
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.h和GLES-Render.cpp拷到项目里面来,因为Box2d没有b2Debug的实现,这个是我们手头上需要的实现。
3 使用Box2d的时候极有可能会出现编译连接错误,找不到Box2d相关的类,这个时候需要将cocos2d-x-3.2\external\Box2D编译一下,然后在项目的连接器中加入引用:
4 通过b2World::SetDebugDraw(b2Draw * draw)将GLESDebugDraw实例设置为world的debugDraw之后,每帧调用world->DrawDebugData()是不会生效的,只有复写Layer::draw()方法并在其中调用此方法才会显示debugDraw。
由于debugDraw是调用显卡去渲染的,所以debugDraw是位于场景最底层的,当你把所有场景内的显示对象的通过setVisible设置为false的时候,你就可以看到了。
可以设置SHOW_DEBUG的值来设置显示对象的visible。
如下:
可以看到下面的debugDraw有点测漏了。
5 cocos2dx 3.2内置了chipmunk物理引擎,用起来比较简单,但是我之前学习ActionScript的时候有用过Box2d,并且Box2d的功能比较强大一些,所以选择了Box2d。ActionScript还有一个效率比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 }
建议:
不想吐槽搜索引擎,建议大家遇到问题先尝试自己解决,解决不了的然后可以用国外的搜索引擎搜索一下,或者可以直接去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制作一个跑酷游戏