前段时间应公司需求做瓦片地图,cocos与瓦片地图的结合案例很多,但unity的却少之又少,做瓦片地图少不了Tiled Map Editor(下载地址),这个教程很多,也自带例子,关于怎么拼地图,这里就不介绍了,先看看我们的美术小哥拼的地图,效果很赞,我截取一部分:

ok,拼图咱程序不在行,可是怎么导入unity可就是技术活了,虽然很多技术大拿喜欢自己写代码,可是在现在快节奏出游戏的时代,有现成工具不用就可惜了。那我今天要说的是Tiled2Unity。

Tiled2Unity是个纯免费的软件,当然如果有想用money支持的话人家也提供了链接。官网有教程,可以好好看看。打开软件长这个样子:

先声明,我的Unity版本是Unity5.5.2,Tiled版本是0.18.2,Tiled2Unity版本是1.0.10.3,操作系统是mac,下面的介绍如果因版本或系统不同造成的结果不同,请自行找办法。

1、在Unity中导入Tiled2Unity

有两种方法:

1)先打开Unity工程,然后再打开Tiled2Unity,找到并点击Help->Import Unity Package to Project,如下图:

然后Unity会弹出导入资源框:

点Import导入即可。

2)用Unity的Import Package方法,当然要找到package,mac在Tiled2Unity的app上右键点击,显示包内容,然后找到Contents/Resources/Tield2Unity.unitypackage,导入这个就可以了。

导入后你会在工程下看到Tiled2Unity文件目录:

2、绑定Tiled Map Editor 和 Tiled2Unity

在Tiled2Unity上有个黄色区域,上面写了几行内容,如下图:

上面写的很清楚,把“open -a /Applications/Tiled2UnityMac.app --args %mapfile”这个命令行编辑进Tiled Map Editor,就可以用Tiled Map Editor打开Tiled2Unity,如何编辑到Tiled Map Editor里呢,打开Tiled Map Editor,找到下图位置:

点击旁边的下拉三角,选择编辑命令,在弹出的对话框里把上述命令写进去,如下图:

那个Export就是这个命令的名字,注意就把那串字符串复制进去,不要加什么路径啥的,网上有的教程要加各种路径啥的,根本就没明白这个命令行的意思。点击ok保存。这个时候再点击那个小齿轮,Tiled2Unity就会自动打开,并且把当前编辑的地图信息自动导入到Tiled2Unity,注意,如果不通过Tiled Map Editor而是直接打开Tiled2Unity,会没有编辑的地图信息。

3、设置Tiled2Unity并导出地图

通过Tiled Map Editor的小齿轮打开Tiled2Unity后,我们需要在上面做些设置,其实也就两条:

1)Pixels Per Unit

后面有句解释:Set to same "Pixels Per Unit" value for Unity sprites in your project。翻译过来就是与Unity中sprites里的"Pixels Per Unit"里的设置一样,Unity中图片的设置那里可以选成sprite格式,里面就会有这个设置,默认是100,也就是100个像素是一个单位,但这不是我们关心的重点。我们实际关心的是Tiled中我们的每个瓦片的单位,比如我们制作的地图每个瓦片是128*64的,是等角(交错)格式的地图,斜45度效果,所以我的最小单位该是64的一半32,这样整张地图的长和宽用这个最小单位算才不会出现小数个单位。所以Pixels Per Unit里我设置成32。

2)Export To

这是未来地图导出后放置的地方,人家也有说明:

意思是定位到有Tiled2Unity.export.txt这个文件的目录中,还记得你刚导入到Unity里的Tiled2Unity的包吗,里面就有这个文件:

所以你定位到这个文件就好了,以后导出的地图的prefab会在上面的Prefabs文件夹下。

我前面对Tiled2Unity的截图就是最终设置好的样子,其他项保持默认就可,如果你有需求自行设置。

上面都设置好后,可以点Preview Map预览一下你在Tiled中编辑的地图,如果没问题点下面的Big AssExport Button就可以导出了。左侧的Debug窗口会显示编译信息。

关于Tiled2Unity导出地图就上面这些内容,也会有很多文章介绍过,可是Tiled怎么在Unity中用其实是最值得关心的,并且这方面介绍太少了,如果对这方面有需求,且听下回分解。
————————————————

上文说道,Tiled2Unity可以方便的把Tiled编辑器做的地图文件导出到Unity中,这方面文章不少,可是这个地图文件我们在Unity中怎么用,这方面的介绍太少了,这篇是Unity实战篇,如果觉得对您有用,就请看完后点个赞吧。
 
1、Tiled Map Editor中的对象
 
这是Tiled编辑器中唯一值得我们关心的部分,因为只有这部分能参与编程,Tiled中的图层有三种,如下图

点击新建图层的时候会弹出上面三个图层,图块层和图片层是美术关心的,只是负责显示的部分,我们程序重点关心的是对象层,所以如果你想代码操纵某个图层,一定要定义成对象层。比如我会把所有的关卡点和建筑点设置成对象层,其他如地表啊,水面啊,路啊,花花草草啊什么的,都是图块层就好了。然后切换成对象显示,我们会看到那些设置成对象的瓦片列表,都在这里了,如果没有定义名称和类型,就是下面这个样子:

选择某个对象,左侧的属性是这个样子:
这有什么用啊,除了xy,宽高,毛用都没有啊,别着急,我们还什么都没干呢。
 
2、对象类型编辑器
上面都是默认的,我们要经过对象类型编辑器来编辑它们,拿到我们程序需要的变量,先找到对象类型编辑器:
选择后会弹出对象编辑器,如下图:
当然默认是什么都没有的,我这里已经加上了一些内容,先看左侧——类型,我们可以为对象设置类型,每个对象都该有个类型,相同类型的对象有着相同的属性,比如我这里定义了一个关卡类型Level,如果设置了类型的颜色,我们在编辑器中可以对该类型的对象着相同的颜色,可以区分对象类型吧,无关紧要。每个类型对应后面的属性,这个看你的需求,比如我的关卡需要记录id,需要记录4个临近关卡点的id,所以我定义了上面5个变量。每个属性有个默认值,我定义为0。
 
3、设置每个对象的自定义属性
回到主界面,选一个对象,然后在类型那行输入你刚设置的类型,见证奇迹的时刻到了,你会发现下面的自定义属性立刻出现了你在对象编辑器中设置的该类型对应的属性,如下图:
类型我输入Level,下面就出现了那5条属性,然后我可以自定义那些属性值,比如这里它的关卡号是37,相邻关卡点只有一个,是id为30的关卡点。大家可能看到了名称那里,那是自己手动加上的,上面每一条数据,未来代码都是可以访问到的,尤其是类型和自定义属性,是我们的重点。
 
4、Unity内置的自定义属性
unity:tag
unity:sortingLayerName
unity:sortingOrder
unity:layer (Layer is such an overloaded term. In Unity it means the “physics” layer.)
Tiled编辑器是支持几种与Unity相关的自定义属性的,如上面,比如你在Unity中设置好tag或layer或sortingLayerName,那在Tiled编辑器中设置相同的tag、layer或sortingLayerName,将来导出后,这些对象会自动设置成Unity对应的tag、layer、sortingLayerName,下面举个sortingLayerName的例子:
 
首先在Unity中的Tags & Layers中的Sorting Layers里加入一个Layer,设置为Background:如下图:

注意层的关系,我把Default层拖到Background下,这样Background背景层始终在最下面。然后在Tiled编辑器中的图块层和对象层我都加了unity:sortingLayerName,设置为Background,如下:
注意图块层也是可以加自定义属性的,这样导出后,我的图块层就被自动设置为Background了,由于我生成的角色会默认为Default层,这样永远保证角色会在地图上面显示,下图是导出后Unity中该图块自动加的图层:
下面是我加入角色后会在地图上方:
tag和layer也是这么设置使用,就不多介绍了。
 
5、导出时加载数据
在导入到Unity后,每个对象都携带了在Tiled编辑器中的属性,除了自定义数据,并作为TileObject脚本组件绑定到对象上:
对照本篇的3,你可以看到对应关系,所以我们可以在TileObject中获得一些基础属性。
 
但自定义数据怎么在代码中获取呢,首先我们看Tiled2Unity在Unity中导入的包,找到ICustomTiledImporter文件,路径如下:
我们看看里面的代码:
namespace Tiled2Unity
{
    public interface ICustomTiledImporter
    {
        // A game object within the prefab has some custom properites assigned through Tiled that are not consumed by Tiled2Unity
        // This callback gives customized importers a chance to react to such properites.
        void HandleCustomProperties(GameObject gameObject, IDictionary<string, string> customProperties);

        // Called just before the prefab is saved to the asset database
        // A last chance opporunity to modify it through script
        void CustomizePrefab(GameObject prefab);
    }
}

// Examples
/*
[Tiled2Unity.CustomTiledImporter]
class CustomImporterAddComponent : Tiled2Unity.ICustomTiledImporter
{
    public void HandleCustomProperties(UnityEngine.GameObject gameObject,
        IDictionary<string, string> props)
    {
        // Simply add a component to our GameObject
        if (props.ContainsKey("AddComp"))
        {
            gameObject.AddComponent(props["AddComp"]);
        }
    }
    public void CustomizePrefab(GameObject prefab)
    {
        // Do nothing
    }
}
*/

这个类是干吗的呢,就是负责导出我们自定义数据的加载类,人家很贴心的在注释里写了个例子,可供参考。其实就两个接口,HandleCustomProperties是我们处理自定义数据的地方,它传递两个参数,gameObject是每一个对象,props是自定义属性组成的字典。CustomizePrefab是我们对导出的地图prefab可以操作的地方,传递的prefab是我们的地图prefab。

我在工程目录下建个Editor文件夹,然后在其下新建个脚本:CustomImporter_StrategyTiles,内容如下:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Tiled2Unity;
[Tiled2Unity.CustomTiledImporter]
public class CustomImporter_StrategyTiles: Tiled2Unity.ICustomTiledImporter
{
    public void HandleCustomProperties(GameObject gameObject, IDictionary < string, string > customProperties)
    {
        TileObject obj = gameObject.GetComponent < TileObject > ();
        if(obj != null)
        {
            // Add the terrain tile game object
            if(obj.TmxType == "Level")
            {
                BoxCollider2D box = gameObject.AddComponent < BoxCollider2D > ();
                box.offset = new Vector2(2, 1.3 f);
                box.size = new Vector2(2, 1);
                TileData tile = gameObject.AddComponent < TileData > ();
                tile.Id = int.Parse(customProperties["Object_ID"]);
                for(int i = 0; i < 4; ++i)
                {
                    string strId = "Neighbor_ID_" + (i + 1);
                    if(customProperties.ContainsKey(strId))
                    {
                        tile.neighbor[i] = int.Parse(customProperties[strId]);
                    }
                }
            }
        }
    }
    public void CustomizePrefab(GameObject prefab)
    {
        // Do nothing
    }
}

上面的代码就是我继承ICustomTiledImporter实现获得自定义属性的地方,代码很短,首先获得该对象TileObject基础属性,然后判断其类型是否Level,是的话我做了两件事,一是加BoxCollider2D碰撞体,一是加脚本TileData储存自定义数据并绑定到对象上,自定义数据是字典,用Tiled编辑器中的key获得value就行了,然后保存在TileData的数据中,我的TileData特简单:

public class TileData : MonoBehaviour {

    public int Id = 0;
    public int[] neighbor = new int[4];
}

好了,这样所有的数据你都得到了,怎么编程就是你自己的事情了,注意这段代码是在Tiled2Unity导出地图到Unity时调用的,所有数据在导入时就已经都加载好了,跟游戏运行没关系。

嗯,再多说一点,除了上述数据,我们还可以获得整张地图的数据,点击我们的地图prefab,你可以看到它绑了个TiledMap脚本:
我们从中可以看到层数NumLayers,横向格子数NumTilesWide,纵向格子数NumTilesHigh,每个格子宽TileWidth,高TileHeight,导出缩放值ExportScale,地图像素宽MapWidthInPixels,高MapHeightInPixels。
 
如果读者还记得我设计的最小单位是32的话,那按这个最小单位我其实要用MapWidthInPixels和MapHeightInPixels计算我横向和纵向需要多少个最小单位,而不是直接用上面的NumTilesWide和NumTilesHigh,那对我没用。
 
就说这么多吧,上面那些只是基础,要跟地图做交互还要考虑摄像机的Size,位置,防止地图边缘漏出来,尤其是地图可以拖动缩放的情况。