Trail Arc Renderer

From Unify Community Wiki
Jump to: navigation, search

author: Nick Gronow (Stick)

Contents

Description

Alright, this is my last trail renderer I will post (wink). This trail renderer is designed specifically with melee trails in mind. It will track the path of its parent object and create a fluid arc between its saved points. You can set the distance between saved points, which gives you control over the accuracy of the interpolation. Increasing distance will produce more smoothed out arcs, where reducing distance will increase accuracy to the actual path.

Features

  • Set the fade out time (lifetime) when the trail is done emitting
  • Control when its done by simply turning off the "emit" property
  • Set any number of colors
  • Set any number of widths
  • Autodestructs when faded out
  • Set the interpolation distance
  • Set whether it always faces the camera
  • Set the number of segments to place between each interpolation distance

One-Time Use

This trail renderer is designed to be a one-time use component. There are several reasons for this, based on the way it is designed for optimization. When emitting is set to false, the trail renderer will stop adding new segments and fade out. The fade out time is set to the lifetime.

Image

Swipe Trail

C# - TrailArc.cs

using UnityEngine;
using System.Collections;
 
public class TrailArc : MonoBehaviour 
{
 
	int savedIndex;
	int pointIndex;
 
    // Material - Particle Shader with "Tint Color" property
    public Material material;
 
    // Emit
    public bool emit
    {
        get { return Emit; }
        set { Emit = value; }
    }
    bool Emit = true;
    bool emittingDone = false;
 
	//Minimum velocity (script terminates)
	public float minVel = 10;
 
    // Facing
    public bool faceCamera = true;
 
    // Lifetime of each segment
    public float lifetime = 1;
    float lifeTimeRatio = 1;
    float fadeOutRatio;
 
    // Colors
    public Color[] colors;
 
    // Widths
    public float[] widths;
 
    // Optimization
    public float pointDistance = 0.5f;
    float pointSqrDistance = 0;
    public int segmentsPerPoint = 4;
    float tRatio;
 
    // Print Output
    public bool printResults = false;
    public bool printSavedPoints = false;
    public bool printSegmentPoints = false;
 
    // Objects
    GameObject trail = null;
    Mesh mesh = null;
    Material trailMaterial = null;
 
    // Points
    Vector3[] saved;
    Vector3[] savedUp;
    int savedCnt = 0;
    Vector3[] points;
    Vector3[] pointsUp;
    int pointCnt = 0;
 
    // Segment Appearance Normalization
    int displayCnt = 0;
    float lastPointCreationTime = 0;
    float averageCreationTime = 0;
    float averageInsertionTime = 0;
    float elapsedInsertionTime = 0;
 
    // Initialization
    bool initialized = false;
 
    void Start ()
    {
	if(gameObject.rigidbody.velocity.magnitude < minVel)
			Destroy(this);
 
        // Data Inititialization
        saved = new Vector3[60];
        savedUp = new Vector3[saved.Length];
        points = new Vector3[saved.Length * segmentsPerPoint];
        pointsUp = new Vector3[points.Length];
        tRatio = 1f / (segmentsPerPoint);
        pointSqrDistance = pointDistance * pointDistance;
 
        // Create the mesh object
        trail = new GameObject("Trail");
        trail.transform.position = Vector3.zero;
        trail.transform.rotation = Quaternion.identity;
        trail.transform.localScale = Vector3.one;
        MeshFilter meshFilter = (MeshFilter) trail.AddComponent(typeof(MeshFilter));
        mesh = meshFilter.mesh;
        trail.AddComponent(typeof(MeshRenderer));
        trailMaterial = new Material(material);
        fadeOutRatio = trailMaterial.GetColor("_TintColor").a;
        trail.renderer.material = trailMaterial;
    }
 
    void printPoints()
    {
        if(savedCnt == 0)
            return;
        string s = "Saved Points at time " + Time.time + ":\n";
        for(int i = 0; i < savedCnt; i++)
            s += "Index: " + i + "\tPos: " + saved[i] + "\n";
        print(s);
    }
 
    void printAllPoints()
    {
        if(pointCnt == 0)
            return;
        string s = "Points at time " + Time.time + ":\n";
        for(int i = 0; i < pointCnt; i++)
            s += "Index: " + i + "\tPos: " + points[i] + "\n";
        print(s);
    }
 
    void findCoordinates(int index)
    {
        if(index == 0 || index >= savedCnt-2)
            return;
        Vector3 P0 = saved[index-1];
        Vector3 P1 = saved[index];
        Vector3 P2 = saved[index+1];
        Vector3 P3 = saved[index+2];
        Vector3 T1 = 0.5f * (P2 - P0);
        Vector3 T2 = 0.5f * (P3 - P1);
        int pointIndex = index * segmentsPerPoint;
        for(int i = pointIndex; i < pointIndex+segmentsPerPoint; i++)
        {
            float t = (i-pointIndex) * tRatio;
            float t2 = t*t;
            float t3 = t2*t;
            float blend1 = 2*t3 - 3*t2 + 1;
            float blend2 = 3*t2 - 2*t3;
            float blend3 = t3 - 2*t2  + t;
            float blend4 = t3 - t2;
            int pntInd = i - segmentsPerPoint;
            points[pntInd] = blend1*P1 + blend2*P2 + blend3*T1 + blend4*T2;
            pointsUp[pntInd] = Vector3.Lerp(savedUp[index], savedUp[index+1], t);
        }
        pointCnt = pointIndex;
    }
 
    void Update ()
    {
        try
        {
            Vector3 position = transform.position;
            // Wait till the object is active (update called) and emitting
            if( ! initialized && Emit)
            {
                // Place the first point behind this as a starter projected point
                saved[savedCnt] = transform.TransformPoint(0,0,-pointDistance);
                savedUp[savedCnt] = transform.up;
                savedCnt++;
                // Place the second point at the current position
                saved[savedCnt] = position;
                savedUp[savedCnt] = transform.up;
                savedCnt++;
                // Begin tracking the saved point creation time
                lastPointCreationTime = Time.time;
                initialized = true;
            }
 
            if(printSavedPoints)
                printPoints();
            if(printSegmentPoints)
                printAllPoints();
 
            // Emitting - Designed for one-time use
            if( ! Emit )
            {
                if( ! emittingDone && pointCnt > 0 )
                {
                    // Save two final points projected from the ending point
                    saved[savedCnt] = transform.TransformPoint(0,0,pointDistance);
                    savedUp[savedCnt] = transform.up;
                    savedCnt++;
                    findCoordinates(savedCnt-3);
                    // This makes the trail fill the actual entire path
                    saved[savedCnt] = transform.TransformPoint(0,0,pointDistance*2);
                    savedUp[savedCnt] = transform.up;
                    savedCnt++;
                    findCoordinates(savedCnt-3);
                }
                emittingDone = true;
            }
            if(emittingDone)
                Emit = false;
 
            if(Emit)
            {
 
                // Do we save a new point?
                if( (saved[savedCnt-1] - position).sqrMagnitude > pointSqrDistance)
                {
                    saved[savedCnt] = position;
                    savedUp[savedCnt] = transform.up;
                    savedCnt++;
 
                    // Calc the average point display time
                    if(averageCreationTime == 0)
                        averageCreationTime = Time.time - lastPointCreationTime;
                    else
                    {
                        float elapsedTime = Time.time - lastPointCreationTime;
                        averageCreationTime = (averageCreationTime + elapsedTime) * 0.5f;
                    }
                    averageInsertionTime = averageCreationTime * tRatio;
                    lastPointCreationTime = Time.time;
 
                    // Calc the last saved segment coordinates
                    if(savedCnt > 3)
                        findCoordinates(savedCnt-3);
                }
            }
 
            // Do we fade it out?
            if( ! Emit && displayCnt == pointCnt)
            {
                Color color = trailMaterial.GetColor("_TintColor");
                color.a -= fadeOutRatio * lifeTimeRatio * Time.deltaTime;
                if(color.a > 0)
                    trailMaterial.SetColor("_TintColor", color);
                else
                {
                    if(printResults)
                        print("Trail effect ending with a segment count of: " + pointCnt);
                    Destroy(trail);
                    Destroy(gameObject);
                }
                return;
            }
 
            // Do we display any new points?
            if(displayCnt < pointCnt)
            {
                elapsedInsertionTime += Time.deltaTime;
                while(elapsedInsertionTime > averageInsertionTime)
                {
                    if(displayCnt < pointCnt)
                        displayCnt++;
                    elapsedInsertionTime -= averageInsertionTime;
                }
            }
 
            // Do we render this?
            if(displayCnt < 2)
            {
                trail.renderer.enabled = false;
                return;
            }
            trail.renderer.enabled = true;
 
            // Common data
            lifeTimeRatio = 1f / lifetime;
            Color[] meshColors;
 
            // Rebuild the mesh
            Vector3[] vertices = new Vector3[displayCnt * 2];
            Vector2[] uvs = new Vector2[displayCnt * 2];
            int[] triangles = new int[(displayCnt-1) * 6];
            meshColors = new Color[displayCnt * 2];
 
            float pointRatio = 1f / (displayCnt-1);
            Vector3 cameraPos = Camera.main.transform.position;
            for(int i = 0; i < displayCnt; i++)
            {
                Vector3 point = points[i];
                float ratio = i * pointRatio;
 
                // Color
                Color color;
                if(colors.Length == 0)
                    color = Color.Lerp(Color.clear, Color.white, ratio);
                else if(colors.Length == 1)
                    color = Color.Lerp(Color.clear, colors[0], ratio);
                else if(colors.Length == 2)
                    color = Color.Lerp(colors[1], colors[0], ratio);
                else
                {
                    float colorRatio = colors.Length - 1 - ratio * (colors.Length-1);
                    if(colorRatio == colors.Length-1)
                        color = colors[colors.Length-1];
                    else
                    {
                        int min = (int) Mathf.Floor(colorRatio);
                        float lerp = colorRatio - min;
                        color = Color.Lerp(colors[min+0], colors[min+1], lerp);
                    }
                }
                meshColors[i * 2] = color;
                meshColors[(i * 2) + 1] = color;
 
                // Width
                float width;
                if(widths.Length == 0)
                    width = 1;
                else if(widths.Length == 1)
                    width = widths[0];
                else if(widths.Length == 2)
                    width = Mathf.Lerp(widths[1], widths[0], ratio);
                else
                {
                    float widthRatio = widths.Length - 1 - ratio * (widths.Length-1);
                    if(widthRatio == widths.Length-1)
                        width = widths[widths.Length-1];
                    else
                    {
                        int min = (int) Mathf.Floor(widthRatio);
                        float lerp = widthRatio - min;
                        width = Mathf.Lerp(widths[min+0], widths[min+1], lerp);
                    }
                }
 
                // Vertices
                if(faceCamera)
                {
                    Vector3 from = i == displayCnt-1 ?  points[i-1]   : point;
                    Vector3 to = i == displayCnt-1 ?    point    : points[i+1];
                    Vector3 pointDir = to - from;
                    Vector3 vectorToCamera = cameraPos - point;
                    Vector3 perpendicular = Vector3.Cross(pointDir, vectorToCamera).normalized;
                    vertices[i * 2 + 0] = point + perpendicular * width * 0.5f;
                    vertices[i * 2 + 1] = point - perpendicular * width * 0.5f;
                }
                else
                {
                    vertices[i * 2 + 0] = point + pointsUp[i] * width * 0.5f;
                    vertices[i * 2 + 1] = point - pointsUp[i] * width * 0.5f;
                }
 
                // UVs
                uvs[i * 2 + 0] = new Vector2(ratio , 0);
                uvs[i * 2 + 1] = new Vector2(ratio, 1);
 
                if(i > 0)
                {
                    // Triangles
                    int triIndex = (i - 1) * 6;
                    int vertIndex = i * 2;
                    triangles[triIndex+0] = vertIndex - 2;
                    triangles[triIndex+1] = vertIndex - 1;
                    triangles[triIndex+2] = vertIndex - 0;
 
                    triangles[triIndex+3] = vertIndex + 0;
                    triangles[triIndex+4] = vertIndex - 1;
                    triangles[triIndex+5] = vertIndex + 1;
                }
            }
            trail.transform.position = Vector3.zero;
            trail.transform.rotation = Quaternion.identity;
            mesh.Clear();
            mesh.vertices = vertices;
            mesh.colors = meshColors;
            mesh.uv = uvs;
            mesh.triangles = triangles;
        }
        catch(System.Exception e)
        {
            print(e);
        }
    }
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox