Trailing in Unity

Antoine Fortin
6 min readMay 15, 2023

--

Let’s start with a basic sphere moving:

void Update()
{
UpdatePosition();
DoVelocityStuff();
}

The first thing is to take a look at UpdatePosition function, that makes the sphere moves.

    void UpdatePosition()
{
if (Input.GetKey("w"))
{
rb.AddForce(new Vector3(0.0f, 0.0f, force));
}
if (Input.GetKey("s"))
{
rb.AddForce(new Vector3(0.0f, 0.0f, -force));
}
if (Input.GetKey("a"))
{
rb.AddForce(new Vector3(-force, 0.0f, 0.0f));
}
if (Input.GetKey("d"))
{
rb.AddForce(new Vector3(force, 0.0f, 0.0f));
}
}

Basic stuff. But now, let’s take a look at velocity of the movement of the sphere, this way, we will know from where to start generating the particles based on the direction of the sphere.

The velocity vector of the rigidbody. It represents the rate of change of Rigidbody position.

That is what we need to start building our system. Let’s start building a simple GUI tools that will allow us to show the velocity of the sphere.

    void Update()
{
UpdatePosition();
DebugVelocity();
}
void DebugVelocity()
{
Vector3 velocity = rb.velocity.normalized;
Debug.DrawLine(transform.position, transform.position + velocity, Color.red);
}

I added the velocity normalized vector to the position of the Sphere moving. Giving us this:

The red arrow representing the direction on the moving sphere. For starting to generate our particles, we will need to take the opposite of this vector making it point reverse side.

While V is the velocity of the object. we also want to transform this velocity into a reverse of this vector. Mostly like we done on this super dirty paint drawing.

We have V, and luckily for us, the only step missing is the inverse of this vector. We can compute it in code and then show it with the following code:

    void DebugVelocity()
{
Vector3 velocity = rb.velocity.normalized;
Debug.DrawLine(transform.position, transform.position + velocity, Color.red);
Vector3 oppositeVelocity = -velocity;
Debug.DrawLine(transform.position, transform.position + oppositeVelocity, Color.blue);

}

Giving us exactly what we need:

Particle stuff:

Create a prefab of particle

We now have a simple particle, what I want to do is to spawn it at the back of my moving sphere. I made a simple system using the source and the particle as two members.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TheSpawning : MonoBehaviour
{
public GameObject source;
public GameObject particle;

// Start is called before the first frame update
void Start()
{
StartCoroutine(EmitParticleTimer());
}


public void EmitParticle()
{
GameObject sourceParticle = Instantiate(particle);
sourceParticle.transform.position = source.transform.position + new Vector3(0.0f, 0.0f, -1.0f);
//sp.GetComponent<Rigidbody>().AddForce(new Vector3(0.0f, 4.0f, -1.0f));
}

private IEnumerator EmitParticleTimer()
{
EmitParticle();
yield return new WaitForSeconds(0.025f);
StartCoroutine(EmitParticleTimer());
}

}

This line makes sure to spawn at the back of the object. For none moving sphere.

sourceParticle.transform.position = source.transform.position + new Vector3(0.0f, 0.0f, -1.0f);

Alright, so we have a forward spawning working, but let’s remove some particle after a certain time. To achieve this, I will bind a script the makes a Gameobject deletes on its own.

We could use some fancy data structure, but I will simple add a script that makes it itself delete on the particle prefab.

Destruction goes as follow:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DestroyParticle : MonoBehaviour
{
public float destructionDelay = 3f;

private void Start()
{
Invoke("DestroyObject", destructionDelay);
}

private void DestroyObject()
{
Destroy(gameObject);
}
}

Attach this script to the particle prefab:

We now have self-deleting particles.

But as you can see, it always spawn new particles in the offset of the red vector. Wich is the velocity so we need to assign the generation based on the blue vector, that we computed last chapter.

Luckily we have this reverted velocity.

Vector3 oppositeVelocity = -velocity;

So we need to starts generating particle from this position.

To compute this position, I assign the inverse velocity from the MovementScript.cs. Then inside our Spawning, we can access the reverse velocity of the source.

public Vector3 inverseVelocity;    
void DebugVelocity()
{
Vector3 velocity = rb.velocity.normalized;
Debug.DrawLine(transform.position, transform.position + velocity, Color.red);
Vector3 _inverseVelocity = -velocity;
Debug.DrawLine(transform.position, transform.position + _inverseVelocity, Color.blue);
this.inverseVelocity = _inverseVelocity;
}
// TheMovement.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TheMovement : MonoBehaviour
{
public Rigidbody rb;
public float force;
public Vector3 velocity;
public Vector3 inverseVelocity;

void Update()
{
UpdatePosition();
DebugVelocity();
SetValue();
}

void DebugVelocity()
{
Vector3 velocity = rb.velocity.normalized;
Debug.DrawLine(transform.position, transform.position + velocity, Color.red);
Vector3 _inverseVelocity = -velocity;
Debug.DrawLine(transform.position, transform.position + _inverseVelocity, Color.blue);
this.inverseVelocity = _inverseVelocity;
}

void UpdatePosition()
{
if (Input.GetKey("w"))
{
rb.AddForce(new Vector3(0.0f, 0.0f, force));
}
if (Input.GetKey("s"))
{
rb.AddForce(new Vector3(0.0f, 0.0f, -force));
}
if (Input.GetKey("a"))
{
rb.AddForce(new Vector3(-force, 0.0f, 0.0f));
}
if (Input.GetKey("d"))
{
rb.AddForce(new Vector3(force, 0.0f, 0.0f));
}
}

}
// Spawning.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TheSpawning : MonoBehaviour
{
public GameObject source;
public GameObject particle;

// Start is called before the first frame update
void Start()
{
StartCoroutine(EmitParticleTimer());
}


public void EmitParticle()
{
GameObject sourceParticle = Instantiate(particle);
sourceParticle.transform.position = source.transform.position + source.GetComponent<TheMovement>().inverseVelocity;
//source.GetComponent<TheMovement>().inverseVelocity;
//sp.GetComponent<Rigidbody>().AddForce(new Vector3(0.0f, 4.0f, -1.0f));
}

private IEnumerator EmitParticleTimer()
{
EmitParticle();
yield return new WaitForSeconds(0.025f);
StartCoroutine(EmitParticleTimer());
}

}

Code design:

There is something we need to talk about and is the design of this feature. The way I built it was about taking the movement and velovity into it’s own aspect and system. Therefore, we need to achieve calculation on both the Emitter and the Emittee. Thing here, is that we deal with both the player that we treated as emitter for particles, and then treated this same element as the driving force for our particles generation.

Let’s just review this EmitParticle.:

    public void EmitParticle()
{
GameObject sourceParticle = Instantiate(particle);
sourceParticle.transform.position = source.transform.position + source.GetComponent<TheMovement>().inverseVelocity;
//source.GetComponent<TheMovement>().inverseVelocity;
//sp.GetComponent<Rigidbody>().AddForce(new Vector3(0.0f, 4.0f, -1.0f));
}

We need to take a look at

source.transform.position

Let’s review a bit of what source is all about.

public class TheSpawning : MonoBehaviour
{
public GameObject source;

Source is a gameobject that refers to the PlayerSphere.

So in shorts, the Spawning system handles the source and the particle. The source is the sphere, on wich we have a rigid body attached and compute position. Then, inside our SpawningSytem we can reach the reverse velocity from the sphere, wich is the source.

 public void EmitParticle()
{
GameObject sourceParticle = Instantiate(particle);
sourceParticle.transform.position = source.transform.position + source.GetComponent<TheMovement>().inverseVelocity;
//source.GetComponent<TheMovement>().inverseVelocity;
//sp.GetComponent<Rigidbody>().AddForce(new Vector3(0.0f, 4.0f, -1.0f));
}

So we just compute the particles based on the reversed velocity of the source. Wich is our sphere, we spawn those particle based on the reverse velocity of the player.

--

--

Antoine Fortin

In between Montreal and London, I love to write, read, learn and explore.