前言
昨天完成了自定義類和腳本導出,今天假設場景結構,並加載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__)
運行結果

下一步會增加對象層,實現場景對象的添加 :)
