Side trip: How to draw a line in Unity

I’ve been secretly working on a another game project (codename ”Grow Some Ball”). For that game I implemented a line drawing script since the Unity’s own Line Renderer was too restricted. The script draws a line during mouse dragging – between the start click point and the current mouse position. The line is a quad drawn with two adjacent triangles.

Untitled
Mouse drag line drawn with red material (thickness increased to show the quad)

The code is quite straightforward and I though it might be useful to some of your starting with Unity (and mesh generation). It simply transforms the mouse drag screen coordinates to world coordinates (with z -1.0) and calculates the positions of the four vertices that form the line quad. So here it is!

using UnityEngine;

public class LineDrawer : MonoBehaviour
{
    public Material lineMaterial;
    public float lineZ = -1.0f;
    public float lineThickness;

    bool dragOn = false;

    GameObject lineGO;
    Mesh lineMesh;
    Vector3[] lineNormals = new Vector3[4];
    Vector3[] lineVertices = new Vector3[4];
    int[] lineTriangles = new int[6];

    Vector3 dragStartPosition;
    Vector3 mouseCurrentPosition;

    void Start ()
    {
        lineGO = new GameObject("DragLine");
        MeshRenderer renderer =
            lineGO.AddComponent<MeshRenderer>();
        renderer.shadowCastingMode = 
            UnityEngine.Rendering.ShadowCastingMode.Off;
        renderer.material = lineMaterial;

        lineMesh = new Mesh();
        MeshFilter meshFilter = lineGO.AddComponent<MeshFilter>();
        meshFilter.mesh = lineMesh;

        lineTriangles[0] = 0;
        lineTriangles[1] = 1;
        lineTriangles[2] = 2;
        lineTriangles[3] = 0;
        lineTriangles[4] = 2;
        lineTriangles[5] = 3;
        lineNormals[0] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineNormals[1] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineNormals[2] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineNormals[3] = new Vector3(-1.0f, 0.0f, 0.0f);
        lineVertices[0] = new Vector3(0.0f, 0.0f, lineZ);
        lineVertices[1] = new Vector3(0.0f, 0.0f, lineZ);
        lineVertices[2] = new Vector3(0.0f, 0.0f, lineZ);
        lineVertices[3] = new Vector3(0.0f, 0.0f, lineZ);
    }

    void DrawDragLine()
    {
        Debug.Assert(dragOn);

        if (dragStartPosition == mouseCurrentPosition) return;

        Vector3 lineStart;
        Vector3 lineEnd;

        if (dragStartPosition.x > mouseCurrentPosition.x)
        {
            lineStart = mouseCurrentPosition;
            lineEnd = dragStartPosition;
        }
        else
        {
            lineStart = dragStartPosition;
            lineEnd = mouseCurrentPosition;
        }

        float angle = Mathf.Atan2(
            lineEnd.y - lineStart.y, lineEnd.x - lineStart.x);
        float angle2 = Mathf.PI / 2.0f - angle;
        float dx = lineThickness * Mathf.Cos(angle2);
        float dy = lineThickness * Mathf.Sin(angle2);

        lineVertices[0].x = lineStart.x + dx;
        lineVertices[0].y = lineStart.y - dy;

        lineVertices[1].x = lineStart.x - dx;
        lineVertices[1].y = lineStart.y + dy;

        lineVertices[2].x = lineEnd.x - dx;
        lineVertices[2].y = lineEnd.y + dy;

        lineVertices[3].x = lineEnd.x + dx;
        lineVertices[3].y = lineEnd.y - dy;

        lineMesh.vertices = lineVertices;
        lineMesh.triangles = lineTriangles;
        lineMesh.normals = lineNormals;
    }

    void Update ()
    {
        if (!dragOn)
        {
            if (Input.GetMouseButtonDown(0))
            {
                dragStartPosition = Camera.main.ScreenToWorldPoint(
                    Input.mousePosition);
                dragOn = true;
            }
        }
        else
        {
            if (Input.GetMouseButton(0))
            {
                mouseCurrentPosition =
                    Camera.main.ScreenToWorldPoint(
                    Input.mousePosition);
                DrawDragLine();
            }
            else
            {
                if (Input.GetMouseButtonUp(0))
                {
                    lineMesh.Clear();
                    dragOn = false;
                }
            }
        }
    }
}

Sorry for the lack of syntax highlighting 🙁 If your want to test the script create a new 2D project in Unity, create an empty GameObject to the scene and drag the script to it (so it becomes a Component). Assign a material to the script in the Inspector and select the desired line thickness. Have fun!

Generating a mesh in Unity

Before writing about split mesh optimization I would like share a simple example that generates a mesh from scratch in Unity. The example is in C# and for Unity since I use it in my project 🙂

Generating a mesh allows manipulating of existing scene meshes or creation of new ones. For example, when splitting a mesh in half we have to modify the mesh of the original game object (since it is ”losing” a part) and create a new mesh for a new GameObject (the detached part).

In the following example I create a pyramid mesh and a GameObject to bring that mesh to the scene. We start by creating a new Mesh object:

 Mesh newMesh = new Mesh();

Once we have a mesh we have to define its geometry. A mesh consists of one or more triangles. A triangle is formed by joining three vertices. So we need a bunch of vertices to create triangles. To create a pyramid we need four vertices: three for the base and one for the tip:

Vector3[] vertices = new Vector3[4];

// Pyramid base vertices
vertices[0] = new Vector3(-1.0f, 0.0f, 0.0f);
vertices[1] = new Vector3(0.0f, 0.0f, -1.5f);
vertices[2] = new Vector3(1.0f, 0.0f, 0.0f);

// Pyramid tip vertex
vertices[3] = new Vector3(0.0f, 1.0f, -0.8f);

Now we have vertices, let’s create the triangles! A common practice to specify triangle vertices is with indices that point to the vertex array:

int numTriangles = 4;
int[] triangleIndices = new int[3 * numTriangles];

// Botton triangle
triangleIndices[0] = 0;
triangleIndices[1] = 1;
triangleIndices[2] = 2;

// Side triangles
triangleIndices[3] = 0;
triangleIndices[4] = 3;
triangleIndices[5] = 1;

triangleIndices[6] = 1;
triangleIndices[7] = 3;
triangleIndices[8] = 2;

triangleIndices[9] = 2;
triangleIndices[10] = 3;
triangleIndices[11] = 0;

Now we have one array that contains the vertex positions and one array that specifies the triangles. Next we have assign them to the mesh:

newMesh.vertices = vertices;
newMesh.triangles = triangleIndices;

Important: as far as I know the size of Mesh.triangles has to be divisible by three.

To bring the new mesh to the scene we need the following:

GameObject pyramidGameObject = new GameObject("PeteThePyramid");
pyramidGameObject.AddComponent<MeshRenderer>();
MeshFilter mf = pyramidGameObject.AddComponent<MeshFilter>();
mf.mesh = newMesh;

MeshRenderer component is needed so we actually see the mesh 🙂 (although without assigning a material to it we see the default magenta ”no material” color when pressing play). MeshFilter binds the newly created mesh to this GameObject.

The triangle that the code above creates should look something like this (wireframe mode in Scene view, paused):

Untitled3

I GIMPed the numbers on top of the triangle to show which vertices (of the array ”vertices”) are which.

When defining triangles with vertex indices the order of the vertices is VERY important. In Unity triangles are defined by listing the vertices in clockwise order (from the camera viewpoint). So the visible triangles in the picture above would be 0-3-1, 1-3-2 and 2-3-0.

If we define the last triangle in counter-clockwise order like this…

triangleIndices[9] = 0;
triangleIndices[10] = 3;
triangleIndices[11] = 2;

…the winding order of the triangle would be reversed and the triangle would not be visible:

Untitled

So be careful when defining triangles!

Now we have a simple pyramid mesh in the scene, cool! As you see generating a mesh is fairly simple but I wanted to post this since there cannot be too many examples 🙂

THE END

Special bonus to make thing more herp derp:

MeshCollider mc = pyramidGameObject.AddComponent<MeshCollider>();
mc.convex = true;
mc.sharedMesh = newMesh;
Rigidbody rb = pyramidGameObject.AddComponent<Rigidbody>();

As you probably guessed this makes the mesh a collider and adds a RigidBody component to the new GameObject. A view of the MeshCollider:

Untitled4