簡介
這邊要介紹一個好工具,當你覺得你在Terrain上種植了許多樹木,但在執行場景時的效能不佳的時候,又必須使用到Terrain來顯示地形,這篇提供了將Terrain中用筆刷種植的樹木單獨每個轉換成GameObject方法,並且又可以讓你的場景多多少少省了一些資源。此篇會講到透過程式來轉換並加上了TerrainToMesh的套件應用。
Convert Terrain Tree to GameObject
首先,這個功能在Unity3D中沒有範例程式,範例功能可以使用。這邊會使用到程式碼來轉換Terrain Data中的樹木的參數成為GameObject。我們先講解整個新增Convert Terrain Tree to GameObject的工具,請看下方流程 :
Step1 : 新增一個資料夾在Project Window中的Assets資料夾,並命名為「Editor」。
Step2 : 在剛剛新增的「Editor」資料夾中新增一個「C# Script」,並命名為「 TreeReplacerS 」。
Step3 : 大家可以看下方的Tool程式碼區,把它複製整份內文貼上剛剛新增的「TreeReplacerS.cs」程式碼中。(有在程式碼中註解解釋程式架構)。
Step4 : 可以看到下圖,點選Window -> ConvertTerrainTool -> TreeReplacerS 。
Step5 : 將Terrain拖曳到剛剛新增的Tool第一個欄位中,下圖中的1.是將Terrain中的樹木獨立出來成為GameObject,2.是假如你已把TerrainTree拆出來了話,按下這按鈕就可以把他移除。
Step6 : 拆出來的樹木將會歸整在「TREES_GENERATED」父GameObject中。(這個名稱可以在程式碼中修改」。
Terrain to Mesh
這時我們就不需要再重新發明輪子了,大家可以到Asset Store搜尋「Mesh Terrain Editor Free」。裡面其實包含很多功能,但我們這邊只先講述到轉換Terrain為Mesh來看看效能有沒有提升。
Step1 : 請到Window -> Mesh Terrain Editor -> Converter,並會跳出Editor視窗。
Step2 : 這邊要注意的是自己的Terrain上的Texture Layer不能超過四個(付費才可以超過四張)。可以看到MTE轉換器視窗,名稱是轉換後的物件名稱,下方的文件格式可以選取.obj與.Asset。下方式選擇輸出的資料夾(建議放在Asset中)。接著的區塊會影響到轉換出來Mesh的精細度,當調整頂點數的數值時,下方會及時運算細部數面數。
Step3 : 上方都設定好之後請按下「開始轉換」,會可以看到在Hierarchy有多出一個Terrain的GameObject(Prefab)。下圖是要說明,標示1.的是原始我們已經擁有的Terrain(包含樹木),2.是我們透過程式碼拔出Terrain中的樹木與TerrianToMesh所轉換的物件。
小結
這篇提供了兩個方法來將Terrain做一個不同方式的優化,可以先看到下面兩張圖,左邊是原始的Terrain Batch : 2686,而右邊是整理過的兩物件的GameObject Batch : 2633,看來是優化了一些,而且這樣拆開物件有好有壞,可以讓Terrain的自由度更高,且可以把不需要的Terrain部分刪除、下上Tag等等。假如想要在右圖更省資源一點可以將物件Static化 ( Terrain Batch : 2400左右)。
Tool 程式碼
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
// Replaces Unity terrain trees with prefab GameObject.
//下方的class名稱「TreeReplacerS 」要跟在Unity Editor中新增的名稱要相同
//並可以看到public class要繼承「EditorWindow」,並配合要在public class上方寫上「[ExecuteInEditMode]」,才可以把這工具鑲嵌在UnityEditor Window中
[ExecuteInEditMode]
public class TreeReplacerS : EditorWindow
{
//在Tool Window中的顯示的Title方法
[Header("References")]
public Terrain _terrain;
//在Window中可以看到的MenuItem名稱
[MenuItem("Window/TerrainConvertTool/TreeReplacer")]
static void Init()
{
TreeReplacerS window = (TreeReplacerS)GetWindow(typeof(TreeReplacerS));
}
//顯示在Tool Window的功能GUI
void OnGUI()
{
_terrain = (Terrain)EditorGUILayout.ObjectField(_terrain, typeof(Terrain), true);
if (GUILayout.Button("Convert to objects"))
{
Convert();
}
if (GUILayout.Button("Clear generated trees"))
{
Clear();
}
}
//"Convert to objects"按鈕按下去之後的功能process
public void Convert()
{
TerrainData data = _terrain.terrainData;
float width = data.size.x;
float height = data.size.z;
float y = data.size.y;
// Create parent
GameObject parent = GameObject.Find("TREES_GENERATED");
if (parent == null)
{
parent = new GameObject("TREES_GENERATED");
}
// Create trees
foreach (TreeInstance tree in data.treeInstances)
{
if (tree.prototypeIndex >= data.treePrototypes.Length)
continue;
var _tree = data.treePrototypes[tree.prototypeIndex].prefab;
Vector3 position = new Vector3(
tree.position.x * width,
tree.position.y * y,
tree.position.z * height) + _terrain.transform.position;
Vector3 scale = new Vector3(tree.widthScale, tree.heightScale, tree.widthScale);
GameObject go = Instantiate(_tree, position, Quaternion.Euler(0f, Mathf.Rad2Deg * tree.rotation, 0f), parent.transform) as GameObject;
go.transform.localScale = scale;
}
}
//"Clear generated trees"按鈕按下去之後的功能process
public void Clear()
{
DestroyImmediate(GameObject.Find("TREES_GENERATED"));
}
}