讓子彈飛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的話,很容易看清楚這個類就是封裝了基本的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 = sprite;

	b2Body* spriteBody = m_world->CreateBody(&spriteBodyDef);
	sprite->setB2Body(spriteBody);
	this->addFixtureForSprite(sprite, density, friction, restitution, type);
}

void Box2dHandler::addStaticBodyForSprite(B2Sprite* sprite, double density)
{
	b2BodyDef spriteBodyDef;
	spriteBodyDef.type = b2_staticBody;
	spriteBodyDef.position.Set(sprite->getPosition().x / PTM_RATIO, sprite->getPosition().y / PTM_RATIO);
	//spriteBodyDef.userData = sprite;

	b2Body* spriteBody = m_world->CreateBody(&spriteBodyDef);
	sprite->setB2Body(spriteBody);
	this->addFixtureForSprite(sprite, density);
}

void Box2dHandler::update(float dt)
{
	m_world->Step(dt, 8, 8);
	std::vector<b2Body*> toDestory;
    for(b2Body* b = m_world->GetBodyList(); b; b = b->GetNext())
	{
		if(b->GetUserData() != NULL)
		{
			B2Sprite* sprite = static_cast<B2Sprite*>(b->GetUserData());
			b2Vec2 pos = b->GetPosition();
			float rotation = b->GetAngle() / 0.01745329252;
			sprite->setPosition(pos.x * PTM_RATIO, pos.y * PTM_RATIO);
			sprite->setRotation(rotation);
			if(b->GetPosition().y*PTM_RATIO<= -25)
			{
				toDestory.push_back(b);
			}
		}
	}

	if(toDestory.size()>0)
	{
		for(int i = 0; i < toDestory.size(); i++)
		{
			B2Sprite* sp=static_cast<B2Sprite*>( toDestory.at(i)->GetUserData());
			if(sp != NULL)
				sp->removeFromParentAndCleanup(true);
			m_world->DestroyBody(toDestory.at(i));
		}
		toDestory.clear();
	}

     this->dealCollision();
}

void Box2dHandler::BeginContact(b2Contact * contact)
{
	CCLog("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()
自定義的碰撞處理方法。


就這麼多。很簡單明瞭。接下來看看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 空間,有需要的讀者可以加羣來獲取。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章