最近在研究横版过关游戏,原来是用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);
//让hero与mainLayer的左右边界的相对位置在半个屏幕间不滚动地图,所以英雄与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.0的box2d引擎的话我们一般都是使用方法2作为移动镜头的方法(虽然说是移动镜头,但实际镜头没动,是相对移动,Layer移动了,镜头不动,就好比Layer不动,移动镜头)。但如果使用了box2d的话就不能使用上述移动Layer的方法了,由于Layer这个Layer世界跟物理引擎的物理世界是分开的,如果Layer世界的原点与物理世界的原点同在某一点处,绑定在hero图片上的PhysicsBody的坐标就完全跟hero图片的坐标一致,但如果只移动Layer世界,hero图片也跟着移动,但物理世界没有移动,由于PhysicsBody的坐标是物理世界上的坐标,所以就会出现hero图片与PhysicsBody的红色DebugDraw框脱离的现象。可惜PhysicsWorld暂时没有移动整个物理世界的方法,不然在移动Layer世界的同时也跟着移动物理世界就可以解决这一问题了。
但现在我们是必须想要用到Box2d,也必须要让hero图片与physicsBody的位置同步,该怎么办?真的是骑虎难下,幸好还有一种方法。就是用Node::setPosition,由于Node的setPosition里在设置它自己的位置之余也设置绑定在它身上的body的位置,所以可以让它们同步了。
由于上面只设置Layer的坐标,hero精灵坐标会改变而没有改变body的坐标,所以我们就不能设置Layer的坐标,而改直接设置hero精灵的坐标,即hero->setPosition,由于直接直接调用setPosition是会同时改变body在PhysicsWorld里的坐标的,所以就这种方法可行。
方法如下:
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);
//镜头从零点到目标点需要移动的距离(程序未运行时是范围,运行时x、y都确定,所以为距离),如果镜头可移动
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轴滚屏阶段
//因为map在cameraNeetMovePos.x<=viewSize.width/2和cameraNeetMovePos.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轴滚屏,所以相对于GameLayer的Y坐标就直接设置为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