Search Unity

How to create a prefab that switches its look based on a boolean and that also appears in edit mode?

Discussion in 'Getting Started' started by codfel2, Dec 3, 2020.

  1. codfel2

    codfel2

    Joined:
    Dec 3, 2020
    Posts:
    3


    I basically just started with unity and I'm currently following some beginner youtube tutorials to try to learn it.​

    For now, I'm trying to do a lamp that is orange when the property "is light on" is true and dark when the property is false. (When the light is on I also turn on a halo and a point light from the object)

    I wrote a script that instantiates the appropriate lamp asset according to the checkbox property inside the start function, but with this, I can only see the lamp when the game is running, while I'm editing the map it remains invisible, and that makes it too difficult to build the scene.

    I also tried to add the ExecuteInEditMode in the code changing the logic a bit but besides *sometimes* showing something on the screen during edit mode it keeps cloning the lamps permanently and desyncing the isLightOn property.

    Code for reference:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [ExecuteInEditMode]
    4. public class LampScript : MonoBehaviour
    5. {
    6.     public GameObject lampContainer;
    7.  
    8.     public GameObject haloLight;
    9.     public GameObject emittedLight;
    10.    
    11.     public GameObject lampOn;
    12.     public GameObject lampOff;
    13.    
    14.     public bool isLampOn;
    15.     private bool _currentLampState = false;
    16.    
    17.     void Start()
    18.     {
    19.         if(LampChanged()) ChangeLamp();
    20.     }
    21.     bool LampChanged()
    22.     {
    23.         return isLampOn != _currentLampState;
    24.     }
    25.     void ChangeLamp()
    26.     {
    27.         haloLight.GetComponent<Light>().enabled = !haloLight.GetComponent<Light>().enabled;
    28.         emittedLight.GetComponent<Light>().enabled = !emittedLight.GetComponent<Light>().enabled;
    29.  
    30.         Instantiate(isLampOn ? lampOn : lampOff, lampContainer.transform);
    31.  
    32.         _currentLampState = !_currentLampState;
    33.     }
    34.  
    35.     void Update() {
    36.         if(LampChanged()) ChangeLamp();
    37.     }
    38. }
    Object Hierarchy:
    • Lamp
      • Halo Light
      • Emitted Light
      • LampContainer
        • Lamp Asset ON/OFF
     
    Last edited: Dec 4, 2020
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    The lamp shouldn't make it any more or less difficult to build your scene. Just click the little light bulb in the scene view.

    upload_2020-12-3_12-46-59.png

    If you really want it to update in edit mode, ExecuteInEditMode is a good way to do it. But as you found, you'll have to write your code in such a way that it doesn't do evil things.
     
  3. codfel2

    codfel2

    Joined:
    Dec 3, 2020
    Posts:
    3
    Oh... that icon makes sense now...

    I managed to make the
    ExecuteInEditMode
    work. I used
    DestroyImmediate()
    instead of Destroy to also delete the replaced lamp in edit mode and removed some redundancies in the code which ended up fixing the random desynchronizations.

    But now I encountered a new problem, the lamp works fine, I can turn it on and off during game and edit mode, but that is only while my lamp is a gameObject, cause the moment I convert it to a prefab so that I can reuse it during multiple scenes... suddenly the checkbox has no effect, in fact, it doesn't do anything in any unity mode at all...

    I don't know yet if that when the object is prefabbed it gets permanently frozen or if it doesn't trigger property changes from the inspector the same way anymore or if I made a mistake in some part... I just hope it doesn't require a complex obscure workaround cause that would be sad.

    The new code:
    Code (CSharp):
    1. using Homebrew;
    2. using UnityEngine;
    3.  
    4. [ExecuteInEditMode]
    5. public class LampScript : MonoBehaviour
    6. {
    7.     public bool isLampOn;
    8.    
    9.     [Foldout("Sources", true)]
    10.     public GameObject lampContainer;
    11.     public GameObject haloLightSource;
    12.     public GameObject emittedLightSource;
    13.     public GameObject lampOn;
    14.     public GameObject lampOff;
    15.    
    16.     private bool _currentLampState = false;
    17.     private Light _haloLight;
    18.     private Light _emittedLight;
    19.    
    20.     void Start()
    21.     {
    22.         _haloLight = haloLightSource.GetComponent<Light>();
    23.         _emittedLight = emittedLightSource.GetComponent<Light>();
    24.         if(LampChanged()) ChangeLamp();
    25.     }
    26.  
    27.     void Update() {
    28.         if(Application.isEditor && LampChanged()) ChangeLamp();
    29.     }
    30.  
    31.     bool LampChanged()
    32.     {
    33.         return isLampOn != _currentLampState;
    34.     }
    35.  
    36.     void ChangeLamp()
    37.     {
    38.         RemoveOldLamp();
    39.  
    40.         _haloLight.enabled = _emittedLight.enabled = isLampOn;
    41.  
    42.         Instantiate(isLampOn ? lampOn : lampOff, lampContainer.transform);
    43.  
    44.         _currentLampState = !_currentLampState;
    45.     }
    46.  
    47.     void RemoveOldLamp()
    48.     {
    49.         foreach (Transform child in lampContainer.transform) {
    50.             DestroyImmediate(child.gameObject);
    51.         }
    52.     }
    53.  
    54. }
     
    Last edited: Dec 4, 2020
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    You can't instantiate and destroy child objects in a prefab instance.

    Consider keeping both versions of the lamp inside your parent object, and just turning them on and off (with GameObject.SetActive) instead.

    (Also, please don't use pastebin or other external sites. This forum has a perfectly good Insert Code button right in the editing toolbar.)
     
  5. codfel2

    codfel2

    Joined:
    Dec 3, 2020
    Posts:
    3
    Omg... thank you so much, it finally worked... using the
    setActive()
    did it, it's now working with the prefab on game and edit mode, it also reduced the amount of code so now it looks better.

    ✓ Pastebins were replaced.

    My final script:
    Code (CSharp):
    1. using Homebrew;
    2. using UnityEngine;
    3.  
    4. [ExecuteInEditMode]
    5. public class LampScript : MonoBehaviour
    6. {
    7.     public bool on;
    8.    
    9.     [Foldout("Sources", true)]
    10.     public GameObject lights;
    11.     public GameObject lampOn;
    12.     public GameObject lampOff;
    13.    
    14.     private bool _currentLampState = false;
    15.    
    16.     void Start()
    17.     {
    18.         if(LampChanged()) ChangeLamp();
    19.     }
    20.  
    21.     bool LampChanged()
    22.     {
    23.         return on != _currentLampState;
    24.     }
    25.  
    26.     void ChangeLamp()
    27.     {
    28.         _currentLampState = !_currentLampState;
    29.  
    30.         lights.SetActive(on);
    31.         lampOn.SetActive(on);
    32.         lampOff.SetActive(!on);
    33.     }
    34.  
    35.     void Update() {
    36.         if(Application.isEditor && LampChanged()) ChangeLamp();
    37.     }
    38. }
    39.  
     
    JoeStrout likes this.
  6. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Just want to chime in with something that might be useful:
    You can add an OnValidate method to a MonoBehaviour script. It's an editor-only callback method that runs any time a change is made in the script's inspector.
    Using that, you can change the look or other properties of your GameObject,by reading the values of your script, for example:
    Code (CSharp):
    1. public class Example : MonoBehaviour
    2. {
    3.    public GameObject someObject;
    4.    public bool someObjectIsActive;
    5.  
    6.    //Any time "someObjectIsActive" is changed in the inspector, the associated "someObject" reference will be enabled/disabled.
    7.    private void OnValidate()
    8.    {
    9.       if(someObject != nulll)
    10.       {
    11.          someObject.SetActive(someObjectIsActive);
    12.       }
    13.    }
    14. }
    It's a bit safer than
    [ExecuteInEditMode]
    , since you don't have to worry about the actual MonoBehaviour lifecycle running and having to write editor code to work around it.

    On a side note, rather than checking
    LampChanged()
    every frame to actually change the lamp, you can add a Propery to your "on" field instead, and any time the "on" value is changed, you call
    ChangeLamp()
    :
    Code (CSharp):
    1. public class LampScript : MonoBehaviour
    2. {
    3.    //SerializeField just allows Unity to display this field in the inspector even when it's private.
    4.    [SerializeField] private bool on;
    5.  
    6.    public bool On {
    7.       get => on;
    8.       set {
    9.          if(on != value)
    10.          {
    11.             on = value;
    12.             ChangeLamp();
    13.          }
    14.       }
    15.    }
    16. }
    Now whenever
    LampScript.On
    is changed, it will automatically change the lamp state.
     
    JoeStrout likes this.
  7. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Thanks, @Vryken — I knew about OnValidate but never think to use it. I should use it more in cases like this, as it really is a cleaner solution!
     
    Vryken likes this.
  8. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Oh yeah, one other thing:

    OnValidate
    is an editor-only method, but I'm not actually sure if Unity automatically excludes it (along with other editor-only methods) from standalone builds or not, since it would just be useless and take up unnecessary space in the actual game itself.

    Just to be on the safe side, it's probably best to wrap
    OnValidate
    and all other editor-only callback methods inside of the #if UNITY_EDITOR preprocessor directive:
    Code (CSharp):
    1. #if UNITY_EDITOR
    2.  
    3. void OnValidate()
    4. {
    5.  
    6. }
    7.  
    8. #endif
    Any code between the directive will not be included in the standalone build.