前面制作了地图,现在就可以在工程中使用了。现在只实现了遮挡与碰撞,后续再实现点击屏幕移动、寻路算法、npc交互等。
实现遮挡与碰撞,都需要解决一个核心问题——当前角色到底在哪一块瓦片上,也就是cocos坐标如何转化为TileMap瓦片坐标,这个问题搞了n久,最后实现方法还是很麻烦,如果有人知道有更好的方式,请联系我^_^
一、坐标转换
参考:
Staggered Isometric Map: Calculate map coordinates for point on screen
Picking Tile in staggered isometric map
其原理就在第一篇回答中,不过其最后一步转换是flash中通过取色来完成的,cocos中无法使用,就使用第二篇文章中的利用简单二元方程坐标系来判断点在线上还是线下来完成。
简单说一下原理就是:
- 如下图,将交错地图看做两个矩形网格覆盖在一起:
- 通过地图上一点的cocos坐标,可以知道点是在哪一个小的交叉矩形中:
- 通过判断点在线的上面还是在线的下面,来判断是在哪一个菱形中:
具体上代码:
- 1.小矩形中,两点确定一条直线y=ax+b,通过判断cx在线上对应点与cy的关系,来判断点与线的关系
//小矩形中,两点确定一条直线y=ax+b,通过判断cx在线上对应点与cy的关系,来判断点与线的关系
//return:0:线中,1:线下面,-1:线上面
int Map45Scene::getPosOfLine(float ax,float ay,float bx,float by,float cx,float cy){
//计算斜率
float slope = (by - ay) / (bx - ax);
//计算截距
float yIntercept = ay - ax * slope;
//计算cx对应的线上的解
float cSolution = cx * slope + yIntercept;
//通过判断解与cy的关系,判断在线上还是线下
if(cy < cSolution){
return -1;
}else if(cy > cSolution){
return 1;
}else{
return 0;
}
}
- 2.cocos坐标转换为交错地图坐标
Vec2 Map45Scene::getTilePosAtPos(const Vec2 &pos){
//pos应该转换为相对地图的坐标
auto mapPos = _map->convertToNodeSpaceAR(pos);
//auto mapPos = pos;
auto tileSize = _map->getTileSize();
auto mapSize = _map->getMapSize();
//将其转换为左上角坐标系
mapPos.y = mapSize.height * tileSize.height / 2 + tileSize.height / 2 - mapPos.y;
//取到小矩形的位置
int sx = int(mapPos.x / tileSize.width * 2);
int sy = int(mapPos.y / tileSize.height * 2);
int tx,ty;
//判断小矩形中线的位置
if(sx %2 == sy %2){
//如果线斜率是大于0
//取线上的两个点
float ax = sx * tileSize.width;
float ay = (sy + 1) * tileSize.height;
float bx = (sx + 1) * tileSize.width;
float by = sy * tileSize.height;
if(getPosOfLine(ax,ay,bx,by,mapPos.x,mapPos.y) < 0){
//在线上面
if(sy % 2 == 1){
//偶数行上
tx = int(mapPos.x / tileSize.width);
ty = int(mapPos.y / tileSize.height) * 2;
}else{
//奇数行上
tx = int((mapPos.x - tileSize.width / 2) / tileSize.width);
ty = int((mapPos.y - tileSize.height / 2) / tileSize.height) * 2 + 1;
}
}else if(getPosOfLine(ax,mapPos.y) >= 0){
//线下面或正好在线上
if(sy % 2 == 1){
//奇数行上
tx = int((mapPos.x - tileSize.width / 2) / tileSize.width);
ty = int((mapPos.y - tileSize.height / 2) / tileSize.height) * 2 + 1;
}else{
//偶数行上
tx = int(mapPos.x / tileSize.width);
ty = int(mapPos.y / tileSize.height) * 2;
}
}
}else{
//线斜率小于0
float ax = sx * tileSize.width;
float ay = sy * tileSize.height;
float bx = (sx + 1) * tileSize.width;
float by = (sy + 1) * tileSize.height;
if(getPosOfLine(ax,mapPos.y) < 0){
if(sy % 2 == 0){
//奇数行上
tx = int((mapPos.x - tileSize.width / 2) / tileSize.width);
ty = int((mapPos.y - tileSize.height / 2) / tileSize.height) * 2 + 1;
}else{
tx = int(mapPos.x / tileSize.width);
ty = int(mapPos.y / tileSize.height) * 2;
}
}else{
if(sy % 2 == 0){
//偶数行上
tx = int(mapPos.x / tileSize.width);
ty = int(mapPos.y / tileSize.height) * 2;
}else{
tx = int((mapPos.x - tileSize.width / 2) / tileSize.width);
ty = int((mapPos.y - tileSize.height / 2) / tileSize.height) * 2 + 1;
}
}
}
return Vec2(tx,ty);
}
下面看取到TileMap坐标后的应用
二、碰撞检测
前面制作地图时,我们加了一个blocks用于画玩家不可以穿越的块,我们现在可以取到一个cocos坐标所对应的地图瓦片坐标,这个碰撞功能的实现就很简单了:
- 我们在update时,取得更新后的玩家位置;
- 将位置转化为瓦片坐标;
- 取得blocks层中对应坐标的块,如果能取到,说明这个块是不可以穿越的,则取消移动,否则进行移动动作。
代码如下:
void Map45Scene::dealPlayerPos(float dt){
//玩家移动速度
float speed = MAP45_PLAYER_SPEED;
//两个方向的速度增量
float xDelta = speed * dt;
float yDelta = xDelta * _map->getTileSize().height / _map->getTileSize().width;
Vec2 pos;
//确定移动方向
if(_status == BTN_DOWN){
pos.x -= xDelta;
pos.y -= yDelta;
}else if(_status == BTN_UP){
pos.x += xDelta;
pos.y += yDelta;
}else if(_status == BTN_LEFT){
pos.x -= xDelta;
pos.y += yDelta;
}else if(_status == BTN_RIGHT){
pos.x += xDelta;
pos.y -= yDelta;
}
auto newPos = _normal->getPosition() + pos;
auto tile = getTileAtPosition(newPos,_map->getLayer("blocks"));
if(tile) {
//如果有阻挡,不移动
return;
}
//地图移动代替角色移动
_map->setPosition(_map->getPosition() - pos);
_normal->setPosition(_normal->getPosition() + pos);
_anim->setPosition(_normal->getPosition() + pos);
}
三、遮挡
我们虽然用45度斜角制作了一个2.5D的世界,看起来有了点立体感,但人物即使位于树、墙等的后面,还是会遮挡掉前面的物体,这样一下子就不真实了,所以我们需要动态修改玩家的渲染层级,来让玩家位于树前面遮挡树,树后面则被树遮挡。
1.为瓦片地图添加不同的渲染层级,我们需要在制作地图时,为图层添加OpenGl的Z order属性
为ground层添加属性:cc_vertexz=-500(我测试使用-1000层就不知道显示到哪去了,感觉是透视后太小导致,有时间研究下是为什么),这是为了玩家永远位于这个层的上面
为objects层添加属性:cc_vertexz=automatic,cc_alpha_func=0.5,这样添加后,cocos就会自动为这个层的瓦片分配动态的Z order属性值,算法为:pos.y - _layerSize.height,如下图黑色数字所示:
Director::getInstance()->setProjection(cocos2d::Director::Projection::_2D);
Director::getInstance()->setDepthTest(true);
3.我们知道了z order的算法,又可以根据玩家位置知道玩家位置的瓦片地图坐标,就可以直接动态设置玩家的vertexz属性,达到我们的目的:
void Map45Scene::updatePlayer(){
auto pos = getTilePosAtPos(_normal->getPosition());
auto mapSize = _map->getMapSize();
//设置玩家的vertexz属性
_normal->setPositionZ( pos.y - mapSize.height);
_anim->setPositionZ( pos.y - mapSize.height);
}
四、结语
其他实现后续再补充。