接上一節內容: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地圖后,現在游戲的效果: