30
\$\begingroup\$

I'm working on a game which requires me to draw a few lines from a single point that is more formally said

Given point A with coordinates x,y I draw n lines where the i-th line has the coordinates named as xi,yi. Given the LineRenderer capabilities inside Unity3D I was unable to draw more than one line from a particular point with it since it renders only polylines.

I'm currently using the Debug.DrawLine() method which successfully renders the line. I have tried using GL.Begin() as it is shown in the Unity example but I cannot see my lines being drawn.

My question is : are there any other methods for doing this? If not, can you tell me how can I show the line that is being drawn with Debug.DrawLine() inside play mode? I've saw that I could use Gizmos.DrawLine() but I didn't quite understand it's usage.

\$\endgroup\$

2 Answers 2

54
\$\begingroup\$

Using GL Lines:

I would recommend using the the GL API for drawing lines. The line thickness will always be 1px on screen and there is no option to change it. There will also be no shadows.

The GL method calls are executed immediately so you need to make sure to call them after the camera has already rendered.

Attaching the script to the camera and using Camera.OnPostRender() works good for rendering in the game window. To get them to show in the editor, you can use MonoBehaviour.OnDrawGizmos().

Here is the barebones code to draw a line with the GL API (note, this code does not work in modern Unity):

public Material lineMat = new Material("Shader \"Lines/Colored Blended\" {" + "SubShader { Pass { " + "    Blend SrcAlpha OneMinusSrcAlpha " + "    ZWrite Off Cull Off Fog { Mode Off } " + "    BindChannels {" + "      Bind \"vertex\", vertex Bind \"color\", color }" + "} } }");

void OnPostRender() {
    GL.Begin(GL.LINES);
    lineMat.SetPass(0);
    GL.Color(new Color(0f, 0f, 0f, 1f));
    GL.Vertex3(0f, 0f, 0f);
    GL.Vertex3(1f, 1f, 1f);
    GL.End();
}

Here is a full script that attaches all of the given points to the main point. There are some instructions in the comments of the code to get it set up right and about what is going on.

If you are having problems changing the color of the connecting lines, make sure to use a shader on your line material that takes into account the vertex color such as Unlit/Color.

using UnityEngine;
using System.Collections;

// Put this script on a Camera
public class DrawLines : MonoBehaviour {

    // Fill/drag these in from the editor

    // Choose the Unlit/Color shader in the Material Settings
    // You can change that color, to change the color of the connecting lines
    public Material lineMat;

    public GameObject mainPoint;
    public GameObject[] points;
    
    // Connect all of the `points` to the `mainPoint`
    void DrawConnectingLines() {
        if(mainPoint && points.Length > 0) {
            // Loop through each point to connect to the mainPoint
            foreach(GameObject point in points) {
                Vector3 mainPointPos = mainPoint.transform.position;
                Vector3 pointPos = point.transform.position;
                
                GL.Begin(GL.LINES);
                lineMat.SetPass(0);
                GL.Color(new Color(lineMat.color.r, lineMat.color.g, lineMat.color.b, lineMat.color.a));
                GL.Vertex3(mainPointPos.x, mainPointPos.y, mainPointPos.z);
                GL.Vertex3(pointPos.x, pointPos.y, pointPos.z);
                GL.End();
            }
        }
    }

    // To show the lines in the game window whne it is running
    void OnPostRender() {
        DrawConnectingLines();
    }

    // To show the lines in the editor
    void OnDrawGizmos() {
        DrawConnectingLines();
    }
}

Further note on shadows: I explored using a geometry shader to make shadows but since the GL calls run immediately, they are not in the normal rendering pipeline and AutoLight.cginc and Lighting.cginc won't pick up the ShadowCaster pass.


Lines with Shadows and Radius

If you need to change the line thickness and want to have realistic shadows. Just use a cylinder mesh and scale the height.

Here is a script that will make a cylinder to connect each point to the main point. Place it on a empty game object and fill in the parameters. It will hold all of the extra connecting objects.

using UnityEngine;
using System.Collections;

public class ConnectPointsWithCylinderMesh : MonoBehaviour {

    // Material used for the connecting lines
    public Material lineMat;

    public float radius = 0.05f;

    // Connect all of the `points` to the `mainPoint`
    public GameObject mainPoint;
    public GameObject[] points;

    // Fill in this with the default Unity Cylinder mesh
    // We will account for the cylinder pivot/origin being in the middle.
    public Mesh cylinderMesh;


    GameObject[] ringGameObjects;

    // Use this for initialization
    void Start () {
        this.ringGameObjects = new GameObject[points.Length];
        //this.connectingRings = new ProceduralRing[points.Length];
        for(int i = 0; i < points.Length; i++) {
            // Make a gameobject that we will put the ring on
            // And then put it as a child on the gameobject that has this Command and Control script
            this.ringGameObjects[i] = new GameObject();
            this.ringGameObjects[i].name = "Connecting ring #" + i;
            this.ringGameObjects[i].transform.parent = this.gameObject.transform;

            // We make a offset gameobject to counteract the default cylindermesh pivot/origin being in the middle
            GameObject ringOffsetCylinderMeshObject = new GameObject();
            ringOffsetCylinderMeshObject.transform.parent = this.ringGameObjects[i].transform;

            // Offset the cylinder so that the pivot/origin is at the bottom in relation to the outer ring gameobject.
            ringOffsetCylinderMeshObject.transform.localPosition = new Vector3(0f, 1f, 0f);
            // Set the radius
            ringOffsetCylinderMeshObject.transform.localScale = new Vector3(radius, 1f, radius);

            // Create the the Mesh and renderer to show the connecting ring
            MeshFilter ringMesh = ringOffsetCylinderMeshObject.AddComponent<MeshFilter>();
            ringMesh.mesh = this.cylinderMesh;

            MeshRenderer ringRenderer = ringOffsetCylinderMeshObject.AddComponent<MeshRenderer>();
            ringRenderer.material = lineMat;

        }
    }
    
    // Update is called once per frame
    void Update () {
        for(int i = 0; i < points.Length; i++) {
            // Move the ring to the point
            this.ringGameObjects[i].transform.position = this.points[i].transform.position;

            // Match the scale to the distance
            float cylinderDistance = 0.5f*Vector3.Distance(this.points[i].transform.position, this.mainPoint.transform.position);
            this.ringGameObjects[i].transform.localScale = new Vector3(this.ringGameObjects[i].transform.localScale.x, cylinderDistance, this.ringGameObjects[i].transform.localScale.z);

            // Make the cylinder look at the main point.
            // Since the cylinder is pointing up(y) and the forward is z, we need to offset by 90 degrees.
            this.ringGameObjects[i].transform.LookAt(this.mainPoint.transform, Vector3.up);
            this.ringGameObjects[i].transform.rotation *= Quaternion.Euler(90, 0, 0);
        }
    }
}

\$\endgroup\$
10
  • 2
    \$\begingroup\$ Booo, the lines don't have shadows. :p \$\endgroup\$
    – House
    Commented Mar 19, 2015 at 21:52
  • \$\begingroup\$ That's a great answer. I'm inspecting what you got there for me. Thanks a lot for the detail, I had done something similar to this without any success whatsoever. I'll look into your example very well to see where I went wrong there.Thanks! \$\endgroup\$ Commented Mar 19, 2015 at 22:08
  • \$\begingroup\$ Can I do this without using OnPostRender? \$\endgroup\$ Commented Mar 21, 2015 at 22:22
  • \$\begingroup\$ Because I need to do it in a custom class that is not derived from mono behaviour, thus I don't have the OnPostRender method. \$\endgroup\$ Commented Mar 21, 2015 at 22:23
  • 1
    \$\begingroup\$ It doesn't seem to matter what color I put in GL.Color(), or whether I call it at all - the color of the lines stay the color of the material. My line material is currently using shader Unlit/Color. \$\endgroup\$
    – Erhannis
    Commented Dec 10, 2016 at 19:58
7
\$\begingroup\$

Lines with Shadows and Radius via Cube

Going off of @MadLittleMod's answer, here another version using Cube based lines(tris: 12) instead of Cylinder based lines(tris: 80):

using UnityEngine;
using System.Collections;

public class ConnectPointsWithCubeMesh : MonoBehaviour 
{

    // Material used for the connecting lines
    public Material lineMat;

    public float radius = 0.05f;

    // Connect all of the `points` to the `mainPoint`
    public GameObject mainPoint;
    public GameObject[] points;

    // Fill in this with the default Unity Cube mesh
    // We will account for the cube pivot/origin being in the middle.
    public Mesh cubeMesh;


    GameObject[] ringGameObjects;

    // Use this for initialization
    void Start() 
    {
        this.ringGameObjects = new GameObject[points.Length];
        //this.connectingRings = new ProceduralRing[points.Length];
        for(int i = 0; i < points.Length; i++) {
            // Make a gameobject that we will put the ring on
            // And then put it as a child on the gameobject that has this Command and Control script
            this.ringGameObjects[i] = new GameObject();
            this.ringGameObjects[i].name = "Connecting ring #" + i;
            this.ringGameObjects[i].transform.parent = this.gameObject.transform;

            // We make a offset gameobject to counteract the default cubemesh pivot/origin being in the middle
            GameObject ringOffsetCubeMeshObject = new GameObject();
            ringOffsetCubeMeshObject.transform.parent = this.ringGameObjects[i].transform;

            // Offset the cube so that the pivot/origin is at the bottom in relation to the outer ring     gameobject.
            ringOffsetCubeMeshObject.transform.localPosition = new Vector3(0f, 1f, 0f);
            // Set the radius
            ringOffsetCubeMeshObject.transform.localScale = new Vector3(radius, 1f, radius);

            // Create the the Mesh and renderer to show the connecting ring
            MeshFilter ringMesh = ringOffsetCubeMeshObject.AddComponent<MeshFilter>();
            ringMesh.mesh = this.cubeMesh;

            MeshRenderer ringRenderer = ringOffsetCubeMeshObject.AddComponent<MeshRenderer>();
            ringRenderer.material = lineMat;

        }
    }

    // Update is called once per frame
    void Update() 
    {
        for(int i = 0; i < points.Length; i++) {
            // Move the ring to the point
            this.ringGameObjects[i].transform.position = this.points[i].transform.position;

            this.ringGameObjects[i].transform.position = 0.5f * (this.points[i].transform.position + this.mainPoint.transform.position);
            var delta = this.points[i].transform.position - this.mainPoint.transform.position;
            this.ringGameObjects[i].transform.position += delta;

            // Match the scale to the distance
            float cubeDistance = Vector3.Distance(this.points[i].transform.position, this.mainPoint.transform.position);
            this.ringGameObjects[i].transform.localScale = new Vector3(this.ringGameObjects[i].transform.localScale.x, cubeDistance, this.ringGameObjects[i].transform.localScale.z);

            // Make the cube look at the main point.
            // Since the cube is pointing up(y) and the forward is z, we need to offset by 90 degrees.
            this.ringGameObjects[i].transform.LookAt(this.mainPoint.transform, Vector3.up);
            this.ringGameObjects[i].transform.rotation *= Quaternion.Euler(90, 0, 0);
        }
    }
}
\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .