4. 地图加载与渲染

December 9, 2012 · View on GitHub

地图对象存储在类 CCScene 的层 sceneSmallMapLayer 和sceneMapLayer 中,她们分别是类 SceneSmallMapLayer 和 SceneMapLayer 的实例,现在我们来看看地图是怎么加载和渲染的。最好的学习方式就是一边阅读这个文档,一边阅读源代码。

地图配置文件

引擎在什么时候开始加载地图?有过类似 RPG 游戏的经验的朋友应该知道,玩家进入一个场景的时候,就会显示场景的背景图。进入场景就在 CCScene 的方法 switchSchene 中,这个方法中,第一步会先加载场景的配置信息(配置信息的文的格式可以自定义),默认下引擎使用的JSON格式配置场景信息。加载配置信息在这一行代码中:

MapLoader.loadMapConfig(mapPicId, this, newOnComplete, updateHandler);	// 加载当前地图的配置信息

进入方法 loadMapConfig 中,可以看到,加载完配置文件并解析后,信息会存储到 MapInfo 对象中,然后就开始加载地图缩略图,下一节内容会讲述地图缩略图。

地图的配置信息,包括了地图的 tile (瓦片格子)的大小,地图总的宽度和高度(格子数),最后就是地图场景的阻挡和遮挡的信息,查看 Demo 中的配置文件可以看到详细的内容。

缩略图加载和显示

可能有人会问:为什么要使用缩略图?直接加载原地图不可以吗?其实是网页游戏特殊的需求,因为游戏里面的资源大部分都需要即时加载的(有的资源也可以在进入游戏前预先加载),不想端游和手游,资源可以直接打包在安装包中。又因为,国内的网速不是太给力的缘故(不过新疆的切糕还是挺给力的),所以要加载一张尺寸很大的地图,需要等待一段时间,那边在等待的时候给玩家看到黑屏的感觉不太好,那就有了这个折中的方案,显示一张比较模糊(带格子)的地图。

先看看缩略图加载后会放到什么地方显示?还记得在场景对象一章中,说到了场景对象中分了很多层,每一层显示/渲染不同类型的对象。缩略图也是在其中之一层中显示的,在场景类 CCScene 的构造函数中,会创建 SceneSmallMapLayer 对象并赋值给 CCScene 的成员变量 sceneSmallMapLayer 中,然后 addChild 到 CCScene 中作为它的孩子,所以场景的层 SceneSmallMapLayer 就是缩略图的藏身之处。

上一节中的配置文件加载完成后,会回调到 CCScene.switchScene 的 newOnComplete 函数中,在此时,开始加载地图缩略图:

MapLoader.loadSmallMap(scene);			// 加载地图缩略图

进入 loadSmallMap,加载地图缩略图:

LoaderManager.lazyLoad(loadSmallMapComplete, SceneLoader.smallMapImgLoader, false, scene.mapConfig.smallMapUrl);

scene.mapConfig.smallMapUrl 是在加载完成配置文件后给赋值的,还包括scene.mapConfig.zoneMapDir 也是在那时赋值的。缩略图加载完成后,直接 addChild 到 CCScene.sceneSmallMapLayer 中:

scene.sceneSmallMapLayer.addChild(smallMap);		// 添加小地图显示

然后分派事件 CCEventActionProcess.LOAD_SMALL_MAP_COMPLETE :

var sceneEvent:CCEvent = new CCEvent(CCEvent.PROCESS, CCEventActionProcess.LOAD_SMALL_MAP_COMPLETE, smallMap.bitmapData);
EventDispatchCenter.getInstance().dispatchEvent(sceneEvent);

缩略图的加载和显示就完成了。

地图分块加载

地图Tile(格子)

每张地图都被切割成很多小的 tile,可以根据这些 tile 来实现哪些地方是可以行走的,哪些地方是不可以到达的,还有就是哪些地方需要做遮罩的。tile 的尺寸越小行走的区域就越精确,但是太小寻路的时候效率也就会越低,默认设定 tile 的大小为 24*24,那么地图的宽和高都要是24的倍数。在 SceneInfo 中存储了 tile 的尺寸信息。

在地图编辑器中就可以根据 tile 设定的尺寸来刷图,然后导出配置文件,这个文件就是在第一节中说的地图配置文件。

地图Tile Zone(格子组)

Tile Zone 其实是为了做地图分块加载设定的,设定一组 tile 为一个 zone,那么地图就根据 zone 的大小来做切割,命名的方式是以 zone 的坐标信息组合如 "x_y.jpg"。

我们先来看看,地图加载后放到什么地方显示?在场景对象一章中说到了,地图是在 SceneMapLayer 这一层对象中显示/渲染的。在 CCScene 的构造函数会创建 SceneMapLayer 对象并赋值给 CCScene 的成员 sceneMapLayer,并 addChild 到 CCScene。地图层正在的初始化就在 CCScene.switchScene 中,也就是在加载缩略图代码后面:

sceneMapLayer.InitMapZones();			// 设置背景图(分割显示)

在 InitMapZones() 方法中,初始化地图的 zone 信息,并发它缓存到 SceneCache.mapZones 中,以便以后使用(见SceneMapLayer.loadMap())。

那么在什么时刻开始加载地图?在上一章《场景对象》中已经说到过,引擎渲染器启动是在地图配置文件加载完成后,也是在地图层 sceneMapLayer 初始化(InitMapZones())后的:

sceneRender.StartRender(true);

所以地图加载是在引擎渲染器启动后开始的,在 CCRender 的私有方法 render() 中我们见过:

scene.sceneMapLayer.Run();				// 地图跟随

这一行代码,是的,加载地图的代码就隐藏在这个方法之中,我们进去一窥究竟,Run() 中的代码不够10行,很容易理解,前面几行主要是与引擎摄像机(CCCamera)同步地图的位置,以便做跟随,没有改变视野,就不需要更新地图。我们主要看看最后一行代码:

this.loadMap();

loadMap() 方法主要是根据角色当前的位置(tileX, tileY ),计算出当前视野需要显示的地图块(zone),如果地图块还没加载过就进入 addMapZone() 进行加载,这是一个循环的过程,直到加载完所有当前视野的地图块。

其实真正触发加载的地方还是在 loadMap() 中,addMapZone() 只是准备好加载数据和加载完成后的回调。地图加载循序有做优化,靠近玩家的地图块优先加载。加载完成后(回调到 addMapZone() 的 itemLoadComplete 函数),把地图块 addChild 到对应的 zone 对象(MapZone)的 showContainer 中,因为 MapZone 是继承于 CCNode 的,自然就继承了 showContainer 属性。而所有 zone 对象的 showContainer 是被 addChild 到 SceneMapLayer 的,至此,地图加载和显示的流程就完成了。

BTW:加载地图有做错误重试处理,尝试3次后会放弃。

地图资源释放

当玩家在游戏中需要切换场景的时候,上一个场景的地图资源是需要释放的,我们可以在CCScene.Dispose() 中可以查看到如何释放地图资源:

// 清理缓冲内容
SceneCache.MapImgCache.dispose();
SceneCache.InViewMapZones = {};
SceneCache.MapTiles = {};
SceneCache.MapSolids = {};
SceneCache.MapZones = {};

mapConfig = null;
sceneSmallMapLayer.Dispose();
sceneMapLayer.Dispose();

我们看看缩略图释放资源代码,简单的把缩略图层中的孩子清除掉:

public function Dispose():void {
	Fun.clearChildren(this, false, false);
}

地图层的资源释放代码:

public function Dispose():void {
	Fun.clearChildren(this, false, false);
	this.currentMapPos = new Point(int.MAX_VALUE, int.MAX_VALUE);
	this.currentMapZone = null;
	this.waitingLoadData = {};
}

把这一层中孩子清除掉,然后重置一些成员,如正在加载的地图缓存,当前可视范围的 zone 对象缓存和当前地图的所有 zone 缓存。

到此基本就完成了地图这一块内容的讲述,大家可以详细的再看看这一章中哦给你所提到的所有相关的类,尝试去理解里面的每一行代码,下一章我们会一起研究一些角色对象。

导航