Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question On loading scene, material changing code stops working.

Discussion in 'Scripting' started by AnnaMartorano, Oct 7, 2023.

  1. AnnaMartorano

    AnnaMartorano

    Joined:
    Sep 29, 2021
    Posts:
    7
    Hello,
    I am working on a toggle button that changes the material of all the game (sprites and UI images) to a Grayscale.

    My game has two scenes: MainMenu and Game. The settings, where the toggle button is located, are in the MainMenu, and my code works great there. However, when pressing the Play Button and changing into the Game Scene, nothing works.

    I have been asking GPT, and made me create a SceneData script (in an empty object) with the isGrayscale variable in the MainMenu scene. I have a DontDestroyOnLoad empty with the GrayScale script in the Main Menu, and it seems to instance correctly in the Game scene. However isGrayscale is off every time Game scene loads, and even if I switch it on (it's a public variable), nothing happens.

    I have tested the material in a single sprite in the Game scene and it also works...

    Does anyone know what else could I try?

    Here is the code:

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.UI;
    6. using UnityEngine.SceneManagement;
    7.  
    8.  
    9.  
    10. public class GrayScale : MonoBehaviour
    11. {
    12.     private static GrayScale instance;
    13.  
    14.     SpriteRenderer[] spriteRenderers;
    15.     Image[] images;
    16.  
    17.     Material grayScaleMaterial;
    18.     Material originalMaterial;
    19.     Material originalMaterialUI;
    20.  
    21.     public bool isGrayscale = false;
    22.  
    23.     private void Awake()
    24.     {
    25.        
    26.         Debug.Log("GrayScale Awake");
    27.         if (instance == null)
    28.         {
    29.             instance = this;
    30.             DontDestroyOnLoad(instance);
    31.         }
    32.         else
    33.         {
    34.             Destroy(gameObject);
    35.         }
    36.     }
    37.  
    38.     // Start is called before the first frame update
    39.     void Start()
    40.     {
    41.         Debug.Log("GrayScale Start");
    42.  
    43.         isGrayscale = SceneData.isGrayscale;
    44.         spriteRenderers = FindObjectsOfType<SpriteRenderer>();
    45.         images = FindObjectsOfType<Image>();
    46.         grayScaleMaterial = Instantiate(Resources.Load<Material>("Materials/GrayscaleEffect"));
    47.  
    48.         SceneManager.sceneLoaded += OnSceneLoaded;    
    49.     }
    50.  
    51.     void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    52.     {
    53.         // Aplica el efecto de escala de grises a los objetos de acuerdo con el estado actual
    54.         //ToggleGrayscale();
    55.         ApplyGrayscaleToAll(isGrayscale);
    56.         print(isGrayscale +"on Scene Loaded");
    57.     }
    58.  
    59.     public void ToggleGrayscale()
    60.     {
    61.         // Invierte el valor en SceneData
    62.         SceneData.isGrayscale = !SceneData.isGrayscale;
    63.  
    64.         // Actualiza el valor en GrayScale
    65.         isGrayscale = SceneData.isGrayscale;
    66.         ApplyGrayscaleToAll(isGrayscale);
    67.         print(isGrayscale + " on Toggle");
    68.     }
    69.  
    70.     public void ApplyGrayscaleToAll(bool isGrayscale)
    71.     {
    72.        
    73.         foreach (SpriteRenderer sr in spriteRenderers)
    74.         {
    75.             print("trying to access to the ApplyGrayScale");
    76.             ApplyGrayscale(sr, isGrayscale);
    77.         }
    78.         foreach (Image image in images)
    79.         {
    80.             ApplyGrayscaleUI(image, isGrayscale);
    81.         }
    82.     }
    83.  
    84.     void ApplyGrayscale(SpriteRenderer spriteRenderer, bool isGrayscale)
    85.     {
    86.         print("applying material");
    87.         print("isGrayscale: " + isGrayscale); // Agrega esta línea
    88.  
    89.         if (spriteRenderer != null && spriteRenderer.gameObject != null) // Verificar si el objeto SpriteRenderer y su GameObject aún existen
    90.         {
    91.             print("HAY SPRITE RENDERERS");
    92.             if (isGrayscale)
    93.             {
    94.                 originalMaterial = spriteRenderer.material; // Almacena el material original antes de aplicar el efecto de escala de grises
    95.                 print("Original material: " + originalMaterial.name);
    96.                 spriteRenderer.material = grayScaleMaterial; // Aplica el material de escala de grises
    97.             }
    98.             else
    99.             {
    100.                 print("Restored material: " + originalMaterial.name);
    101.                 spriteRenderer.material = originalMaterial; // Restaura el material original
    102.                
    103.             }
    104.         }      
    105.     }
    106.     void ApplyGrayscaleUI(Image image, bool isGrayscale)
    107.     {
    108.         if (image != null && grayScaleMaterial != null) // Verificar si el objeto Image y grayScaleMaterial no son nulos
    109.         {
    110.             if (isGrayscale)
    111.             {
    112.                 originalMaterialUI = image.material; // Almacena el material original antes de aplicar el efecto de escala de grises
    113.                 image.material = grayScaleMaterial; // Aplica el material de escala de grises
    114.             }
    115.             else
    116.             {
    117.                 image.material = originalMaterialUI; // Restaura el material original
    118.             }
    119.         }
    120.     }
    121. }
    122.  
    123.  
    EDIT:
    I have figured out that on loading the new scene, the code does get into the methods ApplyGrayscale and ApplyGreyscaleUI since it prints the first 2 prints ("applying material" and "isGrayscale: true"), but does not get into de condition, as if it didn't find any sprite renderers or images. They are all there... I did notice, though, that the DontDestroyOnLoad empty appears separate in the hierarchy, not inside the Game scene.
     
    Last edited: Oct 7, 2023
  2. AnnaMartorano

    AnnaMartorano

    Joined:
    Sep 29, 2021
    Posts:
    7
    Here some images of Playmode Game scene inspector and hierarchy.
     

    Attached Files:

  3. AnnaMartorano

    AnnaMartorano

    Joined:
    Sep 29, 2021
    Posts:
    7
    This is the result of the Console when changing scenes. The result of Prints and Debugs.Logs.
     

    Attached Files:

  4. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    541
    Code (CSharp):
    1.         private void Awake()
    2.         {
    3.             DontDestroyOnLoad(this.gameObject);
    4.         }
    5.  
    6.         void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    7.         {
    8.             ApplyGrayscaleToAll(isGrayscale);
    9.             print(isGrayscale +"on Scene Loaded");
    10.         }
     
  5. AnnaMartorano

    AnnaMartorano

    Joined:
    Sep 29, 2021
    Posts:
    7
    Tried this. Didn't work. Everything else does, but seems like I'm missing something on how to apply a material. I have it in the Assets/Resources/Materials folder, and it does work when in the MainMenu scene...
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    This is a defective singleton. It fails because it searches things in Start() and never again. You change the scene, those things are gone.

    Does this even NEED to be a singleton in the first place?? If it does, use this pattern:

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance

    Alternately you could start one up with a
    RuntimeInitializeOnLoad
    attribute.

    The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

    OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

    And finally there's always just a simple "static locator" pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

    WARNING: this does NOT control their uniqueness.

    WARNING: this does NOT control their lifecycle.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , but only while it exists.




    ALSO, do not use this type of programming:

    Keep in mind that using GetComponent<T>() and its kin (in Children, in Parent, plural, etc) to try and tease out Components at runtime is definitely deep into super-duper-uber-crazy-Ninja advanced stuff.

    This sort of coding is to be avoided at all costs unless you know exactly what you are doing.

    If you run into an issue with any of these calls, start with the documentation to understand why.

    There is a clear set of extremely-well-defined conditions required for each of these calls to work, as well as definitions of what will and will not be returned.

    In the case of collections of Components, the order will NEVER be guaranteed, even if you happen to notice it is always in a particular order on your machine.

    It is ALWAYS better to go The Unity Way(tm) and make dedicated public fields and drag in the references you want.
     
    AnnaMartorano likes this.
  7. AnnaMartorano

    AnnaMartorano

    Joined:
    Sep 29, 2021
    Posts:
    7
    Wow, that's a lot of information. I am not new to this, but not very old either. I will go point by point tonight and see if I understand everything you said. Many thanks.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    This information may help you understand the WHY of the above.

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    When you load a scene, 100% of root GameObjects that are NOT marked as DontDestroyOnLoad() are... destroyed.
     
    AnnaMartorano likes this.
  9. AnnaMartorano

    AnnaMartorano

    Joined:
    Sep 29, 2021
    Posts:
    7
    OK, I've been looking at it, and I cannot find a suitable way to not use findObjectsOfType. I need to store all objects in different scenes into an array, and then check if those objects have a Sprite Renderer or an Image. I want this because what i want the toggle button to do is to change the material in the current scene (MainMenu) and in the Game scene, and this is the only way I could think of. I'm not sure if there would be another approach to it.

    My game is very simple, has a player moving around escaping from objects falling. These objects are instances and are destroyed when they reach an object with tag, so I cannot use the "FindObjectsWithTag" method either to build my array.

    You're right, I don't think I need a singleton here, since I do not need to access this code from anoter, I just need it to keep working in the next scene. A DontDestroyOnLoad would do, I think.
     
  10. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    541
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.SceneManagement;
    3.  
    4. public class GrayScale : MonoBehaviour
    5. {
    6.     public bool isGrayscale;
    7.     public Material grayScaleMaterial; // drag the grayscale material onto this
    8.  
    9.     void Awake()
    10.     {
    11.         SceneManager.sceneLoaded += OnSceneLoaded;
    12.         DontDestroyOnLoad(this.gameObject);
    13.     }
    14.  
    15.     void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    16.     {
    17.         if (!isGrayscale)
    18.             return;
    19.         Renderer[] renderers= FindObjectsOfType<Renderer>();
    20.         foreach (Renderer r in renderers)
    21.             r.material=grayScaleMaterial;
    22.     }
    23. }
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    A GameManager is the construct typically used to track game state, such as a setting like this.

    The toggle button would call methods to change that value in the GameManager. It would need to be either a ScriptableObject instance (that instance dragged into everybody that cares about it), a static or a simple singleton like what I posted above.

    Objects would subscribe to something in the GameManager to inform them that their appearance changed.

    You absolutely CAN use Find and Get, just know the perimeter and surface of stuff they touch and consider and when they do so. Failing to understand that is a recipe for confusion and code crashing.

    ALSO, here's some more general "find stuff" patterns to consider:

    https://starmanta.gitbooks.io/unitytipsredux/content/first-question.html

    https://forum.unity.com/threads/why-cant-i-find-the-other-objects.1360192/#post-8581066