In this section you will learn how to create a random forest and how to combine the trees into larger meshes to increase the performance.
Begin by removing the trees you added in the last tutorial (but keep the prefap Tree). Then create two new game objects and add a Mesh Renderer and a Mesh Filter to them. Rename them "Tree combined wood" and "Tree combined leaf" and add materials to them. These objects will hold the combined tree meshes. To get a cleaner work space, create a new game object called "Tree parent" and as a child to this object, create 2 new game objects called "Individual trees" and "Combined parent." So we will parent all individual trees to the parent "Individual trees" because we have to save them after creating the combined tree mesh so we can remove them later on from the combined mesh.
Now create a script called "TutorialCreateForest" and add it to the "_Controller" game object. Deactivate the "TutorialCombineAll" script if you haven't already done so (the script we created in the last part). Then add the following to the new script:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class TutorialCreateForest : MonoBehaviour { public static TutorialCreateForest current; //Drags //The main tree public GameObject treeObj; //What we are going to parent everything to, to get a clean workspace public Transform individualTreeParent; public Transform combinedMeshParent; //The combined meshes public GameObject combinedWoodObj; public GameObject combinedLeafObj; //How many vertices per combined mesh (max 65535) //Should sometimes be smaller because smaller meshes are faster to modify [System.NonSerialized] public int vertexLimit = 30000; //Lists to make it easier to remove/add trees from/to combined meshes //Will contain references to all combined meshes [System.NonSerialized] public List<GameObject> combinedWoodList = new List<GameObject>(); [System.NonSerialized] public List<GameObject> combinedLeafList = new List<GameObject>(); //We also need a list of all trees we created before combining them //to make it easier to remove them from the combined meshes [System.NonSerialized] public List<GameObject> allTrees = new List<GameObject>(); void Awake() { current = this; } void Start() { CreateForest(); //CombineTrees(); } //Creates a random forest void CreateForest() { for (int i = 0; i < 2000; i++) { //Generate random map coordinates float halfMapSize = 300f; float x = Random.Range(-halfMapSize, halfMapSize); float z = Random.Range(-halfMapSize, halfMapSize); Vector3 treePos = new Vector3(x, 0f, z); //Create a tree GameObject newTree = Instantiate(treeObj, treePos, Quaternion.identity) as GameObject; //Parent it to get a clean workspace newTree.transform.parent = individualTreeParent; //Add it to the list of trees allTrees.Add(newTree); } } }
Add everything needed to the script and press Play. If you click to display the Stats window and if you zoom out, you should see that the number of batches has grown to almost 4000:
Let's reduce the number of batches. So add the method "CombineTrees()" to the script:
//Combines the trees in the forest into combined meshes void CombineTrees() { //Lists needed to create the combined meshes of each type List<CombineInstance> woodList = new List<CombineInstance>(); List<CombineInstance> leafList = new List<CombineInstance>(); //Used in the loop, but better to create it here CombineInstance combine = new CombineInstance(); //Keep track of the vertices in the list so we know when we have reached the limit int verticesSoFar = 0; //Keep track of which gameobject the mesh has been combined into int meshListCounter = 0; //Loop through all trees for (int i = 0; i < allTrees.Count; i++) { allTrees[i].SetActive(false); //Add to which list och combined meshes this tree belongs allTrees[i].GetComponent<TreeData>().listPos = meshListCounter; //Get all children MeshFilter[] meshFilters = allTrees[i].GetComponentsInChildren<MeshFilter>(true); //Loop through all children and add them to respective list for (int j = 0; j < meshFilters.Length; j++) { //We need to get both the mesh's mesh renderer and mesh filter MeshFilter meshFilter = meshFilters[j]; //To find out if this is mesh is a wood or a leaf we need the name of the material //Modify the material name, because Unity adds (Instance) to the end of the name string materialName = meshFilter.GetComponent<MeshRenderer>().material.name.Replace(" (Instance)", ""); if (materialName == "Leaf") { combine.mesh = meshFilter.mesh; combine.transform = meshFilter.transform.localToWorldMatrix; //Add it to the list of leaf mesh data leafList.Add(combine); //The leafs have more vertices, so we need to keep track of them verticesSoFar += meshFilter.mesh.vertexCount; } else if (materialName == "Wood") { combine.mesh = meshFilter.mesh; combine.transform = meshFilter.transform.localToWorldMatrix; //Add it to the list of wood mesh data woodList.Add(combine); } } //Have we reached the limit? if (verticesSoFar > vertexLimit) { //If so we have added to many vertices, so we undo the last step i -= 1; //And remove the last mesh we added to the lists leafList.RemoveAt(leafList.Count - 1); woodList.RemoveAt(woodList.Count - 1); //Now we can create a combined mesh of the meshes we have collected so far CreateCombinedMesh(woodList, combinedWoodObj, combinedWoodList); CreateCombinedMesh(leafList, combinedLeafObj, combinedLeafList); //Reset the lists with mesh data leafList.Clear(); woodList.Clear(); verticesSoFar = 0; //Change how many combined meshes we have generated so far meshListCounter += 1; } } //When the main loop is finished we have to add what didnt reach the vertice limit CreateCombinedMesh(woodList, combinedWoodObj, combinedWoodList); CreateCombinedMesh(leafList, combinedLeafObj, combinedLeafList); } //Creates a combined mesh from a list and adds it to a game object void CreateCombinedMesh(List<CombineInstance> meshDataList, GameObject meshHolderObj, List<GameObject> combinedHolderList) { //Create the new combined mesh Mesh newMesh = new Mesh(); newMesh.CombineMeshes(meshDataList.ToArray()); //Create new game object that will hold the combined mesh GameObject combinedMeshHolder = Instantiate(meshHolderObj, Vector3.zero, Quaternion.identity) as GameObject; combinedMeshHolder.transform.parent = combinedMeshParent; //Add the mesh combinedMeshHolder.GetComponent<MeshFilter>().mesh = newMesh; //Add the combined holder to the list combinedHolderList.Add(combinedMeshHolder); }
The basic idea is that we loop through all trees we created and combine them into meshes. The problem is that the limit of one mesh is 65535 vertices (a vertice is like a corner in the mesh), so when we have reached that limit we need to create a new combined mesh and begin all over again. I've also decided to lower the limit from 65535 to 30000. The reason is that when we begin to add and remove trees, it will be much faster to search through the combined mesh if we have fewer trees combined into one mesh. But if you are not going to remove or add something to the mesh, you can safely leave the limit at 65535.
To be able to easier add and remove trees, we also need to know which combined mesh the individual tree has been combined into. So we have to save the list number in a script called "TreeData." So create such a script, add it to the Tree prefab, and add the following:
using UnityEngine; using System.Collections; //Holds data about a tree public class TreeData : MonoBehaviour { public int listPos; void Awake() { //-1 means not in any list at all listPos = -1; } }
I've also been lazy by assuming that the wood part of the tree and the leaf part of the tree have the same amount of vertices. But in reality the cylinder has fewer vertices than a sphere. So if you want to squeeze out every inch of performance out of your game, you should not change to a new combined wood mesh when the combined mesh that holds the leafs is full, because you can fit more cylinders in one combined mesh than what you can fit spheres. But to get a cleaner code, I've decided to ignore that fact.
If you now press Play and zoom out you should be able to see that the number of batches have decreased from 4000 to 200:
Now let's move on and learn how to add and remove trees. It might sound easy, but it will be complicated, so take a break before you begin!