Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question proper way to change material by script

Discussion in 'Scripting' started by shuskry, May 26, 2023.

  1. shuskry

    shuskry

    Joined:
    Oct 10, 2015
    Posts:
    462
    Hello everybody :D

    i'm doing a 3D game on mobile , and I would like to implement a basic effect when the player shoot a ennemy.
    I want to the ennemy to have a basic red outline effect.

    I already have my materials , but I would like to know if this is the right way to do it, or if there is a more optimized way:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class ObjectMaterialChanger : MonoBehaviour
    5. {
    6.     public Material defaultMaterial;  // Original material of the object
    7.     public Material projectileMaterial;  // Material to apply when hit by a projectile
    8.  
    9.     private MeshRenderer ennemyRenderer;  // Object's renderer
    10.     private bool isChangingMaterial;  // Indicates if material is currently changing
    11.     private float materialChangeTime = 0.5f;  // Duration of material change
    12.     private float timeSinceLastProjectile;  // Time elapsed since the last projectile
    13.  
    14.     private void Start()
    15.     {
    16.         ennemyRenderer = GetComponent<MeshRenderer>();
    17.         ennemyRenderer.material = defaultMaterial;
    18.     }
    19.  
    20.     private void Update()
    21.     {
    22.         if (isChangingMaterial)
    23.         {
    24.             timeSinceLastProjectile += Time.deltaTime;
    25.  
    26.             if (timeSinceLastProjectile >= materialChangeTime)
    27.             {
    28.                 ennemyRenderer.material = defaultMaterial;
    29.                 isChangingMaterial = false;
    30.             }
    31.         }
    32.     }
    33.  
    34.     private void OnCollisionEnter(Collision collision)
    35.     {
    36.         if (collision.gameObject.CompareTag("Projectile"))
    37.         {
    38.             ennemyRenderer.material = projectileMaterial;
    39.             isChangingMaterial = true;
    40.             timeSinceLastProjectile = 0f;
    41.         }
    42.     }
    43. }
    44.  
    Thanks for your help !
    Have a good day :D

    EDIT:

    Maybe my words aren't the right ones.
    I didn't mean "optimized" in the sense of optimizing performance.

    But I meant to say "proper" way instead, in the sense of doing the best possible way.

    I have been using this method for several years, but with the addition of certain functionality (I have heard a little about the variants of material for example).

    That's why I'm asking the question, to make sure I'm still up to date on how to do it
     
    Last edited: May 26, 2023
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Optimized in what way? Are you seeing an actual performance issue here?

    Otherwise, performance investigations always begin the same way:

    DO NOT OPTIMIZE "JUST BECAUSE..." If you don't have a problem, DO NOT OPTIMIZE!

    If you DO have a problem, there is only ONE way to find out. Always start by using the profiler:

    Window -> Analysis -> Profiler

    Failure to use the profiler first means you're just guessing, making a mess of your code for no good reason.

    Not only that but performance on platform A will likely be completely different than platform B. Test on the platform(s) that you care about, and test to the extent that it is worth your effort, and no more.

    https://forum.unity.com/threads/is-...ng-square-roots-in-2021.1111063/#post-7148770

    Remember that optimized code is ALWAYS harder to work with and more brittle, making subsequent feature development difficult or impossible, or incurring massive technical debt on future development.

    Notes on optimizing UnityEngine.UI setups:

    https://forum.unity.com/threads/how...form-data-into-an-array.1134520/#post-7289413

    At a minimum you want to clearly understand what performance issues you are having:

    - running too slowly?
    - loading too slowly?
    - using too much runtime memory?
    - final bundle too large?
    - too much network traffic?
    - something else?

    If you are unable to engage the profiler, then your next solution is gross guessing changes, such as "reimport all textures as 32x32 tiny textures" or "replace some complex 3D objects with cubes/capsules" to try and figure out what is bogging you down.

    Each experiment you do may give you intel about what is causing the performance issue that you identified. More importantly let you eliminate candidates for optimization. For instance if you swap out your biggest textures with 32x32 stamps and you STILL have a problem, you may be able to eliminate textures as an issue and move onto something else.

    This sort of speculative optimization assumes you're properly using source control so it takes one click to revert to the way your project was before if there is no improvement, while carefully making notes about what you have tried and more importantly what results it has had.
     
  3. shuskry

    shuskry

    Joined:
    Oct 10, 2015
    Posts:
    462
    Hi ! :)

    Maybe my words aren't the right ones.
    I didn't mean "optimized" in the sense of optimizing performance.

    But I meant to say "proper" way instead, in the sense of doing the best possible way.

    I have been using this method for several years, but with the addition of certain functionality (I have heard a little about the variants of material for example).

    That's why I'm asking the question, to make sure I'm still up to date on how to do it :D
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Looking at your code it certainly looks like the simplest most straightforward way, so to me that IS the best way, as long as it satisfies all the needs.
     
    shuskry likes this.
  5. shuskry

    shuskry

    Joined:
    Oct 10, 2015
    Posts:
    462
    @Kurt-Dekker Thanks for the answer :D !

    @MaltsGangfoot

    Thanks for the advice ! I didn't kow for the 'string' , I will create a private String constant for "projectile"

    Can you tell me more about that ? I heard nothing about that !
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    You CAN use an integer but then framerate has to be accounted for.

    By using a float and Time.deltaTime you already take care of framerate.

    In today's world, math on a
    float
    or an
    int
    is essentially identical.
     
    orionsyndrome and shuskry like this.
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I guess if I had one single simple change to the logic above is to remove the boolean.

    Code (csharp):
    1. void Update()
    2. {
    3.   if (isGlowingFromHit > 0)
    4.   {
    5.     isGlowingFromHit -= Time.deltaTime;
    6.     if (isGlowingFromHit <= 0)
    7.     {
    8.        // restore normal material
    9.     }
    10.   }
    11. }
    In the onhit func, just do:

    Code (csharp):
    1. // (whatever you did to change the material)
    2.  
    3. isGlowingFromHit = 0.5f;
    That way the float both serves as timer AND as boolean.

    Every variable you make is one more place that a bug can hide out.
     
    shuskry and MaltsGangfoot like this.
  8. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    655
    I'm pretty sure the compiler will optimize literal string " " and converts it to a const, so no new string is allocated each time as you said.
     
    shuskry and orionsyndrome like this.
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    All literal strings are optimized in a compilation prepass called 'string interning'.
    It is smarter than most people think. It will consider concatenated and interpolated strings as well (and obviously reuse the existing ones if they're already interned). Some read here and here, but the topic is even wider than this and I can't find good sources atm.

    C# compiler is sufficiently advanced for all such worries, no need to try and outsmart it at every corner. It's better to focus and spend that energy on how you're dealing with the string production during runtime, because they are the major source of garbage if used improperly.
     
    shuskry and Nad_B like this.
  10. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,070
    Also a division is not that much of a hurt compared to multiplication. It is still slightly slower, according to my tests.
     
    shuskry likes this.
  11. shuskry

    shuskry

    Joined:
    Oct 10, 2015
    Posts:
    462
    Thanks for all these clarifications ! :D
     
    orionsyndrome likes this.
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I blast strings through it all the time, every frame, slicing and dicing an endless stream of interop dialog between Unity and my native C / C++ applications... I developed this over-the-wire way of talking back and forth a decade ago and it has made my life heaven: the entire game works on four (4) native methods:

    - send op integer
    - send op string
    - render one frame
    - give me that frame buffer graphic

    Every time I need to add a new feature, it's just a string switch statement and some native code.

    Each call to the "send op" functions gets a string response back , which might have data to play new sounds or do other things in Unity.

    It's not pretty but it looks like:

    camera:100,100,0;playing:1;ok:eek:neframe
    HARDKEYS:
    camera:100,100,0;playing:1;ok:eek:neframe
    HARDKEYS: DOWN
    camera:100,100,0;playing:1;ok:eek:neframe
    HARDKEYS: DOWN
    camera:100,100,0;playing:1;ok:eek:neframe
    HARDKEYS: DOWN
    camera:100,100,0;playing:1;ok:eek:neframe
    HARDKEYS: DOWN
    camera:100,100,0;playing:1;ok:eek:neframe
    HARDKEYS: DOWNLEFT
    camera:98,100,0;playing:1;ok:eek:neframe
    HARDKEYS: DOWNLEFT FIRE1
    snd:firea:20;camera:95,100,0;playing:1;ok:eek:neframe
    HARDKEYS: DOWNLEFT FIRE1
    camera:92,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT
    camera:89,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT
    camera:85,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT
    camera:86,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP RIGHT FIRE1
    snd:firea:20;camera:87,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP RIGHT FIRE1
    camera:90,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP
    camera:91,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP
    camera:92,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP
    camera:92,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP FIRE1
    snd:firea:20;camera:92,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP RIGHT FIRE1
    camera:94,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP RIGHT
    camera:96,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP RIGHT
    camera:99,100,0;playing:1;ok:eek:neframe
    HARDKEYS: UP RIGHT
    camera:103,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT FIRE1
    snd:firea:20;camera:109,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT FIRE1
    camera:105,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT FIRE1
    camera:102,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT
    camera:98,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT
    camera:95,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFTRIGHT
    camera:95,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFTRIGHT
    camera:96,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    snd:firea:20;camera:98,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    camera:101,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    camera:104,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    camera:107,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    camera:111,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT FIRE1
    snd:ih_expl#:49;snd:firea:20;camera:111,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT FIRE1
    camera:109,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT FIRE1
    camera:106,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT
    camera:103,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT
    camera:100,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFT
    snd:ih_expl#:44;camera:97,100,0;playing:1;ok:eek:neframe
    HARDKEYS: LEFTRIGHT
    camera:97,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    snd:firea:20;camera:98,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    camera:101,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT FIRE1
    camera:104,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT
    camera:107,100,0;playing:1;ok:eek:neframe
    HARDKEYS: RIGHT

    Each one of those strings gets sliced and diced... the hardkeys are just sent as a bit-encoded integer, and displayed as strings in the above wire logger feed for ease of reading.

    KurtMaster2D is at:

    Apple iTunes: https://itunes.apple.com/us/app/kurtmaster2d/id1015692678
    Google Play (including TV): https://play.google.com/store/apps/details?id=com.plbm.plbm1
     
    orionsyndrome likes this.