How to create ghost or after image on an animated sprite?

Hey there.
I am having an impossible time recreating this effect for my 2D platformer:

I’d like to create a sprite trail just like in the example but I can’t seem to figure it out; I can almost create the effect for just one still sprite with particles, but to have it trail after the sprite animations seems way out of my grasp. I’ve tried trail and line renderer but both those don’t create a trail from the sprite sheet, only their own little separate effect.

Maybe this will help unity - Create a trailing, ghosting effect of a sprite - Game Development Stack Exchange

You can apply that technique for current animation sprite. Something like that I think:

particleSystem.renderer.material.mainTexture = spriteObject.GetComponent<SpriteRenderer>().sprite.texture

I didn’t try to do this, will test it later and update this comment.

Ok, previous solution works for me fine, but not as good as I want, because it updates every trail part texture continuously.
Here’s another small example I’ve wrote for couple of minutes:

using UnityEngine;
using System.Collections;

    void Start()
    {
        InvokeRepeating("SpawnTrail", 0, 0.2f); // replace 0.2f with needed repeatRate
    }

    void SpawnTrail()
    {
        GameObject trailPart = new GameObject();
        SpriteRenderer trailPartRenderer = trailPart.AddComponent<SpriteRenderer>();
        trailPartRenderer.sprite = GetComponent<SpriteRenderer>().sprite;
        trailPart.transform.position = transform.position;
        Destroy(trailPart, 0.5f); // replace 0.5f with needed lifeTime

        StartCoroutine("FadeTrailPart", trailPartRenderer);
    }

    IEnumerator FadeTrailPart(SpriteRenderer trailPartRenderer)
    {
        Color color = trailPartRenderer.color;
        color.a -= 0.5f; // replace 0.5f with needed alpha decrement
        trailPartRenderer.color = color;

        yield return new WaitForEndOfFrame();
    }

Add this code to your character’s script on GameObject with Animator component.
The code is pretty simple and works perfect for me. I wrote it in hurry, so it can be refactored and optimized:) For example you could reuse trailPart gameobjects instead of instantiating and destroying it everytime (object pool technique), etc., depends on you;)

2 Likes

Well this is some pretty magnificent script you wrote for me; so pardon my while I show some cringe-worthy noobness.

The ghost doesn’t appear to be be affected by my Flip () function. so while it looks great heading in the correct direction. if i turn around my sprite the ghost doesn’t turn as well :sweat_smile:

void Flip ()
    {
        facingRight = !facingRight;
        Vector3 theScale = transform.localScale;
        theScale.x *= -1;
        transform.localScale = theScale;
    }

Think you can help iron out this last wrinkle?

Ok, try this:

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

List<GameObject> trailParts = new List<GameObject>();

void Start()
{
    InvokeRepeating("SpawnTrailPart", 0, 0.2f); // replace 0.2f with needed repeatRate
}

void SpawnTrailPart()
{
    GameObject trailPart = new GameObject();
    SpriteRenderer trailPartRenderer = trailPart.AddComponent<SpriteRenderer>();
    trailPartRenderer.sprite = GetComponent<SpriteRenderer>().sprite;
    trailPart.transform.position = transform.position;
    trailParts.Add(trailPart);

    StartCoroutine(FadeTrailPart(trailPartRenderer));
    StartCoroutine(DestroyTrailPart(trailPart, 0.5f)); // replace 0.5f with needed lifeTime
}

IEnumerator FadeTrailPart(SpriteRenderer trailPartRenderer)
{
    Color color = trailPartRenderer.color;
    color.a -= 0.5f; // replace 0.5f with needed alpha decrement
    trailPartRenderer.color = color;
   
    yield return new WaitForEndOfFrame();
}

IEnumerator DestroyTrailPart(GameObject trailPart, float delay)
{
    yield return new WaitForSeconds(delay);

    trailParts.Remove(trailPart);
    Destroy(trailPart);
}
  
void Flip()
{
    facingRight = !facingRight;
    Vector3 theScale = transform.localScale;
    theScale.x *= -1;
    transform.localScale = theScale;
     
    FlipTrail();
}

void FlipTrail()
{
    foreach (GameObject trailPart in trailParts)
    {
        Vector3 trailPartLocalScale = trailPart.transform.localScale;
        trailPartLocalScale.x *= -1;
        trailPart.transform.localScale = trailPartLocalScale;
    }
}

But I highly recommend you to use some kind of object pool system instead of new/Instantiate/Destroy calls:)

1 Like

Alright , updated the code to our new attempt but no luck. Seems like these ghosts just don’t want to flip! Script still runs but acts unchanged.

I will also haphazard this guess(which might be completely wrong since I know very little);
The Hierarchy is instantiating the ghosts as “New Game Object”, and I feel like maybe it’s supposed to create them as trailPart for the “foreach” function to work.

Weird, it works perfect for my test project. Can you share your project?

I’d be happy to share with you, if I knew how.

Hmm, upload it on dropbox or something and put the public link here

[Link Disabled]

Project called “character move tutorial without rigidbody” inside unity fails direcotry . I should probably rename it haha

Which scene should I open to test it?:slight_smile:

only have 1 scene called Level 1 haha

Weird, Level 1 scene is empty, did you save it?

There are several scenes inside “ExampleScenes” folder:)

Unity Fails2\Character Move Tutorial without rigidbody\Assets\Levels

Yep, but there’s nothing on this scene, there’s no any object in the hierarchy

Wow that’s wild- those examples are all part of A* pathfinding i think.

Let me run a “save as” and see if I can send that over

I promise these scenes aren’t empty. They work on my second machine if I go into unity File>Open Project>Open Other>Character Move Tutorial Without Rigidbody>Select folder.

Guess the whole project is required for the scenes inside it to work

Oh, such a silly mistake!!!:slight_smile:

That’s all you need:

void Start()
{
    InvokeRepeating("SpawnTrailPart", 0, 0.2f); // replace 0.2f with needed repeatRate
}

void SpawnTrailPart()
{
    GameObject trailPart = new GameObject();
    SpriteRenderer trailPartRenderer = trailPart.AddComponent<SpriteRenderer>();
    trailPartRenderer.sprite = GetComponent<SpriteRenderer>().sprite;
    trailPart.transform.position = transform.position;
    trailPart.transform.localScale = transform.localScale; // We forgot about this line!!!
    trailParts.Add(trailPart);

    StartCoroutine(FadeTrailPart(trailPartRenderer));
    Destroy(trailPart, 0.5f); // replace 0.5f with needed lifeTime
}

IEnumerator FadeTrailPart(SpriteRenderer trailPartRenderer)
{
    Color color = trailPartRenderer.color;
    color.a -= 0.5f; // replace 0.5f with needed alpha decrement
    trailPartRenderer.color = color;

    yield return new WaitForEndOfFrame();
}

I completely forgot about trailPart.transform.localScale
You don’t need DestroyTrailPart and FlipTrail methods and you don’t need List trailParts:)

1 Like

Oh that’s much better, friend!
I don’t need this either, right?

using System.Collections.Generic;

trailParts.Add(trailPart);

Thank you so much!
if you got another two seconds I’d be delighted to know how to change it’s color as well?

right.

trailPartRenderer.color = Color.blue; // or new Color(0, 0, 255) or whatever you want