cocos2dx-3.0 中的物理引擎Box2D使用(四)

~~~~我的生活,我的点点滴滴!!



             相信下面的这个图片大家一定不陌生,我们就以此为基础来继续说cocos2dx中box2d的使用,下面这个游戏中用到Box2D的地方有哪些了?

白色圆壶与红色圆壶碰撞及红色圆壶与四周的碰撞等都是的。





1、刚体与精灵父类 b2Sprite



我们创建一个Box2D中的刚体body与cocos2dx中精灵sprite一体的类b2Sprite,用来方便的管理物体,我们想象一下他不是一个普通的sprite了他

的外表由sprite表现,但是他的内心由Box2D来操控。


//b2Sprite.h

class b2Sprite : public Sprite
{
public:
	b2Sprite(GameLayer *game, int type);

	CC_SYNTHESIZE(b2Body*, _body, Body);
	CC_SYNTHESIZE(GameLayer*, _game, Game);
	CC_SYNTHESIZE(int, _type, Type);
	
	virtual void setSpritePosition(Point position);
	virtual void update(float dt);
	virtual void hide(void);
	virtual void reset(void);
	virtual float mag();
};

在b2Sprite.cpp中的两个核心方法。

//当你为一个精灵设置位置和旋转角度的时候,会同样改变它的b2Body的位置和旋转角度
void b2Sprite::setSpritePosition(Point position)
{
	setPosition(position);

	if(_body)
	{
		_body->SetTransform(b2Vec2(
			position.x / PTM_RATIO,
			position.y / PTM_RATIO),
			_body->GetAngle());
	}
}

//当精灵的b2Body根据碰撞发生位置上的偏移时,会改变它的精灵的位置和旋转角度
void b2Sprite::update(float dt)
{
	if(_body && isVisible())
	{
		setPositionX(_body->GetPosition().x * PTM_RATIO);
		setPositionY(_body->GetPosition().y * PTM_RATIO);
		setRotation(CC_RADIANS_TO_DEGREES(-1 * _body->GetAngle()));
	}
}

上面定义完b2Sprite类后,后面所有的需要使用的刚体的精灵都可以直接继承此类来轻松完成刚体与精灵的绑定。



2、玩家Player



玩家需要带有碰撞属性,因为他需要和红圆壶碰撞产生力使用其运动。

class Player : public b2Sprite
{
	PlayerState   m_state;

public:

	CC_SYNTHESIZE(CCPoint,_nextPosition,NextPosition);

	Player(GameLayer *game,int type, Point position);
	virtual ~Player(void);

	Rect rect();

	float radius();

	bool containsTouchLocation(Touch* touch);

	static Player* create(GameLayer *game,int type, Point position);

	virtual bool onTouchBegan(Touch* touch, Event* event);
	virtual void onTouchMoved(Touch* touch, Event* event);
	virtual void onTouchEnded(Touch* touch, Event* event);

	virtual void update(float dt);

	virtual void reset();

private:
	void initPlayer();
	Point _startPosition;
};

上面是在Player里面开启了以触摸,在主场景中没有开启,但是不知道为什么,我点击一次,会触发两次触摸事件!!!!!此问题还得继续探讨

一下不知道是不是只能在主场景中使用触摸。下面是initPlayer()函数中完成刚体绑定:

//初始化玩家
void Player::initPlayer()
{
	m_state = kPlayerStateUngrabbed;

	this->initWithFile("mallet.png");

	b2BodyDef bodyDef;
	//玩家肯定是动态物体,他需要运动,并且还要有碰撞
	bodyDef.type = b2_dynamicBody;

	//_body在父类b2Sprite中定义,继承过来
	_body = _game->getWorld()->CreateBody(&bodyDef);
	_body->SetSleepingAllowed(true);
	//设置物体的线性阻尼
	_body->SetLinearDamping(1.2);
	//设置物体角阻尼
	_body->SetAngularDamping(0.8);

	b2CircleShape circle;
	circle.m_radius = radius() / PTM_RATIO;

	b2FixtureDef fixtureDef;
	fixtureDef.shape = &circle;
	fixtureDef.density = 10;
	fixtureDef.restitution = 1;
	//fixtureDef.filter.maskBits = 0x0100;

	_body->CreateFixture(&fixtureDef);
	//绑定刚体与精灵对象
	_body->SetUserData(this);

	setSpritePosition(_nextPosition);

	//开启触摸
	auto touchListener = EventListenerTouchOneByOne::create();
	touchListener->onTouchBegan = CC_CALLBACK_2(Player::onTouchBegan, this);
	touchListener->onTouchMoved = CC_CALLBACK_2(Player::onTouchMoved, this);
	touchListener->onTouchEnded = CC_CALLBACK_2(Player::onTouchEnded, this);
	Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener, this);
}



3、球-红圆壶 Ball



红色圆壶基本上与玩家类似,所以这里设计一个父类b2Sprite来实现他们共同点,然后让其继承实现自己独特功能是复合c++模式的。

//Ball.h
class Ball : public b2Sprite
{

public:
	Ball(GameLayer *game,int type, Point position);

	~Ball(void);

	static Ball* create(GameLayer *game, int type, Point position);

	virtual void reset(void);

	virtual void update(float dt);

	float radius();

private:
	void initBall();

	Point _startPosition;

};

相应的我只给出核心的部分initBall()

//初始化ball
void Ball::initBall()
{
	this->initWithFile("puck.png");

	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;

	_body = _game->getWorld()->CreateBody(&bodyDef);
	_body->SetSleepingAllowed(true);
	_body->SetLinearDamping(1.2);
	_body->SetAngularDamping(0.8);

	b2CircleShape circle;
	circle.m_radius = radius() / PTM_RATIO;

	b2FixtureDef fixtureDef;
	fixtureDef.shape = &circle;
	fixtureDef.density = 5;
	fixtureDef.restitution = 0.7;
	//fixtureDef.filter.categoryBits = 0x0100;

	_body->CreateFixture(&fixtureDef);
	_body->SetUserData(this);

	setSpritePosition(_startPosition);

}



4、球门 Goal



球门也需要碰撞,这样才能知道是否球进了,但是他又比较特殊,他不能产生碰撞,不然球来了直接把球碰出去了,这样的球门无敌了,即要让球

能穿过球门,但是要能及时监听到球进来了,这样就需要设置isSensor=true;

//Goal.h
class Goal : public b2Sprite
{

public:
	Goal(GameLayer *game,int type,Point position);
	~Goal();

	static Goal* create(GameLayer *game, int type,Point position);

	void reset();

private:

	void initGoal();

	CCPoint _startPosition;

};

核心代码initGoal()

//初始化球门
void Goal::initGoal()
{
	this->initWithFile("tansparent.png");

	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;

	_body = _game->getWorld()->CreateBody(&bodyDef);

	//球门定义为一个四边形
	b2PolygonShape polygon;
	polygon.SetAsBox(GOAL_WIDTH / 2 / PTM_RATIO,10 / PTM_RATIO);

	b2FixtureDef fixtureDef;
	fixtureDef.shape = &polygon;
	//不参加碰撞,但是要监听碰撞事件,所以要把isSensor设置成true
	fixtureDef.isSensor = true;

	_body->CreateFixture(&fixtureDef);
	_body->SetUserData(this);
	setSpritePosition(_startPosition);
}

仔细看注释,会发现亮点。



5、碰撞检测



上一篇文章就有描述到碰撞检测,所以这里我们只简单的列出来代码,并且这次的检测会简单,我们换种方式来表达:

#include "Box2D/Box2D.h"

//依然需要继承b2ContactListener
class CollisionListener : public b2ContactListener
{
public:
	virtual void BeginContact(b2Contact* contact);

	//virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);
};

我们只需要知道他们开始碰撞,其他的不用管。

void CollisionListener::BeginContact(b2Contact* contact)
{
	b2Body *bodyA = contact->GetFixtureA()->GetBody();
	b2Body *bodyB = contact->GetFixtureB()->GetBody();

	b2Sprite *spriteA = (b2Sprite *)bodyA->GetUserData();
	b2Sprite *spriteB = (b2Sprite *)bodyB->GetUserData();

	if(spriteA && spriteB)
	{
		if(spriteA->getType() == kGoalTop && spriteB->getType() == kBall)
		{
			spriteB->getGame()->setScore(kPlayer1GetScore);
		}
		else if(spriteB->getType() == kGoalTop && spriteA->getType() == kBall)
		{
			spriteB->getGame()->setScore(kPlayer1GetScore);
		}
		else if(spriteA->getType() == kGoalBottom && spriteB->getType() == kBall)
		{
			spriteB->getGame()->setScore(kPlayer2GetScore);
		}
		else if(spriteB->getType() == kGoalBottom && spriteA->getType() == kBall)
		{
			spriteB->getGame()->setScore(kPlayer2GetScore);
		}
	}
}



6、场景处理 GameLayer



终于回到主控了,在这里我们将对上面所有列到的类作出相应的控制,上一文章中已经讲过使用Box2D的基本流程,这里我们不重复,我们只上代

码,代码中会有注释,看完就一目了然了。

//初始化场景中物理世界
void GameLayer::initPhysics()
{
	b2Vec2 gravity;
	//我们是在一个平面上,所以只需要将重力加速度设置为0
	gravity.Set(0.0f, 0.0f);
	_world = new b2World(gravity);

	_world->SetAllowSleeping(true);
	_world->SetContinuousPhysics(true);
	//b2ContactListener,这里可以自定义处理物体碰撞发生的事情
	//比如球碰到球门,我们希望重置游戏。
	_collisionListener = new CollisionListener();
	_world->SetContactListener(_collisionListener);

	//显示轮廓
	m_debugDraw = new GLESDebugDraw(PTM_RATIO);
	_world->SetDebugDraw(m_debugDraw);

	uint32 flags = 0;
	flags += b2Draw::e_shapeBit;
	m_debugDraw->SetFlags(flags);

	//玩家1
	Point player1Position = Point(_screenSize.width * 0.5, 80);
	_player1 =(Player*) Player::create(this, kPlayer1Tag, player1Position);
	this->addChild(_player1, 0, kPlayer1Tag);

	//玩家2
	Point player2Position = Point(_screenSize.width * 0.5, _screenSize.height -  80);
	_player2 =(Player*) Player::create(this, kPlayer2Tag, player2Position);
	this->addChild(_player2, 0, kPlayer2Tag);

	//红色圆壶
	Point ballPosition = Point(_screenSize.width * 0.5, _screenSize.height * 2 / 5);
	_ball = Ball::create(this, kBall, ballPosition);
	this->addChild(_ball);

	//玩家1的球门
	Point goalTopPosition = Point(_screenSize.width * 0.5, _screenSize.height + _ball->radius() * 2);
	_goalTop = Goal::create(this, kGoalTop, goalTopPosition);
	this->addChild(_goalTop);

	//玩家2的球门
	Point goalBottomPosition = Point(_screenSize.width * 0.5,0 - _ball->radius() * 2);
	_goalBottom = Goal::create(this, kGoalBottom, goalBottomPosition);
	this->addChild(_goalBottom);


	//设置四周的边框
	b2BodyDef tableBodyDef;
	tableBodyDef.position.Set(0, 0);
	b2Body *tableBody = _world->CreateBody(&tableBodyDef);

	b2EdgeShape tableBox;
	//bottom
	tableBox.Set(b2Vec2(0.0f, 0.0f),
		b2Vec2((_screenSize.width / 2 - GOAL_WIDTH / 2) / PTM_RATIO,0.0f));
	tableBody->CreateFixture(&tableBox, 0);

	tableBox.Set(b2Vec2((_screenSize.width - (_screenSize.width / 2 - GOAL_WIDTH / 2)) / PTM_RATIO, 0.0f),
		b2Vec2(_screenSize.width / PTM_RATIO,0.0f));
	tableBody->CreateFixture(&tableBox, 0);

	//top
	tableBox.Set(b2Vec2(0.0f , _screenSize.height / PTM_RATIO),
		b2Vec2((_screenSize.width / 2 - GOAL_WIDTH / 2) / PTM_RATIO, _screenSize.height / PTM_RATIO));
	tableBody->CreateFixture(&tableBox,0);

	tableBox.Set(b2Vec2((_screenSize.width - (_screenSize.width / 2 - GOAL_WIDTH / 2)) / PTM_RATIO , _screenSize.height / PTM_RATIO),
		b2Vec2(_screenSize.width / PTM_RATIO, _screenSize.height / PTM_RATIO));
	tableBody->CreateFixture(&tableBox,0);

	//left
	tableBox.Set(b2Vec2(0.0f, 0.0f),b2Vec2(0.0f,_screenSize.height / PTM_RATIO));
	tableBody->CreateFixture(&tableBox,0);
	
	//right
	tableBox.Set(b2Vec2(_screenSize.width / PTM_RATIO, 0.0f),
		b2Vec2(_screenSize.width / PTM_RATIO, _screenSize.height / PTM_RATIO));
	tableBody->CreateFixture(&tableBox, 0);
	
	//	
}

有人会问,要使用Box2D不是要不停的调用Step()里,在哪了?当然是在update()里面:

void GameLayer::update(float delta)
{
	//调用Box2D的刷新函数
	_world->Step(delta, 10, 10);
	//调用各个对象的update()函数
	_ball->update(delta);
	_player1->update(delta);
	_player2->update(delta);
	
	if(getScore() == kPlayer1GetScore)
	{
		this->playerScore(1);
		setScore(kNoneGetScore);
	}
	else if(getScore() == kPlayer2GetScore)
	{
		this->playerScore(2);
		setScore(kNoneGetScore);
	}
}

到此简单的Box2D在cocos2dx中的使用就完成了。Box2D里面的内容才说了点皮毛,去看Box2D的官方例子Testbed里面还有好多东西我这没有涉及到,

或者看cocos2dx的官方例子cpp-test里面也有好多复杂的例子,并且我自己也不太懂,等以后做项目时运用到在去学吧。后续开始opengles的学习

了,他将和box2d交叉学习,因为box2d里面的绘图也是用的opengles。




你可能感兴趣的:(cocos2dx-3.0 中的物理引擎Box2D使用(四))