首页 > 代码库 > 让子弹飞Demo版
让子弹飞Demo版
让子弹飞是我非常喜欢的一款游戏。今天的目标就是利用cocos2dx 3.0 和box2d 打造一款这样的类型游戏的Demo版。本来cocos2dx 3.0 已经封装了physicals模块,可是我在使用的过程中遇到了一些问题,比方子弹速度过快时候会出屏等,所以就认为还是直接封装box2d API来完毕这款Demo。
我主要封装了两个类,一个叫Box2dHandler, 这个类继承自Node和b2ContactListener, 是用来和box2d打交道的。能够用来创建方形,圆形的静态或者动态刚体。创建物理世界,以及处理碰撞的检測重任都交给这个类了。另外一个类叫B2Sprite, 也继承自Node,本来是想继承自Sprite的,可是在实现过程中发现有问题。就改成继承自Node。它的功能是用来粘合cocos2dx和Box2D, 起到一个中间的桥梁作用。这里要感谢一下<<cocos2d-x高级开发教程>>一书,这两个类基本从它移植而来,可是原书使用的是2.0版本号。所以还是要做一些改动。
好了,闲话到此为止。上代码。
首先是Box2dHandler。
#ifndef __BOX2DHANDLER_H__ #define __BOX2DHANDLER_H__ #include "cocos2d.h" #include "Box2D.h" #include "Box2dHandlerDelegate.h" #include "B2Sprite.h" USING_NS_CC; enum shape { box=1, circle=2, }; class Box2dHandler : public cocos2d::Node, public b2ContactListener { private: b2World *m_world; typedef std::pair<b2Fixture*, b2Fixture*> MyContact; std::set<MyContact> m_contacts; public: bool init(); bool initBox2D(); void addBodyForSprite(B2Sprite* sprite, double density = 1.0, double friction = 0.9, double restituion = 0.1, shape type=box); void addFixtureForSprite(B2Sprite* sprite, double density = 1.0, double friction = 0.9, double restituion = 0.1, shape type=box); void addStaticBodyForSprite(B2Sprite* sprite, double density = 0.0); void dealCollision(); public: virtual void BeginContact(b2Contact * contact); virtual void EndContact(b2Contact * contact); static Box2dHandler * handler(); //void draw(); void update(float dt); CC_SYNTHESIZE(Box2dHandlerDelegate*, m_delegate, Delegate); }; #endif // __BOX2DHANDLER_H__
熟悉box2d的话,非常easy看清楚这个类就是封装了主要的Box2D操作。包含创建刚体,以及监听碰撞。
接下来是实现代码。
#include "Box2dHandler.h" #include "HelloWorldScene.h" #define PTM_RATIO 32 Box2dHandler * Box2dHandler::handler() { static Box2dHandler * handler = NULL; if(handler == NULL) { handler = new Box2dHandler(); handler->init(); return handler; } else { return handler; } } bool Box2dHandler::init() { this->initBox2D(); this->scheduleUpdate(); return true; } bool Box2dHandler::initBox2D() { Size s = Director::getInstance()->getWinSize(); b2Vec2 gravity; gravity.Set(0.0f, -10.0f); m_world = new b2World(gravity); m_world->SetAllowSleeping(true); m_world->SetContinuousPhysics(true); m_world->SetContactListener(this); b2BodyDef groundBodyDef; groundBodyDef.position.Set(0, 0); b2Body* groundBody = m_world->CreateBody(&groundBodyDef); b2EdgeShape groundBox; //Bottom //groundBox.Set(b2Vec2(0, 0), b2Vec2(s.width/PTM_RATIO, 0)); //groundBody->CreateFixture(&groundBox, 0); //Top groundBox.Set(b2Vec2(0, s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO, s.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox, 0); //Left groundBox.Set(b2Vec2(0, s.height/PTM_RATIO), b2Vec2(0,0)); groundBody->CreateFixture(&groundBox, 0); //Right groundBox.Set(b2Vec2(s.width/PTM_RATIO, s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO, 0)); groundBody->CreateFixture(&groundBox, 0); return true; } void Box2dHandler::addFixtureForSprite(B2Sprite* sprite, double density, double friction, double restitution, shape type) { b2PolygonShape spriteShape; Size size = sprite->getB2Sprite()->getContentSize() * sprite->getScale(); spriteShape.SetAsBox(size.width / PTM_RATIO / 2, size.height / PTM_RATIO / 2); b2CircleShape circle; circle.m_radius = (sprite->getB2Sprite()->getContentSize().width * sprite->getScale())/2/PTM_RATIO; b2FixtureDef spriteShapeDef; if(type == box) spriteShapeDef.shape = &spriteShape; else spriteShapeDef.shape = &circle; spriteShapeDef.density = density; spriteShapeDef.restitution = restitution; spriteShapeDef.friction = friction; b2Body * spriteBody = sprite->getB2Body(); spriteBody->CreateFixture(&spriteShapeDef); } void Box2dHandler::addBodyForSprite(B2Sprite* sprite, double density, double friction, double restitution, shape type) { b2BodyDef spriteBodyDef; spriteBodyDef.type = b2_dynamicBody; spriteBodyDef.position.Set(sprite->getPosition().x / PTM_RATIO, sprite->getPosition().y / PTM_RATIO); spriteBodyDef.userData = http://www.mamicode.com/sprite;"start"); B2Sprite* spa = static_cast<B2Sprite*>(contact->GetFixtureA()->GetBody()->GetUserData()); B2Sprite* spb = static_cast<B2Sprite*>(contact->GetFixtureB()->GetBody()->GetUserData()); if(spa != NULL && spb != NULL) { MyContact myContact(contact->GetFixtureA(), contact->GetFixtureB()); m_contacts.insert(myContact); } } void Box2dHandler::EndContact(b2Contact* contact) { CCLog("end"); MyContact myContact(contact->GetFixtureA(), contact->GetFixtureB()); m_contacts.erase(myContact); } void Box2dHandler::dealCollision() { if(m_delegate != NULL && m_contacts.size()>0) { std::set<MyContact>::iterator it; for(it = m_contacts.begin(); it != m_contacts.end(); ++it) { B2Sprite* bullet = static_cast<B2Sprite*>(it->first->GetBody()->GetUserData()); B2Sprite* actor = static_cast<B2Sprite*>(it->second->GetBody()->GetUserData()); if(bullet->getTag() == kTagBulletBase && (actor->getTag() == kTagRedEnemy || actor->getTag() == kTagBlueEnemy || actor->getTag() == kTagYellowEnemy)) m_delegate->CollisionEvent(bullet, actor); else if((bullet->getTag() == kTagRedEnemy || bullet->getTag() == kTagBlueEnemy || bullet->getTag() == kTagYellowEnemy) && actor->getTag() == kTagBulletBase ) m_delegate->CollisionEvent(actor, bullet); } } m_contacts.clear(); }
简单解释下:
Box2dHandler * Box2dHandler::handler()构造函数,内存管理交给cocos2dx
bool Box2dHandler::initBox2D()设置好重力场,创建物理世界m_world, 以及定义好屏幕边界为可碰撞的静态刚体。
void Box2dHandler::addBodyForSprite(B2Sprite* sprite, double density, double friction, double restitution, shape type)
创建动态刚体,有方形和圆形两种选择。
void Box2dHandler::addFixtureForSprite(B2Sprite* sprite, double density, double friction, double restitution, shape type)
为刚体创建外观并设置刚体属性。
void Box2dHandler::update(float dt)更新物理世界,并同步刚体位置到cocos2dx中精灵,当中的桥梁就是B2Sprite。
void Box2dHandler::BeginContact(b2Contact * contact)
void Box2dHandler::EndContact(b2Contact* contact)物理碰撞检測的回调方法。
void Box2dHandler::dealCollision()自己定义的碰撞处理方法。
就这么多。非常easy明了。接下来看看B2Sprite的代码
#include "cocos2d.h" #include "Box2D.h" USING_NS_CC; enum Enemy_Color { k_red = 0, k_blue = 1, k_yellow = 2, }; class B2Sprite : public cocos2d::Node { public: static B2Sprite* create(CCTexture2D * texture); static B2Sprite* create(const char* pngFile); bool init(const char* pngFile); bool init(CCTexture2D* texture); CCActionInterval* createAnimation(const char* plist,int frames); CCActionInterval* createZombileAnimation(const char* plist, int frames, Enemy_Color color); static B2Sprite* create(const char* plist, int frames, Enemy_Color color); bool init(const char* plist, int frames, Enemy_Color color); CC_SYNTHESIZE_READONLY(Sprite*, m_sprite, B2Sprite); CC_SYNTHESIZE(b2Body*, m_b2Body, B2Body); // 物理实际的“物体” CC_SYNTHESIZE(bool, m_isDead, IsDead); CC_SYNTHESIZE(bool, m_isAlive, IsAlive); }; #endif
主要内容在这里
CC_SYNTHESIZE_READONLY(Sprite*, m_sprite, B2Sprite); CC_SYNTHESIZE(b2Body*, m_b2Body, B2Body); // 物理实际的“物体”我们能够看到B2Sprite一方面挂接了一个Sprite用于显示刚体,另外一方面有挂接了一个刚体对象m_b2Body, 所以它就起着一个桥梁的作用。
实现代码就不给出了。由于都是些细节性的方法,是用来写这个Demo的。大家全然能够依据自己须要来封装自己的B2Sprite版本号。
好了完毕了这两个类的封装。接下来就是完毕我们的Demo了。
我建立一个叫HelloWorldScene的Layer来承载这个游戏Demo。
#ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" #include "Box2D.h" #include "VisibleRect.h" #include "cocos-ext.h" #include "Box2dHandler.h" using namespace cocos2d; enum { kTagParentNode = 1, kTagBulletParentNode=2, kTagHandler = 3, kTagFloor = 100, kTagFloor2 = 101, kTagRedEnemy = 102, kTagBlueEnemy = 103, kTagYellowEnemy = 104, kTagBulletBase = 500, }; class HelloWorld : public cocos2d::Layer, public Box2dHandlerDelegate { public: // there‘s no ‘id‘ in cpp, so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); HelloWorld(); ~HelloWorld(); // Touch process bool onTouchBegan(Touch* touch, Event* pEvent); void onTouchEnded(Touch* touch, Event* pEvent); virtual void CollisionEvent(B2Sprite*, B2Sprite*); void HelloWorld::LaunchBomb(const b2Vec2& position, const b2Vec2& velocity); }; #endif // __HELLOWORLD_SCENE_H__
我将创建刚体的任务都放在构造函数中,CollisionEvent是处理碰撞的回调方法。LauchBomb是发射子弹的方法。
接下来看实现。
#include "HelloWorldScene.h" #define PTM_RATIO 32 USING_NS_CC; Scene* HelloWorld::createScene() { auto scene = Scene::create(); auto layer = new HelloWorld(); scene->addChild(layer); layer->release(); return scene; } HelloWorld::HelloWorld() { auto dispatcher = Director::getInstance()->getEventDispatcher(); auto touchListener = EventListenerTouchOneByOne::create(); touchListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this); //touchListener->onTouchMoved = CC_CALLBACK_2(MapLayer::onTouchMoved, this); touchListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this); dispatcher->addEventListenerWithSceneGraphPriority(touchListener, this); Sprite* bg = Sprite::create("bg3.png"); addChild(bg, -1); bg->setPosition(ccp( VisibleRect::center().x, VisibleRect::center().y)); Box2dHandler * handler = Box2dHandler::handler(); handler->setDelegate(this); handler->setTag(kTagHandler); this->addChild(handler); B2Sprite * floor = B2Sprite::create("floor.png"); floor->setTag(kTagFloor); floor->setPosition(300, 350); handler->addStaticBodyForSprite(floor); addChild(floor); B2Sprite * floor2 = B2Sprite::create("floor2.png"); floor2->setTag(kTagFloor2); floor2->setPosition(450, 250); handler->addStaticBodyForSprite(floor2); addChild(floor2); B2Sprite * redEnemy = B2Sprite::create("Zombie", 16, k_red); redEnemy->setTag(kTagRedEnemy); redEnemy->setPosition(442,500); handler->addBodyForSprite(redEnemy); addChild(redEnemy); B2Sprite * blueEnemy = B2Sprite::create("Zombie", 16, k_blue); blueEnemy->setTag(kTagBlueEnemy); blueEnemy->setPosition(310,500); handler->addBodyForSprite(blueEnemy); addChild(blueEnemy); B2Sprite * yellowEnemy = B2Sprite::create("Zombie", 16, k_yellow); yellowEnemy->setTag(kTagYellowEnemy); yellowEnemy->setPosition(330,500); handler->addBodyForSprite(yellowEnemy); addChild(yellowEnemy); } HelloWorld::~HelloWorld() { } void HelloWorld::LaunchBomb(const b2Vec2& position, const b2Vec2& velocity) { B2Sprite * sprite = B2Sprite::create("bullet.png"); sprite->setScale(0.3); sprite->setPosition(ccp(position.x, position.y)); Box2dHandler * handler = (Box2dHandler*)(this->getChildByTag(kTagHandler)); handler->addBodyForSprite(sprite, 1.0, 0.3, 0.8, circle); sprite->getB2Body()->SetLinearVelocity(velocity); sprite->getB2Body()->SetBullet(true); sprite->setTag(kTagBulletBase); addChild(sprite); } bool HelloWorld::onTouchBegan(Touch* touch, Event* pEvent) { return true; } void HelloWorld::onTouchEnded(Touch* touch, Event* pEvent) { Point p = touch->getLocation(); b2Vec2 target(p.normalize().x * 100, p.normalize().y*100); b2Vec2 v = target; b2Vec2 source(0, 0); LaunchBomb(source, v); } // a is bullet void HelloWorld::CollisionEvent(B2Sprite*a, B2Sprite*b) { if(a->getPositionX() < b->getPositionX()) b->getB2Body()->ApplyLinearImpulse(b2Vec2(100,0), b->getB2Body()->GetPosition(), true); else if(a->getPositionX() > b->getPositionX()) b->getB2Body()->ApplyLinearImpulse(b2Vec2(-100,0), b->getB2Body()->GetPosition(), true); }
HelloWorld::HelloWorld()中我放置了两个镜头刚体作为floor, 然后在上面放了几个敌人。当然,假设要做一个正式的游戏,关卡数据要在外面编辑好,然后读取进来。这里只写个Demo,就没有编辑关卡了。
void HelloWorld::LaunchBomb(const b2Vec2& position, const b2Vec2& velocity)创建了一个子弹刚体。这种方法在每次触摸屏幕都会触发。
void HelloWorld::CollisionEvent(B2Sprite*a, B2Sprite*b)碰撞检測回调方法,这里做的处理是: 假设子弹和敌人产生碰撞,假设敌人在子弹坐标,就给它一个水平向左的冲量,反之给它一个向右的冲量,让它掉下平台。
能够看到,真正的Demo代码是很少的。
接下来上图。
设计一颗子弹
好了,这就是本章的所有内容。源代码已经上传到群 216208142 空间,有须要的读者能够加群来获取。