前言
昨天完成了自定义类和脚本导出,今天假设场景结构,并加载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__)
运行结果
下一步会增加对象层,实现场景对象的添加 :)