Search Unity

Question Boolean variable in script initialized (automatically) to true after first run

Discussion in 'Scripting' started by PedroPoni, Jul 8, 2020.

  1. PedroPoni

    PedroPoni

    Joined:
    Jun 21, 2017
    Posts:
    5
    Hi there,

    I've two scripts

    Code (CSharp):
    1. public abstract class ItemAction : MonoBehaviour
    2. {
    3.     /// <summary>
    4.     /// This is false ONLY the first time I run the app
    5.     /// </summary>
    6.     bool initialized;
    7.  
    8.     public void Initialize()
    9.     {
    10.         Debug.Log($"ItemAction Initialize() -> initialized: {initialized}");
    11.  
    12.         // initialized is false ONLY the first time I run the app
    13.         if (initialized)
    14.         {
    15.             Debug.LogWarning($"Already initialized");
    16.             return;
    17.         }
    18.  
    19.         Debug.Log($"Initializing ...");
    20.         initialized = true;
    21.         Init();
    22.     }
    23.  
    24.     protected virtual void Init()
    25.     {
    26.         Debug.Log($"ItemAction: Init()");
    27.     }
    28. }
    and here is a concrete class of ItemAction

    Code (CSharp):
    1. public class DoShot : ItemAction
    2. {
    3.     protected override void Init()
    4.     {
    5.         Debug.Log($"DoShot: Init()");
    6.     }
    7. }
    I've this ScriptableObject where I have a reference to an ItemAction

    Code (CSharp):
    1. [CreateAssetMenu]
    2. public class ItemActionReference : ScriptableObject
    3. {
    4.     public Sprite sprite;
    5.     public ItemAction action;
    6. }
    So I've created an asset (in the Resources folder) of type ItemActionReference and assigned to it a prefab which contains the script DoShot.

    I've another script that load the Asset LevelItemActions created in the Resources folder and call the Initialize() method of ItemAction script

    Code (CSharp):
    1. public class ResLoader : MonoBehaviour
    2. {
    3.     LevelItemActions levelItemActions;
    4.  
    5.     void Start()
    6.     {
    7.         if (!levelItemActions)
    8.             levelItemActions = Resources.Load<LevelItemActions>("LevelItemActions");
    9.  
    10.         levelItemActions.actions[0].action.Initialize();
    11.     }
    12. }
    When I run the game the first time it works as expected. When called the method ItemAction.Initialize() the variable initialized in ItemAction is false and is initialized to true but the second time I run the game the variable initialized is true so it's not initialized and the method ItemAction.Init() is not called.

    I really have no idea why is this happening.

    How is possible that the variable initialized is true when I run the app a second time? A boolean variable is false by default!

    Also is a good practice to use a prefab in the way I'm using it? I've created the prefab only because is the way I found to reference the script DoShot in the ShotAction asset.

    Any help will be appreciated!

    This game was created using Unity 2018.4.23f1 but I tested it using Unity 2019.4.0f1 and I get the same behaviour in both versions. I'm working with Windows 10x64 Version 1909 (OS Build 18363.836).

    I've uploaded the whole project (a very small runnable project) no executables, only source code because I think it's easier to analyze and debug a runnable project than analyzing the code posted here.
     

    Attached Files:

    Last edited: Jul 8, 2020
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    This will actually happen even to private fields in a ScriptableObject.

    Now I know you're seeing it happen to an instance of a private variable in a MonoBehavior, an instance of which is referenced in a public field in your ScriptableObject instance, but I wonder if it's the same mechanism.

    The good news is that with MonoBehaviors you can reliably get the Awake() call and reset variables to known states there. Alternately, I think you can decorate it with NonSerializedAttribute:

    Code (csharp):
    1. [NonSerialized]private bool MyThang;
    That's how I always do it in ScriptableObjects to TRULY get private behavior, but I've never referred into a prefab with private fields like you did. I'm curious and I'll go test now.

    The reason for this behavior is that ScriptableObjects have a weird and surprising lifecycle in Unity: they are created the first time they are referenced in any way (usually by the game starting, or clicking on them), and then they are never destroyed until Unity exits. That's very unlike MonoBehavior lifecycles.
     
    Dkp2 likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    Testing results: was unable to replicate your exact problem, but I confirmed the stickiness of the ScriptableObject instance. Full code and scene here, maybe you can tinker with it to replicate the issue you're seeing so you can reason about where to zero your data out manually.

    It could be you're seeing the issue because of subclassing, which might muck with how the serialization is handled.

    If you stick a NonSerializedAttribute on the private field in my BagOData ScriptableObject, the stickiness is fixed. Perhaps that will apply to your MonoBehaviour private in this case too?
     

    Attached Files:

  4. PedroPoni

    PedroPoni

    Joined:
    Jun 21, 2017
    Posts:
    5
    The only explanation I can think of this behavior is that because the ScriptableObject is never destroyed and it has a reference to the MonoBehaviour then the referenced Monobehaviour is not destroyed and is serialized in the same way the ScriptableObject is serialized.

    In my scenario I can't rely on any MonoBehaviour callback because I'm using the prefab only as a container for scripts so I think it doesn't make sense to add it to the scene (like you did in your test) because adding it to the scene (I guess) will only add extra overhead unnecessarily. I'm using it only as a reference in the ScriptableObject.

    And here's again my second question of the original post:

    Is a good practice to use a prefab in that way? a prefab that will never be used in any scene because it's only used as a reference in other scripts because it will be used as a Scripts's container.

    Using the NonSerialized attribute as you suggested fixed the issue!

    I think that this weird behavior must be well explained in the documentation because the only page that explain the ScriptableObject almost say nothing about them.

    https://docs.unity3d.com/Manual/class-ScriptableObject.html