Come disegnare correttamente una linea in Unity


27

Sto lavorando a un gioco che mi richiede di tracciare alcune linee da un singolo punto che è detto in modo più formale

Dato il punto A con coordinate x, y Traccio n linee in cui l'i-esima linea ha le coordinate denominate xi, yi. Date le funzionalità di LineRenderer all'interno di Unity3D, non sono stato in grado di tracciare più di una linea da un punto particolare poiché rende solo le polilinee.

Attualmente sto usando il metodo Debug.DrawLine () che esegue correttamente il rendering della linea. Ho provato a usare GL.Begin () come mostrato nell'esempio Unity ma non riesco a vedere le mie linee disegnate.

La mia domanda è: ci sono altri metodi per farlo? In caso contrario, puoi dirmi come posso mostrare la linea che viene disegnata con Debug.DrawLine () all'interno della modalità di riproduzione? Ho visto che avrei potuto usare Gizmos.DrawLine () ma non ho capito bene il suo utilizzo.

Risposte:


48

Utilizzando le linee GL:

Consiglierei di usare l' API GL per disegnare le linee. Lo spessore della linea sarà sempre 1px sullo schermo e non è possibile modificarlo. Non ci saranno nemmeno ombre.

Le chiamate al metodo GL vengono eseguite immediatamente, quindi è necessario assicurarsi di chiamarle dopo il rendering della telecamera.

Collegare lo script alla telecamera e usare Camera.OnPostRender () funziona bene per il rendering nella finestra di gioco. Per farli mostrare nell'editor, puoi usare MonoBehaviour.OnDrawGizmos () .

Ecco il codice barebone per tracciare una linea con l'API GL:

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();
}

Ecco uno script completo che collega tutti i punti dati al punto principale. Ci sono alcune istruzioni nei commenti del codice per impostarlo correttamente e su cosa sta succedendo.

In caso di problemi durante la modifica del colore delle linee di connessione, assicurarsi di utilizzare uno shader sul materiale della linea che tenga conto del colore del vertice, ad esempio 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();
    }
}

Ulteriore nota sulle ombre: ho esplorato usando uno shader di geometria per creare ombre ma poiché le chiamate GL vengono eseguite immediatamente, non sono nella normale pipeline di rendering AutoLight.cgince Lighting.cgincnon raccolgono il ShadowCasterpassaggio.


Linee con ombre e raggio

Se è necessario modificare lo spessore della linea e si desidera avere ombre realistiche. Basta usare una maglia cilindrica e ridimensionare l'altezza.

Ecco uno script che creerà un cilindro per collegare ciascun punto al punto principale. Posizionalo su un oggetto di gioco vuoto e inserisci i parametri. Conterrà tutti gli oggetti di collegamento extra.

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);
        }
    }
}


2
Booo, le linee non hanno ombre. : p
MichaelHouse

Questa è un'ottima risposta. Sto ispezionando quello che ci sei arrivato per me. Grazie mille per il dettaglio, avevo fatto qualcosa di simile a questo senza alcun successo. Esaminerò molto bene il tuo esempio per vedere dove ho sbagliato lì. Grazie!
Christo

Posso farlo senza usare OnPostRender?
Christo,

Perché devo farlo in una classe personalizzata che non deriva dal comportamento mono, quindi non ho il metodo OnPostRender.
Christo,

1
Non sembra importare il colore che metto in GL.Color () o se lo chiamo affatto: il colore delle linee rimane il colore del materiale. Il materiale della mia linea attualmente utilizza shader Unlit / Color.
Erhannis,

5

Linee con ombre e raggio via cubo

Uscendo dalla risposta di @ MadLittleMod , qui un'altra versione che utilizza linee basate su cubo ( tris: 12 ) invece di linee basate su cilindro ( 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);
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.