一、概念
tileMap是一个开源的、跨平台的地图制作工具,地图存储为Tmx格式,而cocos则提供对tileMap的原生支持。
相关概念网上资料很多,如: cocos2dx[3.4](25)——瓦片地图TiledMap,推荐初学者先去了解一下,这里主要记录制作过程。
二、素材
马里奥网络相关素材很多,不过基本都是低分辨率下的,要想手机中有比较好的效果,基本都需要放大。除非自己弄个高分辨率的。这里贴一下我用到的两张素材,如下:
三、tileMap制作
到官网下载tileMap工具,然后开始制作流程:
- 新建地图,其中块大小16*16像素,是根据素材选择的。Tile layer format推荐使用压缩,可以大幅减小地图文件大小。Tile render order——Right down,表示地图文件会先从最顶一行,从左往右表述,直到最底部。
2.默认会建立一个图层,我们将这个图层命名为walls,用于存放一些不可移动、不可穿越的墙等元素。
3.导入图块。图块其实就是创建地图的砖头,一般是将这些砖头放入同一张图中,tileMap会在导入时,按照我们选择的素材块大小帮我们切割。
导入后如图:
4.然后就可以在中间的编辑器中用这些图块来创建地图了,注意使用上面的工具栏:
四、创建遮挡
地图制作出来,就可以在cocos中加载使用。不过现在的程度,和加载一张普通图片没有任何区别,所以我们还需要给地图加上其他属性,如墙——不可穿越、怪物——不可碰撞等等。怪物属性当前还没有用到,后面再更新。墙的实现尝试了两种方式,下面都讲一下:
1. 使用块属性
所有的块都可以添加属性,然后在代码中获取使用。这种方式常被用做怪物、金币等对象的实现。最开始我尝试也使用这种方式来实现墙,但是效果不太好,后来换用了第二种方式。使用方法如下:
void MarioScene::initMap(){ //给所有的墙加刚体属性 //遍历所有层 for(auto &object : _map->getChildren()){ auto layer = static_cast<TMXLayer*>(object); if(layer){ for(int x = 0;x < layer->getLayerSize().width;++x){ for (int y = 0; y < layer->getLayerSize().height; ++y) { //遍历所有tiles auto tile = layer->getTileAt(Point(x,y)); if(tile){ tile->setAnchorPoint(Vec2(0.5,0.5)); int gid = layer->getTileGIDAt(Point(x,y)); auto properties = _map->getPropertiesForGID(gid); //如果是墙属性,则添加Fixture,便于后续碰撞处理 if(!properties.isNull() && properties.asValueMap().find("wall") != properties.asValueMap().end()){ tile->setName("wall"); tile->setTag(CONTACT_TARGET); addBodyToWorld(tile,b2_staticBody,2); } } } } } } }
缺点:
每个小块单独产生一个刚体,增加运算量,且实际测试中,马里奥在小刚体之间滑动会有意外效果,如被弹起、阻挡等。
2.使用对象层
这个方法来源于文章:【COCOS2DX-BOX2D游戏开发之三】 读取tiledmap的tmx阻挡,我这里只是修改为3.9版本可用。且其中有一步增加对polyline的解析支持,在3.9版本中,官方已经添加,不过椭圆还是没有的,需要自己修改代码。
- TileMap中添加对象层,命名为pyhsics
bool MarioScene::createPhysical(float scale) { b2BodyDef body_def; body_def.type = b2_staticBody; body_def.position.SetZero(); _pyhsicalBody = _b2World->CreateBody(&body_def); // 找出阻挡区域所在的层 TMXObjectGroup* group = _map->getObjectGroup("physics"); auto objects = group->getObjects(); for(const auto v :objects) { auto dict = v.asValueMap(); if (dict.size() == 0) continue; b2FixtureDef fixture_def; // 读取所有形状的起始点 float x = dict["x"].asFloat() * scale; float y = dict["y"].asFloat() * scale; b2Shape* shape = NULL; //多边形 if (dict.find("polygonPoints") != dict.end()) { auto polygon_points = dict["polygonPoints"].asValueVector(); std::vector<b2Vec2> points; // 必须将所有读取的定点逆向,因为翻转y之后,三角形定点的顺序已经逆序了,构造b2PolygonShape会crash int c =polygon_points.size(); points.resize(c); c--; for(auto obj : polygon_points) { // 相对于起始点的偏移 float offx = obj.asValueMap()["x"].asFloat() * scale; float offy = obj.asValueMap()["y"].asFloat() * scale; points[c--] = (b2Vec2((x + offx) / PTM_RATIO,(y-offy) / PTM_RATIO)); } b2PolygonShape *ps = new b2PolygonShape(); ps->Set(&points[0],points.size()); fixture_def.shape = ps; shape = ps; } else if (dict.find("polylinePoints") != dict.end()){ auto polyline_points = dict["polylinePoints"].asValueVector(); std::vector<b2Vec2> points; for(auto obj : polyline_points) { float offx = obj.asValueMap()["x"].asFloat() * scale; float offy = obj.asValueMap()["y"].asFloat() * scale; points.push_back(b2Vec2((x + offx) / PTM_RATIO,(y-offy) / PTM_RATIO)); } b2ChainShape *ps = new b2ChainShape(); ps->CreateChain(&points[0],points.size()); fixture_def.shape = ps; shape = ps; } fixture_def.density = 1.0; fixture_def.friction = 0; fixture_def.restitution = 0; _pyhsicalBody->CreateFixture(&fixture_def); if (shape) { delete shape; shape = NULL; } } return true; }