- Home /
Trail rendering for car skid marks
Hey there! I'm trying to create a racing game, and one effect I'd like to achieve is skid marks appearing on the ground when drifting. I've tried a few things with various levels of success. The first thing I tried was the built-in TrailRenderer. This had two problems for this application. The first issue is the "billboard" effect, which as far as I know can't be turned off, and the second was that there appeared to be no way to stop the trail emitting without removing the entire trail, which is an absolute necessity.
I then found this code, which did a slightly better job, in that it didn't render the trail as a billboard, and, with a bit of tweaking, could be made to fade the trail out. My problem with this approach, though, was that it was built to only support one trail. This caused weird artefacts to occur if you drifted more than once in quick succession, as it tried to join the trails together. I attempted to build a two-class system, one of which was a trail emitter, and one of which was a trail itself, the emitter keeping track of the trails it has spawned, but this failed spectacularly. I'm just trying to see if anyone knows of a way I could achieve this? Essentially all I need is something that emits trails, and will allow me to stop emitting them, and let them fade out, but also let me start creating a new trail without affecting the old one. Does anyone have any tips on how to go about this?
Answer by Hoeloe · May 23, 2013 at 02:52 PM
I solved this one myself. I thought I should post the code here. It uses two classes, Trail and TrailEmitter. To use the code, you attach the TrailEmitter to some object, and when you call "NewTrail()" on it, it will start emitting a trail. Calling "EndTrail()" will stop the trail, and allow you to start a new one. The Trail class is used by the TrailEmitter to actually render the trail.
Here is Trail.cs:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Trails
{
// Created by Edward Kay-Coles a.k.a Hoeloe
public class Trail {
//Properties of the trail
private float width;
private float decay;
private Material m;
private int rough;
private int maxRough;
private bool softSource;
//Parent object
private Transform par;
//Pieces for the mesh generation
private GameObject trail;
private MeshFilter filter;
private MeshRenderer render;
private Mesh mesh;
//Lists storing the mesh data
private LinkedList<Vector3> verts = new LinkedList<Vector3>();
private LinkedList<Vector2> uvs = new LinkedList<Vector2>();
private LinkedList<int> tris = new LinkedList<int>();
private LinkedList<Color> cols = new LinkedList<Color>();
//Check if the trail is still being generated, and if it has completely faded
private bool finished = false;
private bool dead = false;
//For registering if the object has been removed from the game (so you don't have to store it any more)
public bool Dead
{
get { return dead; }
private set
{
dead = true;
GameObject.Destroy(trail);
}
}
//Set up the trail object, and parameters
public Trail(Transform parent, Material material, float decayTime, int roughness, bool softSourceEdges, float wid = 0.1f)
{
softSource = softSourceEdges;
maxRough = roughness;
rough = 0;
decay = decayTime;
par = parent;
width = wid;
m = material;
trail = new GameObject("Trail");
filter = trail.AddComponent(typeof(MeshFilter)) as MeshFilter;
render = trail.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
mesh = new Mesh();
render.material = m;
filter.mesh = mesh;
}
//Call this when the trail should stop emitting
public void Finish()
{
finished = true;
}
//Tells you if the trail is emitting or not
public bool Finished
{
get { return finished; }
}
// Updates the state of the trail - Note: this must be called manually
public void Update ()
{
if(!finished) //Only add new segments if the trail is not being emitted
{
//Decides how often to generate new segments. Smaller roughness values are smoother, but more expensive
if(rough > 0)
rough --;
else
{
rough = maxRough;
//Checks in which order we should add vertices (to keep a consistent shape)
bool odd = !(verts.Count%4 == 0);
//Add new vertices as the current position
verts.AddLast(par.position + (odd?-1:1)*par.up*width/2f);
verts.AddLast(par.position + (odd?1:-1)*par.up*width/2f);
//Fades out the newest vertices if soft source edges is set to true
if(softSource)
{
if(cols.Count >= 4)
{
cols.Last.Value = Color.white;
cols.Last.Previous.Value = Color.white;
}
cols.AddLast(Color.clear);
cols.AddLast(Color.clear);
}
else //Sets the first vertices to fade out, but leaves the rest solid
{
if(cols.Count >= 2)
{
cols.AddLast(Color.white);
cols.AddLast(Color.white);
}
else
{
cols.AddLast(Color.clear);
cols.AddLast(Color.clear);
}
}
if(!odd) //Set up uv mapping
{
uvs.AddLast(new Vector2(1,0));
uvs.AddLast(new Vector2(0,0));
}
else
{
uvs.AddLast(new Vector2(0,1));
uvs.AddLast(new Vector2(1,1));
}
//Don't try to draw the trail unless we have at least a rectangle
if(verts.Count < 4) return;
//Add new triangles to the mesh
int c = verts.Count;
tris.AddLast(c-4);
tris.AddLast(c-3);
tris.AddLast(c-2);
tris.AddLast(c-4);
tris.AddLast(c-2);
tris.AddLast(c-1);
//Copy lists to arrays, ready to rebuild the mesh
Vector3[] v = new Vector3[c];
Vector2[] uv = new Vector2[c];
int[] t = new int[tris.Count];
verts.CopyTo(v,0);
uvs.CopyTo(uv,0);
tris.CopyTo(t,0);
//Build the mesh
mesh.vertices = v;
mesh.triangles = t;
mesh.uv = uv;
}
}
//The next section updates the colours in the mesh
int i = cols.Count;
//If we have no vertices, don't bother trying to update
if(i == 0)
return;
//This is for checking if the trail has completely faded or not
bool alive = false;
//Essentially a foreach loop over the colours, but allowing editing to each node as it goes
LinkedListNode<Color> d = cols.First;
do
{
if(d.Value.a > 0)
{
Color t = d.Value;
alive = true;
//Decrease the alpha value, to 0 if it would be decreased to negative
t.a -= Mathf.Min(Time.deltaTime/decay,t.a);
d.Value = t;
}
d = d.Next;
}while(d != null);
//Trail should be removed if it is not emitting and has faded out
if(!alive && finished)
Dead = true;
else
{
//Doesn't set the colours if the number of vertices doesn't match up for whatever reason
if(i != mesh.vertices.Length)
return;
//Copy the colours to an array and build the mesh colours
Color[] cs = new Color[i];
cols.CopyTo(cs,0);
mesh.colors = cs;
}
}
}
}
And this is TrailEmitter.cs:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Trails;
namespace Trails
{
// Created by Edward Kay-Coles a.k.a Hoeloe
public class TrailEmitter : MonoBehaviour {
//Stores all live trails
private LinkedList<Trail> trails = new LinkedList<Trail>();
//Parameters
public float width = 0.1f;
public float decayTime = 1f;
public Material material;
public int roughness = 0;
public bool softSourceEnd = false;
//Checks if the most recent trail is active or not
public bool Active
{
get { return (trails.Count == 0?false:(!trails.Last.Value.Finished)); }
}
// Update is called once per frame
void Update ()
{
//Don't update if there are no trails
if(trails.Count == 0) return;
//Essentially a foreach loop, allowing trails to be removed from the list if they are finished
LinkedListNode<Trail> t = trails.First;
LinkedListNode<Trail> n;
do
{
n = t.Next;
t.Value.Update();
if(t.Value.Dead)
trails.Remove(t);
t = n;
}while(n != null);
}
/// <summary>
/// Creates a new trail.
/// </summary>
public void NewTrail()
{
//Stops emitting the last trail and passes the parameters onto a new one
EndTrail();
trails.AddLast(new Trail(transform, material, decayTime, roughness, softSourceEnd, width));
}
/// <summary>
/// Deactivate the last trail if it was already active.
/// </summary>
public void EndTrail()
{
if(!Active) return;
trails.Last.Value.Finish();
}
}
}
I thought I'd post this here because I had such trouble finding the solution, so I thought it would be a good idea to let others see the one I found.
Sorry to dig up this old thread, but how do I actually call NewTrail() from another script on the same object that handles the car movement?
Answer by BoontjieBoon · Dec 27, 2016 at 06:05 PM
Hi @Hoeloe, thanks for the code, it works great.
I had to tweak it a bit because it wasn't rendering continuously (probably winding of the triangles). I just changed it to only add new vertices after the parent moved a minimum distance and dropped the odd variable.
...
Vector3 currentPosition = par.transform.position + (Vector3)(par.transform.localToWorldMatrix * positionOffset);
if (Vector3.Distance(previousPosition, currentPosition) > minimumSegmentLength) {
previousPosition = currentPosition;
//Add new vertices as the current position
Vector3 offset = par.right * width / 2f;
verts.AddLast(currentPosition - offset);
verts.AddLast(currentPosition + offset);
...
//Add new triangles to the mesh
int c = verts.Count;
tris.AddLast(c - 4);
tris.AddLast(c - 2);
tris.AddLast(c - 3);
tris.AddLast(c - 3);
tris.AddLast(c - 2);
tris.AddLast(c - 1);
Your answer
Follow this Question
Related Questions
Does anyone have figured the WheelFrictionCurve struct ? 2 Answers
Applying Torque to wheelcollider 1 Answer
Realistically drivable car 0 Answers
How to make car accelerate/decelerate over time 2 Answers
Slotcar game 1 Answer