Search Unity

Assign and access static variables in edit and play mode

Discussion in 'Scripting' started by Ravery, Nov 27, 2017.

  1. Ravery

    Ravery

    Joined:
    Mar 5, 2016
    Posts:
    49
    Is there a possibility to do the following?

    Create a class "ItemList", which holds a static reference to a dictionary of Items (a bunch of ScriptableObject instances).
    Access the class with static functions (e.g. "public static Item GetItem(string name)") inside the Editor and during Play.
    This should survive Editor restarts and should work, when I build and distribute the game.

    Some might say "Just drop it into the scene and...", but I'm searching for a solution, which will work on a project basis (i.e. accessible from all scenes, accessible in the editor (no Awake Call))
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Unity doesn't directly support displaying or serializing static fields/properties.

    You can write a custom editor that would draw said field/property to whatever editor you'd like. But this still doesn't resolve the serialization part.

    And also, Dictionary isn't directly serializable either, so you're going to have to write some sort of custom serializer for that... let alone the fact you're going to have to figure out in what data store you'd be placing said serialized static data. It wouldn't be stored in the scene data since it technically persists globally, as opposed to scene by scene.

    ...

    Personally, what I'd do is create a ScriptableObject, in which I put all my fields necessary for serialization. Make said ScriptableObject a singleton with a static 'Instance' property that returns the singleton instance of it.

    I do this with this base class:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. namespace com.spacepuppy.Project
    5. {
    6.  
    7.     /// <summary>
    8.     /// A singleton Game entry point
    9.     /// </summary>
    10.     public abstract class GameSettings : ScriptableObject
    11.     {
    12.      
    13.         public const string PATH_DEFAULTSETTINGS = "GameSettings";
    14.         public const string PATH_DEFAULTSETTINGS_FULL = @"Assets/Resources/GameSettings.asset";
    15.  
    16.         #region CONSTRUCTOR
    17.  
    18.         protected void Awake()
    19.         {
    20.             if (_instance != null && _instance != this)
    21.                 throw new System.InvalidOperationException("Attempted to create multiple GameSettings. Please get instances of the game settings via the static interface GameSettingsBase.GetGameSettings.");
    22.         }
    23.  
    24.         protected abstract void OnInitialized();
    25.  
    26.         protected virtual void OnDestroy()
    27.         {
    28.             if (_instance == this)
    29.                 _instance = null;
    30.         }
    31.  
    32.         #endregion
    33.  
    34.         #region Static Factory
    35.  
    36.         private static GameSettings _instance;
    37.  
    38.         public static GameSettings GetGameSettings(string path = null)
    39.         {
    40.             if(_instance == null)
    41.             {
    42.                 if (path == null) path = PATH_DEFAULTSETTINGS;
    43.                 //_instance = Object.Instantiate(Resources.Load(path)) as GameSettings;
    44.                 _instance = Resources.Load(path) as GameSettings;
    45.                 if (_instance != null) _instance.OnInitialized();
    46.                 return _instance;
    47.             }
    48.             else
    49.             {
    50.                 return _instance;
    51.             }
    52.         }
    53.  
    54.         public static T GetGameSettings<T>(string path = null) where T : GameSettings
    55.         {
    56.             if (_instance == null)
    57.             {
    58.                 if (path == null) path = PATH_DEFAULTSETTINGS;
    59.                 //_instance = Object.Instantiate(Resources.Load(path)) as T;
    60.                 _instance = Resources.Load(path) as T;
    61.                 if (_instance != null) _instance.OnInitialized();
    62.                 return _instance as T;
    63.             }
    64.             else
    65.             {
    66.                 return _instance as T;
    67.             }
    68.         }
    69.  
    70.         #endregion
    71.  
    72.     }
    73.  
    74. }
    75.  
    Which I then implement on a project by project basis like so:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using com.spacepuppy.Project;
    4.  
    5. [CreateAssetMenu(fileName = "GameSettings", menuName = "Spacepuppy/Game Settings", order = int.MaxValue)]
    6. public class Game : GameSettings
    7. {
    8.  
    9.     #region Singleton Access
    10.  
    11.     private static Game _instance;
    12.  
    13.     public static bool Initialized { get { return _instance != null; } }
    14.  
    15.     public static void Init()
    16.     {
    17.         _instance = GameSettings.GetGameSettings<Game>();
    18.     }
    19.  
    20.     public static Game Settings
    21.     {
    22.         get
    23.         {
    24.             return _instance;
    25.         }
    26.     }
    27.  
    28.     #endregion
    29.  
    30.     #region Fields
    31.  
    32.     public string SomePropertyToSerialize;
    33.  
    34.     #endregion
    35.  
    36.     #region CONSTRUCTOR
    37.  
    38.     protected override void OnInitialized()
    39.     {
    40.         //do any initializing you may want for the game
    41.     }
    42.  
    43.     #endregion
    44.  
    45. }
    46.  
    But anyways... that's how I do it.

    Of course you can bake this into a single class... mine is just like this because of the base implementation is in a shared library.

    Note the location of the GameSettings object in the assets folder. If you put in somewhere else, you of course have to change said values.
     
  3. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
    The only thing I've done so far is make a class like:
    Code (csharp):
    1.  
    2. [System.Serializable]
    3. class Whatever{
    4. public string name;
    5. public int data;
    6. }
    7.  
    8.  
    Then make a public List<Whatever>() on an object that is DontDestroyOnLoad

    Then I can fill out the list in the editor with collapsible elements. The thing about a class is that it's easy to change and save.
     
  4. storylineteam

    storylineteam

    Joined:
    Oct 22, 2015
    Posts:
    24
    The problem with scriptableobject alone, is that the serialization works only on the unity side (no stand alone serialization).

    you can put all fields necessary for serialization in a manager Class, then save the entire manager class instance as JSON string on disk and assign it back from disk when needed.

    Code (CSharp):
    1.  
    2.         save = JsonUtility.ToJson(GameManager.instance);
    3.         BinaryFormatter bf = new BinaryFormatter();
    4.         FileStream file = File.Create(Application.persistentDataPath + "/thepath");
    5.  
    6.         savejson = save;
    7.  
    8.         bf.Serialize(file, savejson);
    9.         file.Close();
     
  5. Ravery

    Ravery

    Joined:
    Mar 5, 2016
    Posts:
    49
    @lordofduct
    Wow! Thanks a lot. I'll try this pattern. Seems really useful and reusable.


    @fire7side
    Thanks, but this is not what I was looking for. I'm trying to remove as much of the game logic and data from the scenes as possible. To a point where I try to avoid "MetaScenes" or "SettingsScenes", which are kept via DontDestroyOnLoad between scenes.


    I'm not sure I understand the "unity side" part correctly. Is the stand alone build unable to read/write from and to ScriptableObjects? Or are the changes just lost after the game is closed?