游戏中地图上总有些过不去的沟沟坎坎\山河湖泊.这节我们就来讲讲在coco2dx下如何实现碰撞检测
首先我们基于的tmx地图,所以就要先在地图上做些手脚.
除了建立的基本地形层,我们在编辑器中又建立了一个碰撞层
并在湖面上放置了一些表示不能通过的碰撞障碍
为该图块中增加一个Collidable属性,值设置为true.这项属性值等下我会在程序中使用.下面会有介绍
为了能方便的查看地形层,我们将碰撞层设置成为半透明.地图处理完了,接下来就看我们怎么在代码里处理这些障碍物了.
首先我们建立了一个Map类,Map从CCTMXTiledMap类继承
#include "Map.h" using namespace cocos2d; Map* Map::initTileMap(const char *tmxfile) { Map *map = new Map; if(map->initWithTMXFile(tmxfile)) { CCTMXLayer *clayer = map->layerNamed("collision"); //通过层名字获得该层对象 clayer->setVisible(false); map->autorelease(); return map; } CC_SAFE_DELETE(map); return NULL; } //通过指定的坐标转换为地图块坐标 CCPoint Map::tilePosFromLocation(CCPoint l) { int x = l.x/this->getTileSize().width; int y = l.y/this->getTileSize().height; y = this->getMapSize().height - y; return ccp(x,y); } //判断该瓦片是否为障碍 bool Map::isTilePosBlocked(CCPoint l) { //判断当前块是否为碰撞块 CCPoint tilpos = tilePosFromLocation(l); //将带入的坐标转为块坐标 CCTMXLayer *clayer = this->layerNamed("collision"); //通过层名字获得该层对象 //clayer->setVisible(true); int tileGID = clayer->tileGIDAt(tilpos); //获得该块的GID标识别 if(tileGID!=0) { CCDictionary* properties = this->propertiesForGID(tileGID); if(strcmp(properties->valueForKey("Collidable")->getCString(),"true")==0) return true; } return false; }
tilePosFromLocation是将世界坐标转换为地图专用的图块坐标.在讲这个函数之前,我们先要讲几个概念.
首先是世界坐标,世界坐标的原点在画面左下角,Y轴向上,X轴向右
tilePosFromLocation函数的目的就是要把传入的世界坐标转换为地图图块上的坐标.以确定当前的世界坐标位置属于哪个图块范围内.
isTilePosBlocked传入的也是世界坐标点.用来确定当前位置对应图块是否为障碍物.我们可以看到,
第一步:函数中第一行CCPoint tilpos = tilePosFromLocation(l);就把带入的世界坐标转换为图块坐标.再通过CCTMXLayer的tileGIDAt函数获得了该图块的GID值.GID是全局标识符的意思.它是一个唯一的整数,地图中每层的每一块都会被赋予一个GID,这样可以保证每块都有自己的编号,用来确定自己的身份.当某个块为空时GID值为0.
第二步:获得了块的GID值,我们可以用CCTMXTileMap的propertiesForGID函数获得块的属性值.块的属性值设置方法我们在前面已经介绍.此处我们会利用valueForKey函数判断Collidable值是否为true,如果该条件成立则判断当前坐标对应的图块是一个障碍物.
接下来,我们来修改Hero类.由于碰撞检测的功能是需要每时每刻检测的,所以我们在类初始化的时候增加代码
//激活更新函数 schedule(schedule_selector(Hero::myUpdate,0.5f));
void Hero::myUpdate(float dt) { if(hState != STAND && map->isTilePosBlocked(this->getPosition())) collisionStand(); }
不过有个细节我还是要说一下,当人物走到障碍物时确实停止了,但此时人物已经处于障碍图块之中.所以当我们再命令英雄向其他方向移动时仍然会被判断为遇到障碍物再次执行collisionStand函数.导致直接的结果就是人物卡死在某个障碍图块的边缘,无论如何也走不动.所以我们要在人物碰到障碍物时让人物向相反的方向略微后退一点点.这样就能保证人物停止后不会处于障碍图块边缘上,下次再命令人物走动时也不会卡死在障碍图块的边缘了。
我们看collisionStand函数内容:
void Hero::collisionStand() { float x = this->getPositionX(); //获得hero的x坐标位置 float y = this->getPositionY(); //获得hero的y坐标位置 int offset = 2; //遇到障碍物后防止卡死进行微小移动的偏移量 //当停止时向人物背向方向略微移动2象素,防止人物在图块中卡死. if(hState==LEFT) this->setPosition(x+offset,y); else if(hState==UP) this->setPosition(x,y-offset); else if(hState==RIGHT) this->setPosition(x-offset,y); else if(hState==DOWN) this->setPosition(x,y+offset); hState = STAND; //设置人物状态为站立 sprite->stopAllActions(); //停止播放走动动画 this->stopAllActions(); //停止人物走动动作 }
我们来看看今天的成果吧: