Search Unity

2D effects - tips?

Discussion in '2D' started by RuneShiStorm, May 23, 2017.

  1. RuneShiStorm

    RuneShiStorm

    Joined:
    Apr 28, 2017
    Posts:
    264
    Hi all!
    I'm poking around the "built-in-settings" in Unity - trying to find some sort of "Effect Panel".
    No luck. I know Unity is not Photoshop but was just hoping for something to mess around with. Lights, effects, shadows, etc... I guess its all programming in the end... I'm new to Unity btw. so I might have missed something.
    Anyone have tips on how to make an effect that creates a "delayed ghost" of my character, similar to CastleVania. (picture attacked)



    If its possible to post codes for others to copy, feel free to post them here.
    Just describe your "effect" and how to implement it! :D
     
  2. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    I wrote this for someone else asking about this awhile back:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class SpriteAfterImage : MonoBehaviour {
    6.     [Tooltip("The color each after-image will fade to over its lifetime. Alpha of 0 is recommended")]
    7.     public Color finalColor = Color.clear;
    8.  
    9.     [Tooltip("The amount of time an after-image will take to fade away.")]
    10.     public float trailLifetime = .25f;
    11.  
    12.     [Tooltip("The distance this object must move to spawn one after-image.")]
    13.     public float distancePerSpawn = .1f;
    14.  
    15.     [Tooltip("Optimization - number of after-images to create before the effect starts, to reduce the start-up load.")]
    16.     public int spawnOnStart = 0;
    17.  
    18.  
    19.     private SpriteRenderer mainSpriteRenderer;    // the sprite renderer to trail after
    20.     private List<SpriteRenderer> readyObjects;    // the list of objects ready to be shown
    21.     private float distanceTraveledSinceLastSpawn; // the distance this object has moved since the last object was shown
    22.     private Vector3 lastSpawnPosition;            // the position the last object was spawned
    23.     private Color initialColor;
    24.  
    25.  
    26.     private void Awake() {
    27.         // get the sprite renderer on this object
    28.         mainSpriteRenderer = GetComponent<SpriteRenderer>();
    29.         initialColor = mainSpriteRenderer.color;
    30.  
    31.         // initialize the empty list
    32.         readyObjects = new List<SpriteRenderer>();
    33.  
    34.         // optionally populate list beforehand with objects to use
    35.         for(int i = 0; i < spawnOnStart; i++) {
    36.             readyObjects.Add(makeSpriteObject());
    37.         }
    38.     }
    39.  
    40.     private void OnEnable() {
    41.         StartCoroutine(trailCoroutine());
    42.     }
    43.  
    44.     // function to create a sprite gameobject ready for use
    45.     private SpriteRenderer makeSpriteObject() {
    46.         // create a gameobject named "TrailSprite" with a SpriteRenderer component
    47.         GameObject spriteObject = new GameObject("TrailSprite", typeof(SpriteRenderer));
    48.  
    49.         // parent the object to this object so that it follows it
    50.         spriteObject.transform.SetParent(transform);
    51.  
    52.         // center it on this object
    53.         spriteObject.transform.localPosition = Vector3.zero;
    54.  
    55.         // hide it
    56.         spriteObject.SetActive(false);
    57.  
    58.         return spriteObject.GetComponent<SpriteRenderer>();
    59.     }
    60.  
    61.     private IEnumerator trailCoroutine() {
    62.         // keep running while this component is enabled
    63.         while(enabled) {
    64.             // get the distance between the current position and the last position
    65.             // a trail object was spawned
    66.             distanceTraveledSinceLastSpawn = Vector2.Distance(lastSpawnPosition, transform.position);
    67.  
    68.             // if that distance is greater than the specified distance per spawn
    69.             if(distanceTraveledSinceLastSpawn > distancePerSpawn) {
    70.                 // if there aren't any objects ready to show, spawn a new one
    71.                 if(readyObjects.Count == 0) {
    72.                     // add that object's sprite renderer to the trail list
    73.                     readyObjects.Add(makeSpriteObject());
    74.                 }
    75.  
    76.                 // get the next object in the ready list
    77.                 SpriteRenderer nextObject = readyObjects[0];
    78.  
    79.                 // set this trailSprite to reflect the current player sprite
    80.                 nextObject.sprite = mainSpriteRenderer.sprite;
    81.  
    82.                 // this makes it so that the trail will render behind the main sprite
    83.                 nextObject.sortingLayerID = mainSpriteRenderer.sortingLayerID;
    84.                 nextObject.sortingOrder = mainSpriteRenderer.sortingOrder - 1;
    85.  
    86.                 // set it loose in the world
    87.                 nextObject.transform.SetParent(null, true);
    88.  
    89.                 // show it
    90.                 nextObject.gameObject.SetActive(true);
    91.  
    92.                 // start it fading out over time
    93.                 StartCoroutine(fadeOut(nextObject));
    94.  
    95.                 // remove it from the list of ready objects
    96.                 readyObjects.Remove(nextObject);
    97.  
    98.                 // save this position as the last spawned position
    99.                 lastSpawnPosition = transform.position;
    100.  
    101.                 // reset the distance traveled
    102.                 distanceTraveledSinceLastSpawn = 0;
    103.             }
    104.             // wait until next frame to continue the loop
    105.             yield return null;
    106.         }
    107.  
    108.         // reduce number of sprites back to original pool size
    109.         foreach(SpriteRenderer sprite in this.readyObjects) {
    110.             if(this.readyObjects.Count > spawnOnStart) {
    111.                 Destroy(sprite.gameObject);
    112.             } else {
    113.                 resetObject(sprite);
    114.             }
    115.         }
    116.     }
    117.  
    118.     private IEnumerator fadeOut(SpriteRenderer sprite) {
    119.         float timeElapsed = 0;
    120.  
    121.         // while the elapsed time is less than the specified trailLifetime
    122.         while(timeElapsed < trailLifetime) {
    123.             // get a number between 0 and 1 that represents how much time has passed
    124.             // 0 = no time has passed, 1 = trailLifetime seconds has passed
    125.             float progress = Mathf.Clamp01(timeElapsed / trailLifetime);
    126.  
    127.             // linearly interpolates between the initial color and the final color
    128.             // based on the value of progress (0 to 1)
    129.             sprite.color = Color.Lerp(initialColor, finalColor, progress);
    130.  
    131.             // track the time passed
    132.             timeElapsed += Time.deltaTime;
    133.  
    134.             // wait until next frame to continue the loop
    135.             yield return null;
    136.         }
    137.  
    138.         // reset the object so that it can be reused
    139.         resetObject(sprite);
    140.     }
    141.  
    142.     // resets the object so that it is ready to use again
    143.     private void resetObject(SpriteRenderer sprite) {
    144.         // hide the sprite
    145.         sprite.gameObject.SetActive(false);
    146.  
    147.         // reset the tint to default
    148.         sprite.color = initialColor;
    149.  
    150.         // parent it to this object
    151.         sprite.transform.SetParent(transform);
    152.  
    153.         // center it on this object
    154.         sprite.transform.localPosition = Vector3.zero;
    155.  
    156.         // add it to the ready list
    157.         readyObjects.Add(sprite);
    158.     }
    159. }
    Put this on anything with a sprite renderer, and it configure it in the inspector. It can be enabled/disabled to control when the effect is active.

    Right now the initial color matches the target spriterenderer's tint, but that could be changed to a configurable color.

    I commented the crap out of it, so if you want to learn, read through the code and see if you can understand what's going on. I'd be happy to answer any questions you have about it.
     
    Last edited: May 23, 2017
    larku, qqqbbb and vakabaka like this.
  3. RuneShiStorm

    RuneShiStorm

    Joined:
    Apr 28, 2017
    Posts:
    264
    Wow! It actually worked! Exactly what I was looking for.
    The SpriteAfterEffect is big and moving in opposite direction... I will look into your code when I have time and see if I can crack it. Thank you soo much though!
     
  4. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    No problem! Glad it works for you.

    You'll need to copy the local scaling from your character to the sprites as they're shown. I would do this in the "trailCoroutine", right next to where the sprite is set. "mainSpriteRenderer" is the reference to your character's SpriteRenderer, so adding "nextObject.transform.localScale = mainSpriteRenderer.transform.localScale;" would do it i think.
     
    RuneShiStorm likes this.
  5. RuneShiStorm

    RuneShiStorm

    Joined:
    Apr 28, 2017
    Posts:
    264

    thanks!
    I copied "nextObject.transform.localScale = mainSpriteRenderer.transform.localScale;" into the script, inderneath

    nextObject.sortingLayerID = mainSpriteRenderer.sortingLayerID;
    nextObject.sortingOrder = mainSpriteRenderer.sortingOrder - 1;

    and now the "SpriteAfterEffect" is tiny now... Not sure how/where to write the "local scaling"-sentence in "trailCorotine" tough...
     
  6. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Well, depending on how your character is getting it's size, (through several different parent object scales, etc) you may need to change how you get the scale.

    I have a few ideas for how to fix that. First lets try this:

    Leave that line where it is, and in the function "makeSpriteObject", change the "SetParent(transform)" to "SetParent(mainSpriteRenderer.transform.parent)".

    That way the after-images have the same parent as the character, and by copying the character's local scale, the after-images should be the same size.
     
    Last edited: May 26, 2017
    RuneShiStorm likes this.
  7. RuneShiStorm

    RuneShiStorm

    Joined:
    Apr 28, 2017
    Posts:
    264
    W
    Seem to work, kinda... Its looking better but still has those small animations as you can see.
    And It only works ONCE, meaning, first time I jump it leaves a nice effect in proper size, combined with the smaller ones.
    But second time I jump it only leave the smaller effects, (those small dots)
    Im very noob at programing still, I should actually learn myself... But Im currently working on dialog boxes so... Unless you have time to spare, ignoe me and I will try me best to solve it myself! :) But thanks anyway!
     
  8. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    I'm happy to try and help you fix this. Basically we just need the copies to have the same visible scale as your sprite when they are spawned.

    Try this code where I've added a line to copy the world-scale of the sprite:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.    
    5. public class SpriteAfterImage : MonoBehaviour {
    6.     [Tooltip("The color each after-image will fade to over its lifetime. Alpha of 0 is recommended")]
    7.     public Color finalColor = Color.clear;
    8.  
    9.     [Tooltip("The amount of time an after-image will take to fade away.")]
    10.     public float trailLifetime = .25f;
    11.  
    12.     [Tooltip("The distance this object must move to spawn one after-image.")]
    13.     public float distancePerSpawn = .1f;
    14.  
    15.     [Tooltip("Optimization - number of after-images to create before the effect starts, to reduce the start-up load.")]
    16.     public int spawnOnStart = 0;
    17.  
    18.  
    19.     private SpriteRenderer mainSpriteRenderer;    // the sprite renderer to trail after
    20.     private List<SpriteRenderer> readyObjects;    // the list of objects ready to be shown
    21.     private float distanceTraveledSinceLastSpawn; // the distance this object has moved since the last object was shown
    22.     private Vector3 lastSpawnPosition;            // the position the last object was spawned
    23.     private Color initialColor;
    24.  
    25.  
    26.     private void Awake() {
    27.         // get the sprite renderer on this object
    28.         mainSpriteRenderer = GetComponent<SpriteRenderer>();
    29.         initialColor = mainSpriteRenderer.color;
    30.  
    31.         // initialize the empty list
    32.         readyObjects = new List<SpriteRenderer>();
    33.  
    34.         // optionally populate list beforehand with objects to use
    35.         for(int i = 0; i < spawnOnStart; i++) {
    36.             readyObjects.Add(makeSpriteObject());
    37.         }
    38.     }
    39.  
    40.     private void OnEnable() {
    41.         StartCoroutine(trailCoroutine());
    42.     }
    43.  
    44.     // function to create a sprite gameobject ready for use
    45.     private SpriteRenderer makeSpriteObject() {
    46.         // create a gameobject named "TrailSprite" with a SpriteRenderer component
    47.         GameObject spriteObject = new GameObject("TrailSprite", typeof(SpriteRenderer));
    48.  
    49.         // parent the object to this object so that it follows it
    50.         spriteObject.transform.SetParent(transform);
    51.  
    52.         // center it on this object
    53.         spriteObject.transform.localPosition = Vector3.zero;
    54.  
    55.         // hide it
    56.         spriteObject.SetActive(false);
    57.  
    58.         return spriteObject.GetComponent<SpriteRenderer>();
    59.     }
    60.  
    61.     private IEnumerator trailCoroutine() {
    62.         // keep running while this component is enabled
    63.         while(enabled) {
    64.             // get the distance between the current position and the last position
    65.             // a trail object was spawned
    66.             distanceTraveledSinceLastSpawn = Vector2.Distance(lastSpawnPosition, transform.position);
    67.  
    68.             // if that distance is greater than the specified distance per spawn
    69.             if(distanceTraveledSinceLastSpawn > distancePerSpawn) {
    70.                 // if there aren't any objects ready to show, spawn a new one
    71.                 if(readyObjects.Count == 0) {
    72.                     // add that object's sprite renderer to the trail list
    73.                     readyObjects.Add(makeSpriteObject());
    74.                 }
    75.  
    76.                 // get the next object in the ready list
    77.                 SpriteRenderer nextObject = readyObjects[0];
    78.  
    79.                 // set this trailSprite to reflect the current player sprite
    80.                 nextObject.sprite = mainSpriteRenderer.sprite;
    81.  
    82.                 // this makes it so that the trail will render behind the main sprite
    83.                 nextObject.sortingLayerID = mainSpriteRenderer.sortingLayerID;
    84.                 nextObject.sortingOrder = mainSpriteRenderer.sortingOrder - 1;
    85.  
    86.                 // set it loose in the world
    87.                 nextObject.transform.SetParent(null, true);
    88.  
    89.                 // match the copy's scale to the sprite's world-space scale
    90.                 nextObject.transform.localScale = mainSpriteRenderer.transform.lossyScale;
    91.  
    92.                 // show it
    93.                 nextObject.gameObject.SetActive(true);
    94.  
    95.                 // start it fading out over time
    96.                 StartCoroutine(fadeOut(nextObject));
    97.  
    98.                 // remove it from the list of ready objects
    99.                 readyObjects.Remove(nextObject);
    100.  
    101.                 // save this position as the last spawned position
    102.                 lastSpawnPosition = transform.position;
    103.  
    104.                 // reset the distance traveled
    105.                 distanceTraveledSinceLastSpawn = 0;
    106.             }
    107.             // wait until next frame to continue the loop
    108.             yield return null;
    109.         }
    110.  
    111.         // reduce number of sprites back to original pool size
    112.         foreach(SpriteRenderer sprite in this.readyObjects) {
    113.             if(this.readyObjects.Count > spawnOnStart) {
    114.                 Destroy(sprite.gameObject);
    115.             } else {
    116.                 resetObject(sprite);
    117.             }
    118.         }
    119.     }
    120.  
    121.     private IEnumerator fadeOut(SpriteRenderer sprite) {
    122.         float timeElapsed = 0;
    123.  
    124.         // while the elapsed time is less than the specified trailLifetime
    125.         while(timeElapsed < trailLifetime) {
    126.             // get a number between 0 and 1 that represents how much time has passed
    127.             // 0 = no time has passed, 1 = trailLifetime seconds has passed
    128.             float progress = Mathf.Clamp01(timeElapsed / trailLifetime);
    129.  
    130.             // linearly interpolates between the initial color and the final color
    131.             // based on the value of progress (0 to 1)
    132.             sprite.color = Color.Lerp(initialColor, finalColor, progress);
    133.  
    134.             // track the time passed
    135.             timeElapsed += Time.deltaTime;
    136.  
    137.             // wait until next frame to continue the loop
    138.             yield return null;
    139.         }
    140.  
    141.         // reset the object so that it can be reused
    142.         resetObject(sprite);
    143.     }
    144.  
    145.     // resets the object so that it is ready to use again
    146.     private void resetObject(SpriteRenderer sprite) {
    147.         // hide the sprite
    148.         sprite.gameObject.SetActive(false);
    149.  
    150.         // reset the tint to default
    151.         sprite.color = initialColor;
    152.  
    153.         // parent it to this object
    154.         sprite.transform.SetParent(transform);
    155.  
    156.         // center it on this object
    157.         sprite.transform.localPosition = Vector3.zero;
    158.  
    159.         // add it to the ready list
    160.         readyObjects.Add(sprite);
    161.     }
    162. }
     
  9. RuneShiStorm

    RuneShiStorm

    Joined:
    Apr 28, 2017
    Posts:
    264
    yes! It worked! Nailed it :) Thank you soo much! Now I will just decide collor of effect and distance etc :)
     
    LiterallyJeff likes this.