天天看點

Unity使用Isometric Z As Y Tilemap建立2.5D地圖(三)如何用代碼建立Tilemap建立Tilemap Palette

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調色闆和地圖的。代碼中有不對的地方請務必指正。

繼續閱讀