Cocos2dx 學習筆記(3) ---- 瓦片地圖加載與觸摸控制


前言

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

 運行結果

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM