Dynamic Mesh Combining in Unity with C#

Set up the basic scene

In this part of the tutorial you will make a ground, a tree, a moving camera, and a Tree Brush tool similar to the tool in Cities: Skylines.

Part 1. A one tree forest

Start by making a plane with the scale 100 positioned at the center. Give it a nice color and rename it to "Ground." Then create an empty gameobject and rename it "_Controller." Make sure it is at the center. This controller will hold all scripts we create.

Then create an empty gameobject and rename it to "Tree." As a child to that gameobject, create a cylinder, and rename it to "Wood." It should have position (0, 1, 0) and scale (0.2, 1, 0.2). Give it a material called "Wood." Then add a sphere, and rename the sphere to "Leaf," and give it a material called "Leaf." The sphere should have position (0, 2, 0) and scale (2, 2.54, 2). If you have done everything correctly you should now have what looks like a tree, where the coordinate system of the tree begins at the bottom of the tree. This will make it easier to add trees and we don't need to care about the tree's height. Also make the tree a prefab.

Now we need a script that can move the camera. This is not a tutorial on how to move a camera, so I will just give you my script. The basic idea is that we move the camera with WASD and zoom it with the mouse wheel or keys I or O. Drag the script to the camera.

using UnityEngine;
using System.Collections;

public class CameraController : MonoBehaviour {

	float height = 40f;
	float distanceBack = 40f;

	float camMoveSpeed = 30f;
	float zoomSpeed = 3f;

	void Start() 
	{
		transform.position = Vector3.zero;
		//Move up
		transform.position += new Vector3(0f, height, 0f);
		//Move back
		transform.position -= new Vector3(0f, 0f, distanceBack);
		//Look at the center to get an angle
		transform.LookAt(Vector3.zero);
	}

	void LateUpdate() 
	{
		//Move camera with keys
		//Move left/right
		if (Input.GetKey(KeyCode.A)) {
			transform.position -= new Vector3(camMoveSpeed * Time.deltaTime, 0f, 0f);
		}
		else if (Input.GetKey(KeyCode.D)) {
			transform.position += new Vector3(camMoveSpeed * Time.deltaTime, 0f, 0f);
		}

		//Move forward/back
		if (Input.GetKey(KeyCode.S)) {
			transform.position -= new Vector3(0f, 0f, camMoveSpeed * Time.deltaTime);
		}
		else if (Input.GetKey(KeyCode.W)) {
			transform.position += new Vector3(0f, 0f, camMoveSpeed * Time.deltaTime);
		}

		//Zoom
		float currentHeight = transform.position.y;

		float zoomDistance = 0f;

		if (currentHeight > 20f && currentHeight < 200f) {
			if (Input.GetAxis("Mouse ScrollWheel") > 0f || Input.GetKeyDown(KeyCode.I)) {
				zoomDistance += zoomSpeed;
			} 
			else if (Input.GetAxis("Mouse ScrollWheel") < 0f || Input.GetKeyDown(KeyCode.O)) {
				zoomDistance -= zoomSpeed;
			} 
		}
		//Can only zoom in
		else if (currentHeight > 200f) {
			if (Input.GetAxis("Mouse ScrollWheel") > 0f || Input.GetKeyDown(KeyCode.I)) {
				zoomDistance += zoomSpeed;
			} 
		}
		//Can only zoom out
		else if (currentHeight < 20f) {
			if (Input.GetAxis("Mouse ScrollWheel") < 0f || Input.GetKeyDown(KeyCode.O)) {
				zoomDistance -= zoomSpeed;
			} 
		}

		transform.Translate(Vector3.forward * zoomDistance);
	}
}

Part 2. A Tree Brush tool

Now we will create a Tree Brush tool similar to the tool Cities: Skylines is using to place trees. If you haven't seen it, it is basically a filled circle that you can resize to place (or remove) trees over a larger or smaller area.

Out Tree Brush tool consists of a projector, so import Unity's standard projectors. Go to Assets → Import package → Effects → Projectors. We are here going to use the BlobLightProjector. But first you have to create an empty gameobject and rename it "Circle." Then make the projector a child of that gameobject. Position the projector at (0, 0.38, 0) and make sure the rotation is (90, 0, 0). Also check the projector's "Ortographic" checkbox, and that the projector should ignore layer "Tree." So make sure the Tree you created is at a layer called "Tree."

But the projector's circle doesn't look good, so we need a new one. Open the projector's material and give it a new circle by importing a circle image to Unity and then drag it to the material slot called "Cookie." I'm using this circle:

Our tree brush circle

Now we need to make the circle move around with the mouse. Create a new script called "TutorialMouseMarker," and add the following to it:

using UnityEngine;
using System.Collections;

//Creates a cities skylines style round circle that 
//will move around with the mouse and is resizable
public class TutorialMouseMarker : MonoBehaviour
{
    public static TutorialMouseMarker current;

    //Drags
    //The gameobject holding the projector
    public GameObject circleObj;
    //The projector that will display the circle
    public Projector projector;

    //Projector settings
    float projectorMax = 15f;
    float projectorMin = 3f;

    void Awake()
    {
        current = this;
    }

    void Update()
    {
        UpdateProjector();
    }

    //Move the circle and change its size
    void UpdateProjector()
    {
        //Find the position of the mouse
        Vector3 mouseScreenPosition = Input.mousePosition;

        RaycastHit hit;

        //Fire ray and make sure we hit ground which is layer 10
        if (Physics.Raycast(Camera.main.ScreenPointToRay(mouseScreenPosition), out hit, 1000f, 1 << 10))
        {
            //Change the position of the circle to the position
            //where the ray hit the ground
            circleObj.transform.position = hit.point;
        }

        //Change size of projector radius
        float projectorSize = projector.orthographicSize;

        //Increase/decrease with p and m keys
        if (Input.GetKey(KeyCode.P))
        {
            projectorSize += 0.5f;
        }
        else if (Input.GetKey(KeyCode.M))
        {
            projectorSize -= 0.5f;
        }

        //Make sure it can't grow too big nor too small
        projector.orthographicSize = Mathf.Clamp(projectorSize, projectorMin, projectorMax);
    }
}

Add the script to "_Controller" gameobject and add both the "Circle" gameobject and the projector to the script. The basic idea of the script is to fire a ray from wherever the mouse is towards the ground and then position the circle where the ray hit the ground. To make this work the ray has to ignore everything else except the ground. So click on the "Ground" gameobject and make sure it is at layer 10 (We check that when we fire a ray by using the parameter "1 << 10") and give the layer a cool name like Ground.

The last part of the script above takes care of resizing the circle so we can remove trees over a larger area and add trees over a smaller area. If you now click play you should see something that looks like this (ignore the "Tree parent" and the "Tree combined" gameobjects because we will soon add them):

How the scene should look like when you are finished

Now let's learn how to combine meshes with different colors into one mesh!