前言
昨天完成了自定義類和腳本導出,今天假設場景結構,並加載TMX地圖,實現地圖拖動和點選的功能.
預備工作
1. 用Tiled地圖編輯器做一張簡單的地圖,如下圖:
2. 將地圖文件和地塊紋理加入到資源目錄.
場景與層
CCTMXTiledMap由CCTMXLayer組成,而CCTMXLayer是批量渲染節點,所以沒辦法所復雜紋理的運用.
CCTMXTiledMap在類的頭文件已經說明了,每一層Layer只能夠有一張瓦片紋理,這就限制了雖然CCTMXTiledMap有很多層,但是無法作為復雜對象所在的層,還是專門做地圖和區域標記的好.
在我的設計中,主場景設定了多層Layer,CCTMXTiledMap看做一層,視為地面層(包括地下水層),而地上對象,天空等后續增加,與CCTMXTiledMap並列,沒有包含關系.
CCTMXTiledMap是一個靈活的地圖容器,沒有因為朝向的不同而被設計成不同的實現類,所以我在設計場景控制器的時候是選擇分開的,根據朝向來創建不同的控制器,因為控制器到后來會內容會非常龐雜.
具體設計如下:
CMainScene: 主場景,支持多層,腳本導出
IsometricController: 等軸模式場景控制器,用來控制地圖拖拽,點擊,揀選等操作,主要的邏輯觸發點.
代碼解析
一. 主場景類
// ------------------------------------------------------- // 文件名: MainScene.h // 描 述: 主場景 // 日 期: 11/3/2013 // 作 者: KevinYuen // 版 權: Copyright (C) 2011 - All Rights Reserved // 備 注: // ------------------------------------------------------- #ifndef _MAINSCENE_H_ #define _MAINSCENE_H_ #include "cocos2d.h" USING_NS_CC; // 場景層 typedef enum { ESLT_GROUND, // 地面 ESLT_OBJECT1, // 對象層 ESLT_OBJECT2, // 對象層 ESLT_OBJECT3, // 對象層 ESLT_SKY, // 天空層 ESLT_GUI, // 界面層 ESLT_CONTROL, // 控制層 ESLT_FORCE32 = 0x7FFFFFFF } EnSceneLayerTag; class CMainScene : public CCScene { public: // 析構函數 ~CMainScene(); public: // 加載場景 bool loadScene( const char* tmx_file ); // 卸載場景 bool unloadScene(); protected: // 加載地圖 bool loadMap( const char* map_file ); public: // 靜態create方法實現 CREATE_FUNC( CMainScene ); }; #endif // CPP #include "MainScene.h" #include "IsometricController.h" // 析構函數 CMainScene::~CMainScene() { } // 加載場景 bool CMainScene::loadScene( const char* tmx_file ) { // 進行加載 CCLOG( "[CMainScene] Prepare load scene from: %s.", tmx_file ); // 首先卸載當前場景 if( !unloadScene() ) { CCLOG( "[CMainScene] unload old scene failed." ); return false; } // 加載地圖 if( !loadMap( tmx_file ) ) { CCLOG( "[CMainScene] create map filed. map file: %s.", tmx_file ); } // 添加控制層 CCTMXTiledMap* pMap = dynamic_cast<CCTMXTiledMap*>( getChildByTag( ESLT_GROUND ) ); if( pMap ) { switch( pMap->getMapOrientation() ) { case CCTMXOrientationIso: // 等軸視距模式 { CIsometricController* pCtrl = CIsometricController::create(); pCtrl->bindScene( this ); addChild( pCtrl, 0, ESLT_CONTROL ); } break; case CCTMXOrientationOrtho: break; case CCTMXOrientationHex: break; default: CCLOG( "[CMainScene] unknown map orientation: %n.", pMap->getMapOrientation() ); break; } } // 后續層擴展 return true; } // 卸載場景 bool CMainScene::unloadScene() { CCLOG( "[CMainScene] Prepare unload tilemap." ); // 卸載該層所有子節點 removeAllChildrenWithCleanup( true ); return true; } // 加載地圖 bool CMainScene::loadMap( const char* map_file ) { CCLOG( "[CMapLayer] Prepare load tilemap from: %s.", map_file ); // 加載一個新的瓦片地圖場景節點(TMX是一種瓦片地圖的保存格式) CCTMXTiledMap *map = CCTMXTiledMap::create( map_file ); if( !map ) { CCLOG( "[CMapLayer] load tilemap failed, please check file: %s.", map_file ); return false; } // 添加進地形層 addChild( map, 0, ESLT_GROUND ); #ifdef _DEBUG // 輸出新地圖的基本信息 CCLOG( "" ); CCLOG( "********************* new tmx tile map info ********************" ); // 原始尺寸 CCSize conSize = map->getContentSize(); CCLOG( "[CMapLayer] Content size: %.2f, %.2f.", conSize.width, conSize.height ); // 瓦片排布 CCSize mapSize = map->getMapSize(); CCLOG( "[CMapLayer] Map size: %.2f, %.2f.", mapSize.width, mapSize.height ); // 瓦片尺寸 CCSize tilSize = map->getTileSize(); CCLOG( "[CMapLayer] Tile size: %.2f, %.2f.", tilSize.width, tilSize.height ); // 地圖對象信息 CCArray* pChildren = map->getChildren(); if( pChildren ) { int nLayerCount = 0; // 層數量 int nObjectTotalCount = 0; // 總的對象數 CCObject* pObj = NULL; CCARRAY_FOREACH( pChildren, pObj ) { ++nObjectTotalCount; CCTMXLayer* layer = dynamic_cast<CCTMXLayer*>(pObj); if( layer ) { CCLOG( "[CMapLayer] TMX Layer: name:%s.", layer->getLayerName() ); ++nLayerCount; } } CCLOG( "[CMapLayer] Total object count: %d.", nObjectTotalCount ); CCLOG( "[CMapLayer] Total mtx layer count: %d.", nLayerCount ); } // 對象組信息 CCArray* pGroups = map->getObjectGroups(); if( pGroups && pGroups->count() > 0 ) { CCTMXObjectGroup* pObjectGroup = NULL; CCObject* pObj = NULL; CCARRAY_FOREACH( pGroups, pObj ) { pObjectGroup = dynamic_cast<CCTMXObjectGroup*>( pObj ); if( pObjectGroup ) { CCLOG( "[CMapLayer] Object group: name: %s.", pObjectGroup->getGroupName() ); } } } CCLOG( "" ); #endif // 對地圖節點設定位置 map->setPosition( 0, 0 ); return true; } // PKG $#include "../Scene/MainScene.h" // 場景層 typedef enum { ESLT_GROUND, // 地面 ESLT_OBJECT1, // 對象層 ESLT_OBJECT2, // 對象層 ESLT_OBJECT3, // 對象層 ESLT_SKY, // 天空層 ESLT_GUI, // 界面層 ESLT_CONTROL, // 控制層 ESLT_FORCE32 = 0x7FFFFFFF } EnSceneLayerTag; class CMainScene : public CCScene { // 加載場景 bool loadScene( const char* tmx_file ); // 卸載場景 bool unloadScene(); // 靜態create方法實現 static CMainScene* create(); };
二. 控制類
// ------------------------------------------------------- // 文件名: IsometricController.h // 描 述: 等軸視距控制器 // 日 期: 11/3/2013 // 作 者: KevinYuen // 版 權: Copyright (C) 2011 - All Rights Reserved // 備 注: // ------------------------------------------------------- #ifndef _ISOMETRICCONTROLLER_H_ #define _ISOMETRICCONTROLLER_H_ #include "cocos2d.h" USING_NS_CC; class CIsometricController : public CCLayer { public: // 析構 ~CIsometricController(); public: // 初始化 virtual bool init(); // 進入(所屬場景被設置為當前運行場景時調用) virtual void onEnter(); // 銷毀(所屬場景被從當前運行場景取下時調用) virtual void onExit(); // 靜態create方法實現 CREATE_FUNC( CIsometricController ); public: // 綁定場景 void bindScene( CCScene* scene ); // 獲取綁定場景 CCScene* getScene() const; protected: // 注冊事件監聽 void registerWithTouchDispatcher( void ); // 觸摸拖動 virtual void ccTouchesMoved( CCSet *pTouches, CCEvent *pEvent ); // 觸摸開始 virtual bool ccTouchBegan( CCTouch *pTouch, CCEvent *pEvent ); // 觸摸結束 virtual void ccTouchEnded( CCTouch *pTouch, CCEvent *pEvent ); private: // 構造函數 CIsometricController(); CCScene* m_pBindScene; // 綁定場景 }; #endif // CPP #include "IsometricController.h" #include "MainScene.h" // 構造函數 CIsometricController::CIsometricController(): m_pBindScene( NULL ) { } // 析構 CIsometricController::~CIsometricController() { } // 初始化 bool CIsometricController::init() { bool ret = CCLayer::init(); if( !ret ) return false; // 代碼添加預留 return true; } // 進入(所屬場景被設置為當前運行場景時調用) void CIsometricController::onEnter() { CCLayer::onEnter(); // 設置響應觸摸 setTouchEnabled( true ); } // 銷毀(所屬場景被從當前運行場景取下時調用) void CIsometricController::onExit() { // 解除場景綁定 bindScene( NULL ); // 設置響應觸摸 setTouchEnabled( false ); CCLayer::onExit(); } // 綁定場景 void CIsometricController::bindScene( CCScene* scene ) { m_pBindScene = scene; } // 獲取綁定場景 CCScene* CIsometricController::getScene() const { return m_pBindScene; } // 注冊事件監聽 void CIsometricController::registerWithTouchDispatcher( void ) { CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate( this, 0, false ); CCLayer::registerWithTouchDispatcher(); } // 獲取指定節點坐標的地塊索引 // 方法來自網絡 CCPoint tilePosFromLocation( const CCPoint& location, CCTMXTiledMap* tileMap ) { // Tilemap position must be subtracted, in case the tilemap position is scrolling CCPoint pos = ccpSub(location, tileMap->getPosition() ); float halfMapWidth = tileMap->getMapSize().width * 0.5f; float mapHeight = tileMap->getMapSize().height; float tileWidth = tileMap->getTileSize().width; float tileHeight = tileMap->getTileSize().height; CCPoint tilePosDiv = CCPointMake(pos.x / tileWidth, pos.y / tileHeight); float inverseTileY = mapHeight - tilePosDiv.y; // Cast to int makes sure that result is in whole numbers float posX = (int)(inverseTileY + tilePosDiv.x - halfMapWidth); float posY = (int)(inverseTileY - tilePosDiv.x + halfMapWidth); // make sure coordinates are within isomap bounds posX = MAX(0, posX); posX = MIN(tileMap->getMapSize().width - 1, posX); posY = MAX(0, posY); posY = MIN(tileMap->getMapSize().height - 1, posY); return CCPointMake(posX, posY); } // 觸摸拖動 void CIsometricController::ccTouchesMoved( CCSet *pTouches, CCEvent *pEvent ) { CCTouch *touch = (CCTouch*)pTouches->anyObject(); CCPoint diff = touch->getDelta(); CCTMXTiledMap* pTmxMap = dynamic_cast<CCTMXTiledMap*>( m_pBindScene->getChildByTag( ESLT_GROUND ) ); if( pTmxMap ) { CCPoint currentPos = pTmxMap->getPosition(); pTmxMap->setPosition( ccpAdd(currentPos, diff) ); } } // 觸摸開始 bool CIsometricController::ccTouchBegan( CCTouch *pTouch, CCEvent *pEvent ) { // 這里必須返回真,才會有后續的Move和End消息 return true; } // 觸摸結束 void CIsometricController::ccTouchEnded( CCTouch *pTouch, CCEvent *pEvent ) { if( !m_pBindScene ) return; CCPoint touchLocation = convertTouchToNodeSpace( pTouch ); // 檢測是否有TMX瓦塊地圖 CCTMXTiledMap* pTmxMap = dynamic_cast<CCTMXTiledMap*>( m_pBindScene->getChildByTag( ESLT_GROUND ) ); if( pTmxMap ) { CCTMXLayer* layer = pTmxMap->layerNamed("ground"); CCSprite* sprite = layer ? layer->tileAt( tilePosFromLocation( touchLocation, pTmxMap ) ) : NULL; if( sprite ) sprite->setVisible( false ); } }
三. 腳本調用
與上章不同,我重新創建了一個新的腳本,拋棄模版例子,命名為main.lua.
-------------------------------------------------------- --文件名: main.lua --描 述: 主腳本 --日 期: 11/3/2013 --作 者: KevinYuen --版 權: Copyright (C) 2011 - All Rights Reserved --備 注: --------------------------------------------------------- -- 跟蹤綁定執行函數發生錯誤的信息並輸出 function __G__TRACKBACK__(msg) print("----------------------------------------") print("LUA ERROR: " .. tostring(msg) .. "\n") print(debug.traceback()) print("----------------------------------------") end -- 主函數 local function main() -- 創建主場景 local scene = CMainScene:create(); if not scene then print( "Create main scene failed!" ); return; end -- 加載測試場景 -- 引擎對CCTMXTiledMap有限制,每一層只能有一個瓦塊包,也就是一張紋理 if not scene:loadScene( "TilesMap/Isotile_2.tmx" ) then print( "Load tilemap failed!" ); return; end -- 設定為當前場景並執行 CCDirector:sharedDirector():runWithScene( scene ); end -- 執行腳本函數並捕獲錯誤信息 -- 函數原型: xpcall( 調用函數, 錯誤捕獲函數 ); xpcall(main, __G__TRACKBACK__)
運行結果
下一步會增加對象層,實現場景對象的添加 :)