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