Unity使用Isometric Z As Y Tilemap创建2.5D地图(三)如何用代码创建Tilemap

Unity使用Isometric Z As Y Tilemap创建2.5D地图(三)如何用代码创建Tilemap

  • 创建Tilemap Palette
    • 1.一些需要了解的事情
      • 1.1 UnityEditor代码存放位置
      • 1.2 使用MenuItem添加UnityEditor菜单
    • 2.使用代码创建Palette
    • 3.使用代码在场景中创建Tilemap
    • 4.使用代码在场景Tilemap各层上画地图


   上一篇文档介绍了Unity在使用Isometric Z As Y Tilemap时如何正确处理图片遮挡顺序(《Unity使用Isometric Z As Y Tilemap创建2.5D地图(二)如何按照正确遮挡顺序渲染图片》)。本篇文章将讲解如何使用C#代码方式创建Tilemap、Palette,进行地图绘制。

创建Tilemap Palette

1.一些需要了解的事情

   通常场景地图都是提前在Unity编辑器中提前创建好的,也有根据需要在游戏运行时动态创建的,本例子是在UnityEditor中创建地图内容。

1.1 UnityEditor代码存放位置

   Unity为了避免不必要的包含,Unity3D的运行时类和编辑器类是存储在不同的Assemblies中(UnityEngine和UnityEditor),我们将要编写的编辑器代码通常是需要放在相应工程下的 Assets\Scripts\Editor 目录中。

1.2 使用MenuItem添加UnityEditor菜单

   MenuItem属性允许你添加菜单项到主菜单和检视面板上下文菜单,该属性可以把任意静态函数变为一个可通过菜单调用的命令,仅静态函数能使用MenuItem属性。

   [MenuItem("Tools/Create Palettee")]
    static void CreatePalettee()
    {
        //to do someting
    }

   在 Assets\Scripts\Editor 目录中新建一个C#类,通过上面代码可以在Unity编辑器的 Tools 菜单中增加一个 Create Palettee的菜单,点击菜单后会执行CreatePalettee()方法。

2.使用代码创建Palette

  首先在 Assets 目录下创建需要存放Palette的目录,例子中是放在Assets/Palette目录下。

  if (!AssetDatabase.IsValidFolder("Assets/Palette"))
  {
      AssetDatabase.CreateFolder("Assets", "Palette");
  }
  if (dir.Exists)
  {
      /*
      遍历Resources/images下子目录,可以用不同目录存放不同层的图片
      比如ground目录下放ground层图片、build目录放build图片等等
      */
      foreach (DirectoryInfo subDir in dir.GetDirectories())
      {
          string subPath = "Assets/Palette/" + subDir.Name;

          if (!AssetDatabase.IsValidFolder(subPath))
          {
              AssetDatabase.CreateFolder("Assets/Palette", "" + subDir.Name);
          }
   			  //创建Palette,相关参数含义请参考前面文章
              GameObject palette = CreateNewPalette("Assets/Palette", subDir.Name, GridLayout.CellLayout.IsometricZAsY,
              GridPalette.CellSizing.Manual, new Vector3(72f, 36f, 1f), GridLayout.CellSwizzle.XYZ);
            
              //准备向Palette中写入地图块对象
              Tilemap layer1 = palette.GetComponentInChildren<Tilemap>(true);
			  //获取目录下所有图片
              Sprite[] sprites = Resources.LoadAll<Sprite>("images/" + subDir.Name);

              int x = 0;
              int y = 0;

              for (int i = 0; i < sprites.Length; i++)
              {
                  /*
                 下面这行代码不是必要的,下面会解释。
				  */
                  Vector2 offset = TextureManager.GetSpriteOffset(sprites[i].name);

                  //在Palette里创建Tile
                  Tile tile = Tile.CreateInstance<Tile>();
                  switch(subDir.Name)
                  {
                      case "ground":
                          tile.colliderType = Tile.ColliderType.None;
                          break;
                      case "build":
                          tile.colliderType = Tile.ColliderType.Grid;
                          break;
                      case "airbuild":
                          tile.colliderType = Tile.ColliderType.None;
                          break;
                  }
                  
                  tile.color = Color.white;
                  tile.sprite = sprites[i];
                  tile.flags = TileFlags.LockColor;
                  tile.name = sprites[i].name;
                  
                  /*
                  下面5行代码不是必要的,用处是设置Palette中Tile默认的transform。
                  此处用来将我读取的图片资源偏移写入Palette的Tile中。
                  */
                  Matrix4x4 matrix = tile.transform;
                  matrix.m03 = sprites[i].rect.width/2 - offset.x;//x
                  matrix.m13 = offset.y - sprites[i].rect.height;//y
                  matrix.m23 = (offset.y - sprites[i].rect.height) * 2;//z
                  tile.transform = matrix;
                  
                  //生成Tile asset
                  string tilePath = AssetDatabase.GenerateUniqueAssetPath(subPath + "/" + tile.name + ".asset");
                  AssetDatabase.CreateAsset(tile, tilePath);

                  //将Tile写入Pallette
                  layer1.SetTile(new Vector3Int(x, y, 0), tile);
  				  
  				  //此处将40个图片放在一行,可根据需要调整
  				  y = y + 1;
                  if(y >= 40)
                  {
                      x = x + 1;
                      y = 0;
                  }
              }
      }
      AssetDatabase.SaveAssets();
  }

   上面代码中,Vector2 offset = TextureManager.GetSpriteOffset(sprites[i].name);以及 Matrix4x4相关5行代码不是必要的,但我认为很多时候是很有用的。
   由于我所用资源的图片大小不规则,所以导入资源时需要将图片显示偏移位置加入到Palette的图片中,这样绘制Tilemap地图时才能显示在正确的位置。大家可以根据自己图片需要去掉或调整相关代码。
   Matrix4x4用于矩阵转换,可以用来完成缩放、旋转等作用。这里我们只用到其中3个属性,m03对应坐标X值,m13对应坐标Y值,m23对应坐标Z值。

   代码中Tile.colliderType相关设置,是用来设置Tile的碰撞类型:

属性名 作用描述
Tile.ColliderType.None 无碰撞
Tile.ColliderType.Grid 用网格的outline作为碰撞检测形状
Tile.ColliderType.Sprite 用Sprite outline作为碰撞检测形状

CreateNewPalette方法定义如下:

private static GameObject CreateNewPalette(string folderPath, string name, GridLayout.CellLayout layout, GridPalette.CellSizing cellSizing, Vector3 cellSize, GridLayout.CellSwizzle swizzle)
{
     GameObject temporaryGO = new GameObject(name);
     Grid grid = temporaryGO.AddComponent<Grid>();

     grid.cellSize = cellSize;
     grid.cellGap = cellSize;
     grid.cellLayout = layout;
     grid.cellSwizzle = swizzle;

     CreateNewLayer(temporaryGO, "Layer1", layout);

     string path = AssetDatabase.GenerateUniqueAssetPath(folderPath + "/" + name + ".prefab");

     UnityEngine.Object prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(temporaryGO, path, InteractionMode.AutomatedAction);
     GridPalette palette = CreateGridPalette(cellSizing);
     AssetDatabase.AddObjectToAsset(palette, prefab);
     PrefabUtility.ApplyPrefabInstance(temporaryGO, InteractionMode.AutomatedAction);
     AssetDatabase.Refresh();

     GameObject.DestroyImmediate(temporaryGO);
     return AssetDatabase.LoadAssetAtPath<GameObject>(path);
 }

CreateNewLayer方法定义如下:

private static GameObject CreateNewLayer(GameObject paletteGO, string name, GridLayout.CellLayout layout)
{
    GameObject newLayerGO = new GameObject(name);
    var tilemap = newLayerGO.AddComponent<Tilemap>();

    //Sprite Anchor需要根据实际情况调整
    tilemap.tileAnchor = new Vector3(0, 0, 0);

    var renderer = newLayerGO.AddComponent<TilemapRenderer>();
    newLayerGO.transform.parent = paletteGO.transform;
    newLayerGO.layer = paletteGO.layer;

    //默认设置
    switch (layout)
    {
        case GridLayout.CellLayout.Hexagon:
            {
                tilemap.tileAnchor = Vector3.zero;
                break;
            }
        case GridLayout.CellLayout.Isometric:
        case GridLayout.CellLayout.IsometricZAsY:
            {
                renderer.sortOrder = TilemapRenderer.SortOrder.TopRight;
                break;
            }
    }

    return newLayerGO;
}

GridPalette 方法定义如下:

private static GridPalette CreateGridPalette(GridPalette.CellSizing cellSizing)
{
    var palette = GridPalette.CreateInstance<GridPalette>();
    palette.name = "Palette Settings";
    palette.cellSizing = cellSizing;
    return palette;
}

3.使用代码在场景中创建Tilemap

首先在场景中创建Tilemap对象,主要代码如下:

  //在场景中创建Tilemap Grid,这里命名为TilemapRoot,可根据需要调整。
  GameObject tilemap = new GameObject("TilemapRoot");
  Grid grid = tilemap.AddComponent<Grid>();
  grid.cellSize = new Vector3(72, 36, 1);
  grid.cellLayout = GridLayout.CellLayout.IsometricZAsY;
  grid.cellSwizzle = GridLayout.CellSwizzle.XYZ;

在Tilemap对象下根据需要创建多层Tilemap Layer。我这里创建了地面层、建筑层、空中层、碰撞层,可根据实际情况调整。主要代码如下:

  //创建ground tilemap
  Tilemap groundLayer = CreateTilemapLayer("ground", tilemap, Vector3.zero,"Ground",0);
  Tilemap buildLayer = CreateTilemapLayer("build", tilemap, Vector3.zero, "Build", 1);
  Tilemap airbuildLayer = CreateTilemapLayer("airbuild", tilemap, Vector3.zero, "Build", 1);
  Tilemap colliderLayer = CreateTilemapLayer("collider", tilemap, Vector3.zero, "Ground", 0);
  
  //为碰撞层添加TilemapCollider2D组件,否则碰撞无法触发。
  TilemapCollider2D collider = colliderLayer.gameObject.AddComponent<TilemapCollider2D>();
  collider.usedByComposite = true;
  //此为碰撞形状偏移,可以使用默认没有偏移,或者根据需要设置。
  collider.offset = new Vector2(-2, 26);

CreateTilemapLayer类定义如下:

private static Tilemap CreateTilemapLayer(string name,GameObject parent, Vector3 anchor,string sortingLayerName,int sortOrderInLayer)
{
    GameObject layer = new GameObject(name);
    Tilemap layerTilemap = layer.AddComponent<Tilemap>();
    layerTilemap.tileAnchor = anchor;
    TilemapRenderer tilemapRenderer = layer.AddComponent<TilemapRenderer>();
    tilemapRenderer.sortOrder = TilemapRenderer.SortOrder.TopRight;
    tilemapRenderer.mode = TilemapRenderer.Mode.Individual;
    tilemapRenderer.sortingLayerName = sortingLayerName;
    tilemapRenderer.sortingOrder = sortOrderInLayer;
    layer.transform.parent = parent.transform;
    return layerTilemap;
}

4.使用代码在场景Tilemap各层上画地图

使用上一章定义的Tilemap Layer上画地图了,主要涉及代码如下:

  //可以直接通过加载之前创建的Palette Tile资源画图。举例如下:
  Tile tile = AssetDatabase.LoadAssetAtPath<Tile>("Assets/Palette/ground/0.asset");
  if (tile!=null)
  {
      tile = GameObject.Instantiate(tile);
	  
	  //准备要画图的网格位置
      Vector3Int position = new Vector3Int(x,y,1);
      
      //下面4行代码不是必要的,可以根据需要动态调整场景中图片显示的x、y、z值。
      Matrix4x4 pos = tile.transform;
      pos.m13 = pos.m13 + offsetHeight[i-1, j, k];
      pos.m23 = pos.m23 + (offsetHeight[i - 1, j, k] - 1) * 2;
      tile.transform = pos;
      
      //在对应Tilemap Layer图层中画图
      tilemapLayer.SetTile(position, tile);

  }

   上面Matrix4x4 pos相关代码不是必要的,但根据我的图片资源情况是很有用的,可以根据实际需要调整对应网格图片显示的X、Y、Z值。根据前面的文章可以知道,这对于正确显示地图图片和调整图片遮挡关系是很有必要的。


   好了,有了以上主要代码,大家就可以根据自己需要通过代码自动创建Tilemap Palette调色板和地图的。代码中有不对的地方请务必指正。

你可能感兴趣的:(Unity2019)