cocos2d 3.0 box2d 解決移動相機鏡頭(滾屏)問題的方法

pamxy發表於2014-05-25

最近在研究橫版過關遊戲,原來是用Rect框跟隨主角、怪物來實現碰撞檢測,但覺得不精確,所以想用box2d實現精確的碰撞檢測,但用上box2d後有一個比較嚴重的問題:英雄精靈的座標與PhysicsBody的座標不一樣了。

相機鏡頭(滾屏)原理詳解:

如果Layer是一張長方形的桌子,hero是站在桌子上的一隻螞蟻,我們的遊戲介面視窗是手持的一個DV攝像機,這時我們正通過攝像機的螢幕檢視螞蟻,螞蟻的位置剛好在桌面的左邊界線中間位置(設此位置為x零點位置),而我們長方形的攝像機螢幕的左邊界也正好對準桌面左邊界。此時螞蟻開始向右移動了,不一會螞蟻已經走到攝像機螢幕的中心位置,此時如果螞蟻繼續往右走,它就不會在螢幕的中心位置,但如果這時我們想讓螞蟻一直處於攝像機螢幕的中心位置,那麼我們可以右移動攝像機(方法1)或者左移動桌子(方法2)。

1.我們移動攝像機的步伐跟螞蟻移動的步伐一致,螞蟻一直向右行走,也一直處於螢幕的中心。當螞蟻行走到離右邊邊界只有半個相機螢幕寬度的位置時,螞蟻仍然在螢幕中心,而螢幕的右邊界也正好對準桌子的右邊界,由於我們不想拍攝桌子範圍外的景物,所以此時攝像機不再向右移動,而此時螞蟻仍向右移動,自然螞蟻就不處於攝像機螢幕的中心了,這時就跟開始時一樣,只有螞蟻在移動,但我們仍能拍攝得到。

 


2.移動桌子,這種方法比較費勁,我們一般不會這樣做。攝像機螢幕的左邊界對準桌子的左邊界,螞蟻向右開始移動,當我們在螢幕上看到螞蟻在螢幕的中間時,我們開始讓桌子向左移動,移動的步伐與螞蟻的一致,此時我們看到的是螞蟻仍在螢幕中間,當我們看到螢幕右邊界正好對準桌面的右邊界時,我們停止移動桌面了,因為我們不想拍攝到桌面以外的景物,此時螞蟻繼續向右移動,自然也離開螢幕中心,它在桌面上行走的動作我們仍然能拍攝到。

 

 

一般我們會選擇移動攝像機,即方法1,但如果相機不能移動怎麼辦?那唯有選擇方法一。那又有什麼情況不能移動攝像機呢?我們的遊戲介面視窗就正好是不能移動的(即不能移動攝像機),那麼了為不顯示背景圖片以外的背景,我們選擇了方法2,移動桌子,即移動Layer,幸好移動Layer沒移動桌子費勁。

背景圖與hero圖片都在Layer裡面。假設背景圖的錨點在左邊中間位置,hero的錨點座標在hero的腳下,即hero圖片的下面中間位置。遊戲剛初始化完畢,背景圖位置被設定在與視窗左邊座標一致的地方,即背景圖左邊與視窗左邊重合,而hero也正站在背景左邊中間位置。hero開始向右移動,移動到視窗中間位置,我們就開始讓Layer向左移動,移動的步伐跟hero一致,由於hero是在Layer裡面,所以會隨Layer一起向左移動,但此時他也正以相同的速度向右移動啊,由於運動抵消了,相對於螢幕中心(也即是我們的視點中心)是靜止的,所以我們仍然能看到hero在螢幕的中心。由於跟方法2一樣,所以不再重複,程式碼如下:

void GameLayer::update(float dt)

{

Layer::update(dt);

updatePosition(dt);

}

void GameLayer::updatePosition(float dt)

{

//英雄寬度、高度一半

float side=m_hero->getCenterToSide();

float bottom=m_hero->getCenterToBottom();

 

//map寬度

float tileMapWidth=m_backMap->getMapSize().width * m_backMap->getTileSize().width;

 

//限制英雄的移動範圍

float posX=GetMin(tileMapWidth - m_hero->getCenterToSide(),

GetMax(m_hero->getCenterToSide(),m_hero->getDesiredPosition().x));

float posY=GetMin(4* m_backMap->getTileSize().height+m_hero->getCenterToBottom(),

GetMax(m_hero->getCenterToBottom(),m_hero->getDesiredPosition().y));

 

m_hero->setPosition(ccp(posX,posY));

setViewPointCenter(m_hero->getPosition());

 

}

void GameLayer::setViewPointCenter(Point position)

{

//視窗大小

Size winSize=Director::getInstance()->getWinSize();

//地圖大小

Size mapSize=CCSizeMake(m_backMap->getMapSize().width * m_backMap->getTileSize().width,

m_backMap->getMapSize().height * m_backMap->getTileSize().height);

 

//heromainLayer的左右邊界的相對位置在半個螢幕間不滾動地圖,所以英雄與mainLayer的相對位置在[winSize.w/2,mapSize.w-winSize.w/2]的範圍才發生

//獲得鏡頭移動的範圍(即地圖會滾動的範圍)

float scrollX=GetMax(position.x,winSize.width/2);

scrollX=GetMin(scrollX, mapSize.width - winSize.width/2);

 

float scrollY=GetMax(position.y,winSize.height/2);

scrollY=GetMin(scrollY, mapSize.height - winSize.height/2);

 

//鏡頭移動(地圖滾動)時的座標範圍(在未執行是x,y是不確定值,所以算是範圍,當執行時x,y為定值,此時就是鏡頭相對於GameLayer世界移動的距離【如果鏡頭可以移動的話】)

Point cameraNeedMoveSize=ccp(scrollX,scrollY);

 

//鏡頭相對於Layer的座標位置

Point centerOfView=ccp(CENTER.x,CENTER.y);

 

//由於鏡頭是靜止的,所以移動Layer等同於移動鏡頭

Point viewPoint=ccpSub(centerOfView,cameraNeedMoveSize);

 

//設定Layer移動的目標座標

m_mainLayer->setPosition(viewPoint);

 

}

 

有問題的效果如圖:

http://my.csdn.net/my/album/detail/1767603

 

如果不使用cocos2d 3.0box2d引擎的話我們一般都是使用方法2作為移動鏡頭的方法(雖然說是移動鏡頭,但實際鏡頭沒動,是相對移動,Layer移動了,鏡頭不動,就好比Layer不動,移動鏡頭)。但如果使用了box2d的話就不能使用上述移動Layer的方法了,由於Layer這個Layer世界跟物理引擎的物理世界是分開的,如果Layer世界的原點與物理世界的原點同在某一點處,繫結在hero圖片上的PhysicsBody的座標就完全跟hero圖片的座標一致,但如果只移動Layer世界,hero圖片也跟著移動,但物理世界沒有移動,由於PhysicsBody的座標是物理世界上的座標,所以就會出現hero圖片與PhysicsBody的紅色DebugDraw框脫離的現象。可惜PhysicsWorld暫時沒有移動整個物理世界的方法,不然在移動Layer世界的同時也跟著移動物理世界就可以解決這一問題了。

但現在我們是必須想要用到Box2d,也必須要讓hero圖片與physicsBody的位置同步,該怎麼辦?真的是騎虎難下,幸好還有一種方法。就是用Node::setPosition,由於NodesetPosition裡在設定它自己的位置之餘也設定繫結在它身上的body的位置,所以可以讓它們同步了。

由於上面只設定Layer的座標,hero精靈座標會改變而沒有改變body的座標,所以我們就不能設定Layer的座標,而改直接設定hero精靈的座標,即hero->setPosition,由於直接直接呼叫setPosition是會同時改變bodyPhysicsWorld裡的座標的,所以就這種方法可行。

方法如下:

void GameLayer::update(float dt)

{

Layer::update(dt);

updatePosition(dt);

}

void GameLayer::updatePosition(float dt)

{

//獲得hero精靈寬度、高度的一半

float side=m_hero->getCenterToSide();

float bottom=m_hero->getCenterToBottom();

 

//獲得視窗大小

Size viewSize=Director::getInstance()->getVisibleSize();

//獲得背景地圖大小

Size mapSize=CCSizeMake(m_backMap->getMapSize().width * m_backMap->getTileSize().width,

m_backMap->getMapSize().height * m_backMap->getTileSize().height);

 

//限制英雄Y座標最高只能移動4格地圖

float map4GridH=m_backMap->getTileSize().height * 4;

 

//每幀移動的距離

Point subPos=m_hero->getVelocity() * dt;

//渴望到達的位置

Point desiredPos=ccpAdd(m_hero->getPosition(),subPos);

 

//限制英雄只能在視窗大小中移動

float heroActualX=GetMax(desiredPos.x,0);

heroActualX=GetMin(heroActualX,viewSize.width);

 

float heroActualY=GetMax(desiredPos.y,0);

heroActualY=GetMin(heroActualY,map4GridH);

 

m_hero->setPosition(ccp(heroActualX,heroActualY) );

 

//獲得英雄與地圖的相對位置(如果以地圖為靜止的且相對於世界座標、螢幕座標靜止,那麼此為英雄座標與世界座標原點的距離,如果想讓鏡頭跟隨這個距離,那麼鏡頭也應移動同樣的距離,所以也為相機需要移動的距離點)

Point cameraNeetMovePos=ccpSub(m_hero->getPosition(),m_backMap->getPosition() );

 

//從得到的鏡頭應移動距離設定給物體座標位置

setViewPointCenter(cameraNeetMovePos,subPos);

}

 

void GameLayer::setViewPointCenter(Point _carmNeetMovePos,Point _subPos)

{

//視窗大小

Size viewSize=Director::getInstance()->getVisibleSize();

//地圖大小

Size mapSize=CCSizeMake(

m_backMap->getTileSize().width * m_backMap->getMapSize().width,

m_backMap->getTileSize().height * m_backMap->getMapSize().height

);

 

//限定鏡頭移動的範圍(即限制滾動範圍)

float scrollX=GetMax(_carmNeetMovePos.x,viewSize.width/2);

scrollX=GetMin(scrollX,mapSize.width - viewSize.width/2);

 

float scrollY=GetMax(_carmNeetMovePos.y,viewSize.height/2);

scrollY=GetMin(scrollY,mapSize.height - viewSize.height/2);

 

//鏡頭從零點到目標點需要移動的距離(程式未執行時是範圍,執行時xy都確定,所以為距離),如果鏡頭可移動

Point cameraNeetMoveSize=ccp(scrollX,scrollY);

 

//現實中鏡頭不能移動,要設定個相對運動,讓地圖實行相反運動,就等同於鏡頭運動(運動是相對的)

Point staticCameraPos=ccp(viewSize.width/2,viewSize.height/2);

 

//獲得地圖需相對鏡頭移動的實際位置

//這句主要是根據鏡頭的移動範圍限制地圖的移動範圍

//cameraNeetMoveSize為鏡頭相對於GameLayer世界需移動的距離,即鏡頭需移動這個距離才能讓英雄處於中間位置,且這個相對於世界的移動距離受到不露出其他背景的安全限制,因為我們只關注地圖內的事情。

//ccpSub(staticCameraPos,cameraNeetMoveSize)就是獲得認為鏡頭為靜止物,地圖相對於鏡頭的安全移動距離中的實際位置

Point mapActualPos=ccpSub(staticCameraPos,cameraNeetMoveSize);

 

//x軸滾屏階段

//因為mapcameraNeetMovePos.x<=viewSize.width/2cameraNeetMovePos.x >=(mapSize.width-viewSize.width/2) 不移動的,由於已經限制了不移動,所以只需關心滾屏範圍。

if (cameraNeetMoveSize.x>viewSize.width/2 && cameraNeetMoveSize.x <(mapSize.width-viewSize.width/2) )

{

//map移動的步伐,傳進來的_subPos為英雄移動的步伐,map移動的步伐與hero的相反,所以乘於-1

Point minusSubPos=ccpMult(_subPos,-1);

 

//獲得map相對於GameLayer的實際位置

mapActualPos=ccpAdd(m_backMap->getPosition(),minusSubPos);

//由於map.height與視窗一樣大,map不需移動即不需要y軸滾屏,所以相對於GameLayerY座標就直接設定為0

mapActualPos=ccp(mapActualPos.x,0);

//要讓英雄處於螢幕中間,如果移動GameLayer的話,map會移動,hero也同時跟著移動,由於不能運動GameLayer,所以英雄需要左移map左移的步伐,以模擬移動整個GameLayer的運動

//因不需要滾縱座標的屏,所以hero也不會受到縱座標的滾屏的力

Point heroSubPos=ccp(minusSubPos.x,0);

m_hero->setPosition(ccpAdd(m_hero->getPosition(),heroSubPos));

}

 

m_backMap->setPosition(mapActualPos);

}

 

效果如圖:

http://my.csdn.net/my/album/detail/1767605

相關文章