Search Unity

[ SOLVED ] Blink 'white' when hit

Discussion in 'Scripting' started by PlaymintDev, Dec 4, 2019.

  1. PlaymintDev

    PlaymintDev

    Joined:
    Jan 11, 2014
    Posts:
    306
    Hi,

    I've searched the web and forums and have come across many examples, but most of them seem to deal with singular objects when blinking a GameObject to white when hit.

    I have an imported model, it has 3 child components with multiple materials on each component.

    The closest I've got so far, only seems to find the first object and it's materials and applies 'white' to that child object only, but not the other 2 child objects using this code :
    Code (CSharp):
    1. foreach (Material part in GetComponentInChildren<MeshRenderer>().materials)
    2.             {
    3.                 part.SetColor("_Color", Color.white);
    4.             }
    So, question is, how can I get all the child objects materials, and rather than just setting the color to white, how can I 'blink' it, and then reset the colors back to normal ?
    Thanks
     
  2. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,605
    You changing only materials on one (first one) renderer. The fact what two components blinking means they both using same material. You need two loops for that, not one. First loop should iterate meshrenderers and second, inner loop, should iterate materials for every renderer.
    There is a better option. In Start method collect all unique materials from all renderers and when needed iterate them and change color.
     
    PlaymintDev and Kurt-Dekker like this.
  3. calpolican

    calpolican

    Joined:
    Feb 2, 2015
    Posts:
    204
    yeah, also remember that if you equip a weapon or an armor to the character in question you should add it to its materialToChange list.
    To do the change smoothly you could use a sin function of the time with a coroutine. You could also code the animation in the shader using shader lab and set it to depend of a bool that you can then turn on and off, but I'm not sure if perfromance wise is a good idea, cause I heard that both sides of a branch get evaluated regardless of wich one is chosen.
     
    PlaymintDev likes this.
  4. PlaymintDev

    PlaymintDev

    Joined:
    Jan 11, 2014
    Posts:
    306
    Thanks for the replies

    I'm still struggling a bit with this
    Code (CSharp):
    1. // Find all the children of the GameObject with MeshRenderers
    2.  
    3.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    4.  
    5.         // Cycle through each child object found with a MeshRenderer
    6.  
    7.         foreach (MeshRenderer rend in children)
    8.         {
    9.             // And for each child, cycle through each material
    10.  
    11.             foreach (Material mat in rend.materials)
    12.             {
    13.                 // Change color
    14.  
    15.                 mat.SetColor("_Color", Color.white);
    16.                 //mat.SetColor("_EmissionColor", Color.white);
    17.             }
    18.         }
    So, this works, but not the way I want it to, I'm simply testing out the functionality of the code by initiating it from an OnMouseDown method. I've succeeded in finding all the children, and all the materials and I am able to replace the color (but not _EmissionColor for some stupid reason, but that's a seperate issue !?! ).

    Would you recommend I do something like this in the Start function, and if so, how would I store all the materials ? I'm assuming within the foreach (Material mat in rend.materials) section, but unsure as to how to implement. Apologies, if this is easy to implement and I'm being super stupid here, I'm an artist, and I do struggle with some of the more advanced things in Unity. ( Even though flashing an object when hit sounds super basic :confused: ).

    Thanks

    EDIT : So my basic struggles are :
    Storing original materials somewhere,
    initiating flash over a given time period ( with _EmissionColor instead of Color would be ideal ),
    restoring the original materials.
     
  5. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,131
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class DamageEnemy : MonoBehaviour
    6.  
    7. {
    8.     public int health = 100;
    9.     private Color originalColour;
    10.  
    11.     private void Start()
    12.  
    13.     {
    14.         originalColour = GetComponent<Renderer>().material.color;
    15.     }
    16.  
    17.  
    18.     private void Update()
    19.  
    20.     {
    21.         if (health <= 0)
    22.  
    23.         {
    24.             Destroy(gameObject);
    25.         }
    26.     }
    27.  
    28.     private void OnCollisionEnter(Collision collision)
    29.  
    30.     {
    31.         if (collision.gameObject.tag == "bullet")
    32.  
    33.         {
    34.             health -= 50;
    35.             StartCoroutine("EnemyFlash");
    36.         }
    37.     }
    38.  
    39.     public IEnumerator EnemyFlash ()
    40.  
    41.     {
    42.         GetComponent<Renderer>().material.color = Color.red;
    43.         yield return new WaitForSeconds(0.1f);
    44.         GetComponent<Renderer>().material.color = originalColour;
    45.         StopCoroutine("EnemyFlash");
    46.     }
    47.  
    48. }
    49.  
    Have a look at this and see if it helps you, you need to get the original material before you change the colour so you can change it back again once the gameobject has been hit.
     
    PlaymintDev likes this.
  6. PlaymintDev

    PlaymintDev

    Joined:
    Jan 11, 2014
    Posts:
    306
    Thanks, yes, it helps.

    I do appreciate your feedback, but, not to be ungrateful, I've come across numerous examples like this, and I've scripted variations on this myself already, including just turning the renderer on and off to blink the object instead. The main problem I'm having / understanding is how to store an array of material colors and then restoring them all back to the surfaces they were on after the blink.

    EDIT :
    Ok, So I've decided to use a list to store the original colors, so I'm definitely making progress now. I'll post back again (probably soon) with any more problems I might have.
    Code (CSharp):
    1. {
    2.     List<Color> originalColors = new List<Color>();
    3.  
    4.     private void Start()
    5.     {
    6.         // Find all the children of the GameObject with MeshRenderers
    7.  
    8.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    9.  
    10.         // Cycle through each child object found with a MeshRenderer
    11.  
    12.         foreach (MeshRenderer rend in children)
    13.         {
    14.             // And for each child, cycle through each material
    15.  
    16.             foreach (Material mat in rend.materials)
    17.             {
    18.                 Debug.Log(mat.color); // Debug purposes only
    19.  
    20.                 // Store original colors
    21.  
    22.                 originalColors.Add(mat.color);
    23.             }
    24.         }
    25.     }
    26. }
     
    Last edited: Dec 5, 2019
  7. PlaymintDev

    PlaymintDev

    Joined:
    Jan 11, 2014
    Posts:
    306
    Thanks for all the help, I've finally found a solution that I can work into my project.

    Although, on a side note, I'm curious as to why _EmissionColor never worked, my research has led me to believe if it is not defined in the original shader ( i.e. it's turned off ), then I can't turn it on, is this true ? How might I get around that ? I've also read about a keyword enabler for _EMISSION, but whereas this may have worked in the past, it doesn't seem to work with newer versions of unity ?

    My working test script below :
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class FlashColor : MonoBehaviour
    6. {
    7.     List<Color> originalColors = new List<Color>();
    8.  
    9.     private int originalColorIndex;
    10.  
    11.     private void Start()
    12.     {
    13.         // Find all the children of the GameObject with MeshRenderers
    14.  
    15.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    16.  
    17.         // Cycle through each child object found with a MeshRenderer
    18.  
    19.         foreach (MeshRenderer rend in children)
    20.         {
    21.             // And for each child, cycle through each material
    22.  
    23.             foreach (Material mat in rend.materials)
    24.             {
    25.                 // Store original colors
    26.  
    27.                 originalColors.Add(mat.color);
    28.             }
    29.         }
    30.     }
    31.  
    32.     private void OnMouseDown()
    33.     {
    34.         // StartCoroutine to flash GameObject
    35.  
    36.         StartCoroutine("HitFlash");
    37.     }
    38.  
    39.     public IEnumerator HitFlash()
    40.     {
    41.         // Flash color
    42.  
    43.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    44.  
    45.         foreach (MeshRenderer rend in children)
    46.         {
    47.             foreach (Material mat in rend.materials)
    48.             {
    49.                 mat.SetColor("_Color", Color.white);
    50.             }
    51.         }
    52.  
    53.         yield return new WaitForSeconds(0.1f);
    54.  
    55.         // Restore colors
    56.  
    57.         foreach (MeshRenderer rend in children)
    58.         {
    59.             foreach (Material mat in rend.materials)
    60.             {
    61.                 mat.SetColor("_Color", originalColors[originalColorIndex]);
    62.  
    63.                 // Increment originalColorIndex by 1
    64.  
    65.                 originalColorIndex += 1;
    66.             }
    67.         }
    68.  
    69.         // Reset originalColorIndex
    70.  
    71.         originalColorIndex = 0;
    72.  
    73.         StopCoroutine("HitFlash");
    74.     }
    75. }
     
    Last edited: Dec 5, 2019
  8. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,131
    Ahhh, sorry, my bad I should have taken a closer look at your code, I haven't experimented much with changing materials through lists and arrays but yes using a list sounds like a good idea. One thing I don't understand, is there a specific reason you're using a list?

    I would have thought you could just have a script attached to the gameobject that is being collided to and then have it search for the gameobject that is going to collide with it than this way and then have it GetComponentInChildren or am I misunderstanding what you're trying to achieve here?

    So for example if your script is attached to an enemy have the enemy search for a "Bullet" tag and then just do the code through there. If you're looking to maintain a list of these gameobjects and keep a count you could add the list at the start and then remove them from the list after they get destroyed.

    The reason I'm bringing this up is because I went through this exact situation and found it way easier to just attach a script to the gameobjects that are being collided with and then add them to a list as opposed to fiddling around with all this overly complicated stuff within the list itself.

    This is pretty much what I've done in my code and why I posted it up for you.
     
    Last edited: Dec 5, 2019
  9. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,605
    Select your material in project tab, click that small gear on the top of material inspector, and choose "Edit Shader" option
    You wil see source code of the shader used and what params it can accept.
     
  10. PlaymintDev

    PlaymintDev

    Joined:
    Jan 11, 2014
    Posts:
    306
    @Lethn
    Well, I'm not an veteran programmer, but it seemed the easiest method to me to store the color information, it was super easy to add to, and read from. Yes, it will be attached to a GameObject in my small game, the main Player, so I will trigger it from :
    Code (CSharp):
    1.     private void OnTriggerEnter(Collider other)
    2.     {
    3.         if (other.CompareTag("Police"))
    4.         {
    5.             StartCoroutine("HitFlash");
    6.         }
    7.     }
    And for enemy vehicles in my game, something similar using an OnCollision method, like your great example.

    RE : Your suggestion, I think GetComponentInChildren was only finding the first child object and nothing else that was a child, hence the reasoning behind my more complicated codebase. It makes sense to me, but I'm always open to better suggestions anyway, so thanks.

    @palex-nx
    Thanks for your reply, I will delve into this in more detail, see if I can figure out a better solution to my _Color and _EmissionColor problem, once I've had a chance to study the shader in more detail.

    Thanks.
     
    Last edited: Dec 5, 2019
  11. calpolican

    calpolican

    Joined:
    Feb 2, 2015
    Posts:
    204
    GetComponentsIInChildren uses depth first search, so it goes through all of the children and their childrens (and their children childrens too).
    I think what palex-nx was saying, is that in order to change the property of the material you should know how the shader calls it inside. Not always the tag that the system uses matches the name of the property you see in the inspector. For example in shader graph, you can turn a node into a property... say I have a Color and I want to show it in the inspector. I can call it "t1Color" (like in the image). But if you see below, the system automatically assigns a reference name that can be something like "Color_86CBE207" in this case. If I try to set the color using "t1Color" it'll fail. I need either to call the function using "Color_86CBE207" or change the reference name in the blackboard to something I'll remember easier. The weird names generated by the system are to avoid creating twice the same tag.
    shader.jpg
    Btw, yours seem like a very clever solution. Proof that you can always find many ways to do stuff. Just remember to tag any weapon the character equips.
     
    Last edited: Dec 6, 2019
    PlaymintDev likes this.
  12. PlaymintDev

    PlaymintDev

    Joined:
    Jan 11, 2014
    Posts:
    306
    Script 'test' version working with _EmissionColor now, in this version, I don't even need to work with storing colors in Lists, posting here for future reference ! :)
    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. public class FlashWhenHit : MonoBehaviour
    5. {
    6.     private void Start()
    7.     {
    8.         // Find all the children of the GameObject with MeshRenderers
    9.  
    10.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    11.  
    12.         // Cycle through each child object found with a MeshRenderer
    13.  
    14.         foreach (MeshRenderer rend in children)
    15.         {
    16.             // And for each child, cycle through each material
    17.  
    18.             foreach (Material mat in rend.materials)
    19.             {
    20.                 // Enable Keyword EMISSION for each material
    21.  
    22.                 mat.EnableKeyword("_EMISSION");
    23.             }
    24.         }
    25.     }
    26.  
    27.     private void OnTriggerEnter(Collider other)
    28.     {
    29.         if (other.CompareTag("Police"))
    30.         {
    31.             StartCoroutine("HitFlash");
    32.         }
    33.     }
    34.  
    35.     public IEnumerator HitFlash()
    36.     {
    37.         // Flash color
    38.  
    39.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    40.  
    41.         foreach (MeshRenderer rend in children)
    42.         {
    43.             foreach (Material mat in rend.materials)
    44.             {
    45.                 mat.SetColor("_EmissionColor", Color.white);
    46.             }
    47.         }
    48.  
    49.         yield return new WaitForSeconds(0.1f);
    50.  
    51.         // Restore default emission color
    52.  
    53.         foreach (MeshRenderer rend in children)
    54.         {
    55.             foreach (Material mat in rend.materials)
    56.             {
    57.                 mat.SetColor("_EmissionColor", Color.black);
    58.             }
    59.         }
    60.  
    61.         StopCoroutine("HitFlash");
    62.     }
    63. }
    Actually, just thinking about it, I can use a boolean, so that _Color and _EmissionColor are both potential options, depending on scene requirements.

    EDIT : Boolean check version for both options
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PlayerHitFlash : MonoBehaviour
    6. {
    7.     readonly List<Color> originalColors = new List<Color>();
    8.  
    9.     private int originalColorIndex;
    10.  
    11.     public bool useEmission;
    12.  
    13.     private void Start()
    14.     {
    15.         // Find all the children of the GameObject with MeshRenderers
    16.  
    17.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    18.  
    19.         // Cycle through each child object found with a MeshRenderer
    20.  
    21.         foreach (MeshRenderer rend in children)
    22.         {
    23.             // And for each child, cycle through each material
    24.  
    25.             foreach (Material mat in rend.materials)
    26.             {
    27.                 if (useEmission)
    28.                 {
    29.                     // Enable Keyword EMISSION for each material
    30.  
    31.                     mat.EnableKeyword("_EMISSION");
    32.                 }
    33.  
    34.                 else
    35.                 {
    36.                     // Store original colors
    37.  
    38.                     originalColors.Add(mat.color);
    39.                 }
    40.             }
    41.         }
    42.     }
    43.  
    44.     private void OnTriggerEnter(Collider other)
    45.     {
    46.         if (other.CompareTag("Police"))
    47.             StartCoroutine("HitFlash");
    48.     }
    49.  
    50.     public IEnumerator HitFlash()
    51.     {
    52.         // Flash color
    53.  
    54.         MeshRenderer[] children = GetComponentsInChildren<MeshRenderer>();
    55.  
    56.         foreach (MeshRenderer rend in children)
    57.         {
    58.             foreach (Material mat in rend.materials)
    59.             {
    60.                 if (useEmission)
    61.                     mat.SetColor("_EmissionColor", Color.white);
    62.  
    63.                 else
    64.                     mat.SetColor("_Color", Color.white);
    65.             }
    66.         }
    67.  
    68.         yield return new WaitForSeconds(0.1f);
    69.  
    70.         // Restore default colors or emission
    71.  
    72.         foreach (MeshRenderer rend in children)
    73.         {
    74.             foreach (Material mat in rend.materials)
    75.             {
    76.                 if (useEmission)
    77.                     mat.SetColor("_EmissionColor", Color.black);
    78.  
    79.                 else
    80.                 {
    81.                     mat.SetColor("_Color", originalColors[originalColorIndex]);
    82.  
    83.                     // Increment originalColorIndex by 1
    84.  
    85.                     originalColorIndex += 1;
    86.                 }
    87.             }
    88.         }
    89.  
    90.         if (useEmission)
    91.             StopCoroutine("HitFlash");
    92.  
    93.         else
    94.         {
    95.             // Reset originalColorIndex
    96.  
    97.             originalColorIndex = 0;
    98.  
    99.             StopCoroutine("HitFlash");
    100.         }
    101.     }
    102. }
     
    Last edited: Dec 8, 2019 at 11:54 AM