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__)

 运行结果

下一步会增加对象层,实现场景对象的添加 :)

原文地址:https://www.cnblogs.com/KevinYuen/p/2954207.html