cocos2d 3.0 box2d 解决移动相机镜头(滚屏)问题的方法

最近在研究横版过关游戏,原来是用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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值