您好,登錄后才能下訂單哦!
文章原版為英文版,地址鏈接在文章尾部給出。原文代碼版本為object-c版,本文代碼版本為C++版。對原文大部分內容進行了翻譯,并將對oc版的說明更改為C++版。文章cocos2d-x版本cocos2d-1.0.1-x-0.11.0。
如何用Box2D和cocos2d-x制作彈弓類游戲 第一部分
這是一篇由ios教程團隊成員Gustavo Ambrozio上傳的博客。一位擁有超過20年軟件開發(fā)經驗,超過3年ios開發(fā)經驗的軟件工程師,CodeCrop軟件創(chuàng)始人。
在這個教程系列中我們將會通過使用cocos2d-x和Box2D創(chuàng)建一個很COOL的彈弓類型游戲。
我們將使用Ray的可愛而富有天賦的老婆Vicki創(chuàng)作的彈弓,栗子,狗,貓和憤怒的松鼠素材來創(chuàng)建游戲。(素材我會在上傳附件)
在這個教程系列,你將學到:
怎么用旋轉關節(jié)(rotation joints)
怎么用連接關節(jié)(weld joints)
怎么讓視角跟隨拋射物
怎么根據碰撞檢測判斷力量來消除敵人
和很多其他的
這個教程系列假設你已經掌握了 Intro to Box2D with Cocos2D Tutorial: Bouncing Balls Tutorial或者已經掌握了相關知識。
教程中還會使用很多制作撞球游戲中的概念。
開始吧
新建HelloWorld項目,清空項目。記得選擇需要Box2d支持的cocos2d-x工程。聲明一個catapult類。和HelloWorld類除了名字全都一樣。
加入些精靈
首先我們先添加項目將用的資源。
現在我們來加入些不會被物理模擬的精靈。默認的CCSprite的錨點是中心,我將錨點改到了左下角為了更容易的放置它們。
在init方法中// add your codes below...下面添加代碼:
CCSprite *sprite = CCSprite::spriteWithFile("bg.png"); //背景圖
sprite->setAnchorPoint(CCPointZero);
this->addChild(sprite, -1);
CCSprite *sprite = CCSprite::spriteWithFile("catapult_base_2.png"); //投射器底部后面那塊
sprite->setAnchorPoint(CCPointZero);
sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT));
this->addChild(sprite, 0);
sprite = CCSprite::spriteWithFile("squirrel_1.png"); //左邊松鼠
sprite->setAnchorPoint(CCPointZero);
sprite->setPosition(CCPointMake(11.0, FLOOR_HEIGHT));
this->addChild(sprite, 0);
sprite = CCSprite::spriteWithFile("catapult_base_1.png"); //投射器底部前面那塊
sprite->setAnchorPoint(CCPointZero);
sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT));
this->addChild(sprite, 9);
sprite = CCSprite::spriteWithFile("squirrel_2.png"); //右邊松鼠
sprite->setAnchorPoint(CCPointZero);
sprite->setPosition(CCPointMake(240.0, FLOOR_HEIGHT));
this->addChild(sprite, 9);
sprite = CCSprite::spriteWithFile("fg.png"); //帶冰的地面
sprite->setAnchorPoint(CCPointZero);
this->addChild(sprite, 10);
你也許注意到了很多使用Y坐標的地方用了宏FLOOR_HEIGHT,但我們并未define它。
#define FLOOR_HEIGHT 62.0f
定義了這個宏之后,如果我們改變了地板高度,我們可以更加簡便的放置精靈。
上效果圖。
看起來不錯!
上面就是非物理模擬的部分。
增加彈弓臂
是時候給世界加些物理屬性了,接下來的代碼就是加世界邊框的模板式的代碼了,讓我們改變一點來描述我們的世界。
類聲明中添加:
private:
b2World* m_world;
b2Body* m_groundBody;
init方法尾部添加:
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
bool doSleep = true;
m_world = new b2World(gravity);
m_world->SetAllowSleeping(doSleep);
m_world->SetContinuousPhysics(true);
// Define the ground body.
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0, 0); // bottom-left corner
// Call the body factory which allocates memory for the ground body
// from a pool and creates the ground box shape (also from a pool).
// The body is also added to the world.
m_groundBody = m_world->CreateBody(&groundBodyDef);
默認是世界的尺寸是iphone屏幕尺寸。因為我們場景的寬度是世界寬度的2被。完成這個任務我們只需要讓寬度乘以1.5.
另外,由于我們世界的地板并不是在屏幕的底部,所以我們需要編寫相應的代碼。
邊界代碼:
b2EdgeShape groundBox;
// bottom
groundBox.Set(b2Vec2(0,FLOOR_HEIGHT/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,FLOOR_HEIGHT/PTM_RATIO));
m_groundBody->CreateFixture(&groundBox, 0);
// top
groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,screenSize.height/PTM_RATIO));
m_groundBody->CreateFixture(&groundBox, 0);
// left
groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(0,0));
m_groundBody->CreateFixture(&groundBox, 0);
// right
groundBox.Set(b2Vec2(screenSize.width*1.5f/PTM_RATIO,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*1.5f/PTM_RATIO,0));
m_groundBody->CreateFixture(&groundBox, 0);
說明:box2d某次更新后以前的SetAsEdge函數被刪除了,但是可以使用b2EdgeShape類型對象來生成邊界,函數名也變?yōu)镾et。
現在讓我們增加彈弓臂,首先增加物體(body)和夾具(fixture)的指針。打開HelloWorld.h把下面的代碼加入到類中。
private:
b2Fixture *m_armFixture;
b2Body *m_armBody;
進入到HelloWorld.cpp文件中的init函數的底部:
// Create the catapult's arm
CCSprite *arm = CCSprite::spriteWithFile("catapult_arm.png");
this->addChild(arm, 1);
b2BodyDef armBodyDef;
armBodyDef.type = b2_dynamicBody;
armBodyDef.linearDamping = 1;
armBodyDef.angularDamping = 1;
armBodyDef.position.Set(230.0f/PTM_RATIO, (FLOOR_HEIGHT+91.0f)/PTM_RATIO);
armBodyDef.userData = arm;
m_armBody = m_world->CreateBody(&armBodyDef);
b2PolygonShape armBox;
b2FixtureDef armBoxDef;
armBoxDef.shape = &armBox;
armBoxDef.density = 0.3F;
armBox.SetAsBox(11.0f/PTM_RATIO, 91.0f/PTM_RATIO);
m_armFixture = m_armBody->CreateFixture(&armBoxDef);
你如果看過之前的Box2D教程那么這些代碼對你而言應該很熟悉。
我們先讀取彈弓臂精靈并把它加入到層中。注意z軸索引。當我們向scene中加入靜態(tài)精靈時候我們使用Z軸索引。
讓我們的彈弓臂位于2塊彈弓底部之間看起來不錯!
類聲明中增加:
void tick(cocos2d::ccTime dt);
cpp文件增加:
void HelloWorld::tick(ccTime dt)
{
int velocityIterations = 8;
int positionIterations = 1;
m_world->Step(dt, velocityIterations, positionIterations);
//Iterate over the bodies in the physics world
for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetUserData() != NULL) {
//Synchronize the AtlasSprites position and rotation with the corresponding body
CCSprite* myActor = (CCSprite*)b->GetUserData();
myActor->setPosition( CCPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO) );
myActor->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()) );
}
}
}
在init方法尾部加入:
schedule(schedule_selector(Catapult::tick));
看到沒?我們并沒有設置精靈的位置,因為tick方法會更正精靈的位置到box2D物體的位置。
接下來我們創(chuàng)建box2d物體作為一個動態(tài)物體。userData屬性在這里很重要,因為正如我上段提到的,精靈會跟隨物體。
另外注意到坐標被設置到在FLOOR_HEIGHT之上。因為我們這里使用的坐標是物體的中心,我們不能用左下角在使用Box2d時候。
接下來就是創(chuàng)建物體物理特性的夾具,一個簡單的矩形。
我們設置物體夾具的大小比精靈尺寸小一點,因為精靈尺寸比實際彈弓臂圖案尺寸大一點。
在這幅圖中,黑色矩形框是精靈尺寸,紅色矩形框是夾具尺寸。
運行你會看到機器臂直立著。
旋轉關節(jié)
我們需要某種約束來限制投射器的轉動在一定角度內。借助關節(jié)(joints)你可以約束Box2D關聯物體運動.
有一種特殊的關節(jié)可以完美解決我們的問題——旋轉關節(jié)(revolute joint)。想象一個釘子將2個物體釘在一個特殊的點,但仍然允許他們轉動。
讓我們試試吧!回到HelloWorld.h在類中加入屬性:
b2RevoluteJoint *m_armJoint;
回到類實現文件在生成發(fā)射器臂之后加入下面的代碼:
b2RevoluteJointDef armJointDef;
armJointDef.Initialize(m_groundBody, m_armBody, b2Vec2(233.0f/PTM_RATIO, FLOOR_HEIGHT/PTM_RATIO));
armJointDef.enableMotor = true;
armJointDef.enableLimit = true;
armJointDef.motorSpeed = -10; //-1260;
armJointDef.lowerAngle = CC_DEGREES_TO_RADIANS(9);
armJointDef.upperAngle = CC_DEGREES_TO_RADIANS(75);
armJointDef.maxMotorTorque = 700;
m_armJoint = (b2RevoluteJoint*)m_world->CreateJoint(&armJointDef);
當我們創(chuàng)建關節(jié)時你不得不修改2個物體和連接點。你可能會想:“我們不應該把投射器臂連接到投射器底部嗎》”。在現實世界中,沒錯。
但是在Box2D中這可不是必要的。你可以這樣做但你不得不再為投射器底部創(chuàng)建另一個物體并增加了模擬的復雜性。
由于投射器底部在何時都是靜態(tài)的并且在Box2d中樞紐物體(hinge body)不必要在其他的任何物體中,我們可以只使用我們已擁有的大地物體(groundBody)。
角度限制外加馬達(motor)然我們的投射器更加像真實世界中的投射器。
你也許會注意到我們在關節(jié)上設置一個馬達激活,通過設置“enableMotor”,“motorSpeed”,和“maxMotorTorque”。
通過設置馬達速度為負,可以使投射器臂持續(xù)的順時針轉動。
然而,我們還需要通過設置”enableLimit“,”lowerAngle“,”upperAngle“激活關節(jié)。這讓關節(jié)活動范圍角度9到75°。這如我們所想的那樣模擬投射器運動。
然后我們?yōu)榱讼蚝罄瓌油渡淦鲗⒃黾恿硪粋€關節(jié)。當我們釋放這個力后馬達會讓投射器臂向前運動,更像真實的投射器啦!
馬達的速度值是每秒弧度值。沒錯,不是很直觀,我知道。我所做的就是不聽修改值直到獲得了我想的效果。你可以從小的值開始增加知道你獲得了期望的速度。最大馬達扭矩(maxMotorTorque)是馬達可達的最大扭矩。你可以改變這個值來看看物體的反應。那么你會清楚他的作用。
運行app你會看到投射器臂位置現在偏左了:
推動投射器臂吧!
好的,現在是時候移動這個投射器臂啦!為了完成這個任務我們將會使用鼠標關節(jié)(mouse joint)。如果你讀了雷的彈球游戲教程你就一定已經知道鼠標關節(jié)是什么了。
但你沒讀過,這里是Ray的定義:
“In Box2D, a mouse joint is used to make a body move toward a specified point.”
那就是我們正需要的,所以,然我們先聲明一個鼠標關節(jié)變量在類定義中:
private:
b2MouseJoint *m_mouseJoint;
public:
virtualvoid ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
virtualvoid ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
virtualvoid ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
接下來在類實現文件增加CCTouchesBegan方法:
void Catapult::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
{
if (m_mouseJoint != NULL) { return; }
CCTouch *touch = (CCTouch *)touches->anyObject();
CCPoint location = touch->locationInView(touch->view());
location = CCDirector::sharedDirector()->convertToGL(location);
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
if (locationWorld.x < m_armBody->GetWorldCenter().x + 150.0/PTM_RATIO)
{
b2MouseJointDef md;
md.bodyA = m_groundBody;
md.bodyB = m_armBody;
md.target = locationWorld;
md.maxForce = 2000;
m_mouseJoint = (b2MouseJoint *)m_world->CreateJoint(&md);
}
}
在init方法中調用tick方法代碼前:
m_mouseJoint = NULL;
又是引用Ray的話:
"When you set up a mouse joint, you have to give it two bodies. The first isn’t used, but
the convention is to use the ground body. The second is the body you want to move”.
當你創(chuàng)建一個鼠標關節(jié),你不得不給他2個物體,第一個實際上并不會被使用,但為了方便就把ground body賦給它吧,第二個是你想移動的物體。
目標就是我們想要用關節(jié)來移動物體。我們首先要將觸摸點左邊轉換成cocos2d-x坐標在轉換為Box2D坐標。我們只在觸摸在投射器臂物體左邊時才會創(chuàng)建關節(jié)。50像素的偏移讓我們可以觸摸投射器臂稍微偏右的位置。
最大力參數將決定應用在投射器臂上跟隨目標點移動的最大力。就我們而言,我們不得不確保它足夠強壯來抵消被應用與旋轉關節(jié)的馬達的扭矩。
In our case we have to make it strong enough to counteract the torque applied by the motor of the revolute joint.
如果你把這個值設置的太小,那么你將不能拉回投射器臂,因為它的扭矩過大。你可以減小我們的轉動關節(jié)的最大馬達扭矩(maxMotorTorque)或增大最大力(maxForce)參數。
為了確定什么值合適我建議你用下鼠標關節(jié)的最大力參數和旋轉關節(jié)的最大馬達扭矩參數。減小maxForce到500并試驗,你將看到你不能推動投射器臂。那么減小maxMotorTroque到1000你將看到你又可以推動它了。但讓我們先完成這些的實現。。
我們現在不得不實現CCTouchesMoved方法,這樣鼠標關節(jié)會跟隨你的觸摸:
void Catapult::ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
{
if (m_mouseJoint == NULL) { return; }
CCTouch *touch = (CCTouch *)touches->anyObject();
CCPoint location = touch->locationInView(touch->view());
location = CCDirector::sharedDirector()->convertToGL(location);
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
m_mouseJoint->SetTarget(locationWorld);
}
這個方法足夠簡單。它只是把屏幕坐標轉換為世界坐標,在將鼠標關節(jié)的目標點轉換為這個點。
為了完成它我們不得不通過銷毀鼠標關節(jié)釋放投射器臂。讓我們在CCTouchesEnded方法中實現它:
void Catapult::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
{
if (m_mouseJoint != NULL)
{
m_world->DestroyJoint(m_mouseJoint);
m_mouseJoint = NULL;
return;
}
}
很簡單!只是銷毀了關節(jié)清理了變量。試試看程序吧!用你的鼠標拉動投射器臂。當你放開左鍵時你會看到投射器臂很快的恢復到初始位置。
這真是太快了。正如你記得的,控制它的速度的,就是旋轉關節(jié)(revolute joint)的馬達速度(motorSpeed)和最大馬達扭矩(maxMotorTorque)。讓我們減小馬達速度來看看會發(fā)生什么。
回到init方法改小值。一個讓我的程序工作的不錯的值是-10.改變這個值然后你會看到速度是用來讓投射器看起來更真實的參數。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。