cocos2dx - tmx地图分层移动处理
接上一节内容:cocos2dx - 节点管理
在cocos2dx文档中有简单的介绍及使用。详情可以看:http://www.cocos2d-x.org/docs/manual/framework/native/v2/graphic/tiled-map/zh
一、FastTMXTiledMap & TMXTiledMap选择
在cocos2dx有2种实现加载Tmx地图的方法,分别是FastTmxTiledMap和TmxTiledMap。
主要区别:
FastTmxTiledMap 的绘制层TMXLayer继承Node节点,直接利用opengl 的索引(indices)一次绘制所有的格子纹理。
TmxTiledMap 的绘制层TMXLayer则通过继承 SpriteBatchNode节点,利用cocos2dx中封装好的批量绘制图片节点的功能实现一次绘制。
性能区别:
FastTmxTiledMap 在绘制效率上相对于 TmxTiledMap 有显著提高,因为SpriteBatchNode实际流程是创建了批量的Node,通过SpriteBatchNode来管理这些Node的索引及统一绘制调用,
这样相对于一个FastTmxTiledMap中一个Node的调用多了顶点的消耗及内存的消耗。
GL verts 和 GL calls 对比
(FastTmxTiledMap) (TmxTiledMap)
通过对比,可以看到同样的效果,绘制回调次数一致,但是顶点数 FastTmxTiledMap 仅占 TmxTileMap的 1/3多一点。
二、实际应用
功能: 实现通用的循环地图,同时让地图分层同屏移动。
首先,需要设计一个结构体SMapStruct来管理同一层的地图实现循环,同时利用一个map来管理不同层级的SMapStruct。
class CMapScreen; // 实际显示移动地图的管理 // 管理同一层级的地图 struct SMapStruct { SMapStruct() :nIdx(0), nLastIdx(-1){}; size_t nIdx; // 当前地图索引 size_t nLastIdx; // 上一层的索引 std::vector<int> vCircle; // 循环索引列表 std::vector<CMapScreen*> vScreen; // 实际地图列表 };
std::map<int, SMapStruct> m_mMapList; // 层级到SMapStruct列表
这里的索引可以指向csv读取出来的 SMapConfig 配置
// 地图配置 struct SMapConfig { int nID; // 索引 std::string strFile; // 地图资源 int nIdx; // 层级 int nSpeed; // 地图移动速度 px/s bool bMoveY; // 是否Y移动 };
这样在游戏开始,对配置的地图信息进行加载,存到 m_mMapList列表中。
// 地图设置 { for (size_t i = 0; i < m_vMap.size(); i++) { SMapConfig* pConfig = m_vMap[i]; if (pConfig) { SMapStruct& sMapStruct = m_mMapList[pConfig->nIdx]; sMapStruct.vCircle.push_back(i); } } }
这样m_mMapList列表中就存了当前游戏每一个层级需要的地图列表。然后在update对其进行更新显示,如下:
void CMapMgr::update(float dt) { auto it = m_mMapList.begin(); while (it != m_mMapList.end()) { SMapStruct& sStruct = it->second; // 判断当前层级地图列表是否存在 if (sStruct.vCircle.size() <= sStruct.nIdx) { CCLOG("地图列表更新错误!!"); break; } // 获取上一张显示的地图 if (CMapScreen* pMap = GetScreen(sStruct, sStruct.nLastIdx)) { pMap->update(dt);// 更新坐标 // 显示出视口 if (pMap->IsOutViewPort()) { pMap->Sleep(); //隐藏该地图 sStruct.nLastIdx = -1; } } // 获取当前显示的地图 if (CMapScreen* pMap = GetScreen(sStruct, sStruct.nIdx)) { pMap->update(dt); // 判断是否需要显示下一张地图 if (pMap->IsNeedNextScreen()) { sStruct.nLastIdx = sStruct.nIdx; // 显示出视口 if (pMap->IsOutViewPort()) { pMap->Sleep(); sStruct.nLastIdx = -1; } if (++sStruct.nIdx >= sStruct.vCircle.size()) { sStruct.nIdx = 0; } if (CMapScreen*pNextMap = GetScreen(sStruct, sStruct.nIdx)) { // 显示新地图 pNextMap->Active(pMap->GetConnetPoint()); } } } ++it; } }
以上实现了地图分层移动的管理,可以实现不同层级不同速度移动,或者静止等,也可以往不同方向移动。
SMapStruct类的实现,不再这里详细描述了。主要实现以下方法:
// 每个地图层 class CMapScreen : public Node { public: static CMapScreen* create(const SMapConfig* pConfig); void Release(); void Active(const Vec2& pt); // 启用update循环移动 void Sleep(); // 隐藏停止update bool IsNeedNextScreen() const; Vec2 GetConnetPoint() const; // 获取连接点 void update(float dt); bool IsOutViewPort() const ; // 出了视口an }
三、黑缝处理
Tmx地图在cocos2dx移动的时候会偶尔出现黑线的现象。主要原因是底层顶点坐标取到了纹理之外导致颜色值取不到。
解决办法:
1、移动的偏移坐标用整数。
2、衔接处重叠1个像素。
3、采用Director::Projection::_2D的方式绘制游戏。
1、如下:
m_nDelta+= m_pConfig->nSpeed* dt; // 取整数 int nDelta = int(m_nDelta); m_nDelta -= nDelta; // 移动对应的距离 m_pConfig->bMoveY ? setPositionY(getPositionY() + nDelta) : setPositionX(getPositionX() + nDelta);
2、代码如下:
Vec2 CMapScreen::GetConnetPoint() const
{
Vec2 pt;
if (!m_pConfig)
{
return pt;
}
Vec2 origin = Director::getInstance()->getVisibleOrigin();
if (m_pConfig->bMoveY)
{
pt = m_pConfig->nSpeed>0 ? getPosition() : Vec2(getPositionX(), getPositionY() + getContentSize().height);
pt.y = m_pConfig->nSpeed > 0 ? pt.y + 1: pt.y - 1; //重叠1个像素 防止黑缝出现
}
else
{
pt = m_pConfig->nSpeed>0 ? getPosition() : Vec2(getPositionX() + getContentSize().width, getPositionY());
pt.x = m_pConfig->nSpeed > 0 ? pt.x + 1 : pt.x - 1;//重叠1个像素 防止黑缝出现
}
return pt;
}
3、代码如下:
director->setProjection(Director::Projection::_2D);
另,3在cocos2dx-3.9中用2D方式绘制FastTmxMap有bug,需要回溯之前版本的配置如下:
void TMXLayer::draw(Renderer *renderer, const Mat4& transform, uint32_t flags) { updateTotalQuads(); //正交方式处理纹理,防止地图切换黑线 if (Director::getInstance()->getProjection() == Director::Projection::_2D) { if (flags != 0 || _dirty || _quadsDirty) { Size s = Director::getInstance()->getWinSize(); auto rect = Rect(0, 0, s.width, s.height); Mat4 inv = transform; inv.inverse(); rect = RectApplyTransform(rect, inv); updateTiles(rect); updateIndexBuffer(); updatePrimitives(); _dirty = false; } } else { bool isViewProjectionUpdated = true; auto visitingCamera = Camera::getVisitingCamera(); auto defaultCamera = Camera::getDefaultCamera(); if (visitingCamera == defaultCamera) { isViewProjectionUpdated = visitingCamera->isViewProjectionUpdated(); } if (flags != 0 || _dirty || _quadsDirty || isViewProjectionUpdated) { Size s = Director::getInstance()->getVisibleSize(); auto rect = Rect(Camera::getVisitingCamera()->getPositionX() - s.width * 0.5, Camera::getVisitingCamera()->getPositionY() - s.height * 0.5, s.width, s.height); Mat4 inv = transform; inv.inverse(); rect = RectApplyTransform(rect, inv); updateTiles(rect); updateIndexBuffer(); updatePrimitives(); _dirty = false; } } if(_renderCommands.size() < static_cast<size_t>(_primitives.size())) { _renderCommands.resize(_primitives.size()); } int index = 0; for(const auto& iter : _primitives) { if(iter.second->getCount() > 0) { auto& cmd = _renderCommands[index++]; cmd.init(iter.first, _texture->getName(), getGLProgramState(), BlendFunc::ALPHA_NON_PREMULTIPLIED, iter.second, _modelViewTransform, flags); renderer->addCommand(&cmd); } } }
附上几张加了Tmx地图后,现在游戏的效果: