• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
Question by LightStriker · Oct 07, 2013 at 04:46 PM · sceneprojectlevelmanager

Level Independant Data

Currently, in our projects, we have an empty level with a single GameObject that contains a collection of managers.

Frankly, I find that structure quite horrible for many reasons and I'm searching for something that is more level independent when it comes to project-wide data. I would like to save all my project-wide data as asset of ScriptableObject and as soon as the project load (editor or not), it would load that data.

Is there a way to setup a project to it would automatically load specific structure of data?

I've tried [InitializeOnLoad], but it is an Editor only attribute.

I've tried a self-initializing ScriptableObject, but Unity doesn't like ScriptableObject being created that way.

I've tried a self-initializing singleton that does not derive from ScriptableObject, but Unity blocks everything that is not from his own thread.

I haven't managed to find a way to create a GameObject on load to host a MonoBehaviour.

Or am I forced to always have an empty scene with only a single GameObject hosting managers?

Comment
Jamora
vexe

People who like this

2 Show 0
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

2 Replies

  • Sort: 
avatar image
Best Answer

Answer by LightStriker · Oct 07, 2013 at 06:52 PM

While Jamora's solution does work, it also doesn't do everything I wanted. Namely, it's not just about data but also what I call a manager. In this case, it's a singleton that handle a specific job, like a SoundManager. It may needs to be updated or not. But mostly, I want that data to be available for the user across all level. I also want the user to create new scene without the need to ever drop a GameObject that run logic. Let's call that "create and play".

Another advantage of storing data outside a scene is that the user can modify it without having to load or save any scene or update any object. Clicking the .asset in the Universe folder bring the manager on the inspector and allows you to directly change its values.

First, I enforce a singleton pattern in a class that derive from ScriptableObject. It is only a safeguard as each Manager should exist only at once spot. I'm also a lazy bastard who hates to rewrite the same pattern over and over.

 [Serializable]
 public abstract class Manager : ScriptableObject 
 {
     /// <summary>
     /// Update called by Universe
     /// </summary>
     public virtual void Update() { }
 
     /// <summary>
     /// Called when a manager is loaded.
     /// </summary>
     public virtual void Deserialize() { }
 }
 
 /// <summary>
 /// Base manager class that inforces singleton pattern.
 /// Your class should derive from this, not directly from the non-generic Manager.
 /// </summary>
 /// <typeparam name="T">Self</typeparam>
 [Serializable]
 public abstract class Manager<T> : Manager where T : Manager<T>
 {
     private static T instance = null;
 
     public static T Instance
     {
         get 
         {
             if (instance == null)
                 instance = ScriptableObject.CreateInstance<T>();
 
             return instance;
         }
     }
 
     protected Manager() { }
 
     /// <summary>
     /// Called when a deserialized version is loaded.
     /// </summary>
     public override void Deserialize()
     {
         instance = (T)this;
     }
 }

Secondly, I have a Universe MonoBehaviour (yes, I didn't find a way without it) that loads those Managers in run time. There is a "Loading" scene, but it only contains an empty Universe. Other scene created by the users does not, but a tool adds a hidden one automatically.

 /// <summary>
 /// Entry point of all game-wide serialized data.
 /// The Universe is a self-regulating script.
 /// When awaken, it loads all the possible game Managers existing in the Assemblies.
 /// </summary>
 [Serializable]
 [AddComponentMenu("")]
 public sealed class Universe : MonoBehaviour
 {
     private List<Manager> managers = new List<Manager>();
 
     public Manager[] Managers
     {
         get { return managers.ToArray(); }
     }
 
     // Self initializing.
     private static Universe instance;
 
     public static Universe Instance
     {
         get { return instance; }
     }
 
     private bool initialized = false;
 
 #if UNITY_EDITOR
     public delegate void NewManagerEventHandler(object sender, NewManagerEventArgs e);
 
     /// <summary>
     /// Fired when a new Manager type is found. Editor Only.
     /// </summary>
     public static event NewManagerEventHandler NewManager;
 
     public class NewManagerEventArgs
     {
         private Manager manager;
 
         public Manager Manager
         {
             get { return manager; }
         }
 
         public NewManagerEventArgs(Manager manager)
         {
             this.manager = manager;
         }
     }
 #endif
 
     private void OnEnable()
     {
         Initialize();
     }
 
     private void Update()
     {
         foreach (Manager manager in managers)
             manager.Update();
     }
 
     public void Initialize()
     {
         if (instance == null)
             instance = this;
         else if (instance != null && instance != this)
             Destroy(gameObject);
 
         if (initialized)
             return;
 
         Deserialize();
 
         initialized = true;
     }
 
     private static void Deserialize()
     {
         if (instance == null)
             return;
 
         foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
         {
             foreach (Type type in assembly.GetTypes())
             {
                 if (typeof(Manager).IsAssignableFrom(type) && !type.IsAbstract)
                 {
                     Manager manager = Resources.Load("Universe/" + type.Name) as Manager;
 
                     // If a manager is not loaded, it's because it is a new one that was never serialized before.
                     // In all aspect, that should only happens within the scope of the Editor as a coder add a new Manager type.
                     if (manager == null)
                     {
 #if UNITY_EDITOR
                         PropertyInfo info = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                         manager = info.GetValue(null, null) as Manager;
 
                         if (manager != null && NewManager != null)
                             NewManager(Universe.Instance, new NewManagerEventArgs(manager));
 #endif
                     }
                     else
                         manager.Deserialize();
 
                     if (manager != null && !ManagerExist(type))
                         Instance.managers.Add(manager);
                 }
             }
         }
     }
 
     private static bool ManagerExist(Type type)
     {
         foreach (Manager manager in Instance.managers)
         {
             if (manager.GetType() == type)
                 return true;
         }
 
         return false;
     }
 }

Finally, the tool (editor only) that make sure the Universe always exists. Should a new Manager type be found, if a coder creates a new one, it serialize it as a .asset in the Resources folder so the Universe can load it in-game.

 // <summary>
 /// This simple tool is there to guaranty that one Universe exist at all time.
 /// Should a new Manager be found, it saves it as an Asset.
 /// </summary>
 [InitializeOnLoad]
 public class UniverseTool
 {
     static UniverseTool()
     {
         EditorApplication.hierarchyWindowChanged += HierarchyChanged;
         Universe.NewManager += NewManager;
     }
 
     private static void NewManager(object sender, Universe.NewManagerEventArgs e)
     {
         CreateAsset<Manager>(e.Manager);
     }
 
     private static void HierarchyChanged()
     {
         GameObject go = GameObject.Find("Universe");
 
         if (go == null)
         {
             go = new GameObject("Universe");
             go.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector | HideFlags.NotEditable;
         }
 
         Universe universe = go.GetComponent<Universe>();
 
         if (universe == null)
             universe = go.AddComponent<Universe>();
 
         if (!Application.isPlaying)
             universe.Initialize();
     }
 
     /// <summary>
     /// TODO: Rework to show a display of all Managers contained in the Universe.
     /// </summary>
     [MenuItem("Tool/Show Universe")]
     public static void DisplayUniverse()
     {
         Selection.activeObject = Universe.Instance;
     }
 
     /// <summary>
     /// This saves the Manager in the Resources Folder for in-game retreival.
     /// </summary>
     public static void CreateAsset<T>(T asset) where T : ScriptableObject
     {
         string assetPathAndName = "Assets/Resources/Universe/" + asset.GetType().ToString() + ".asset";
         AssetDatabase.CreateAsset(asset, assetPathAndName);
         AssetDatabase.SaveAssets();
     }
 }
Comment

People who like this

0 Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Jamora · Oct 07, 2013 at 07:01 PM 0
Share

Good thinking on saving the ScriptableObject on disk to be modifiable in the editor.

avatar image

Answer by Jamora · Oct 07, 2013 at 05:17 PM

You could use Monostates. They are a different take on Singletons: instead of having only one object forced on the system (the singleton), Monostates store the data as static, thus ensuring only one set of data will ever exist, but the object from which the data is accessed may be created with new. So essentially, you may be using a Monostate without even knowing it; impossible with Singletons.

The code looks like this:

 /*The actual Monostate object*/
 [System.Serializable]
 public class DataSet : ScriptableObject{
     [SerializeField]
     public List<GameObject> allEnemies;
     /*all kinds of other important data*/
     
     
     void OnEnable(){
         DontDestroyOnLoad(this);
         if(IsFirstRun()){
             /**
              * This block is run if there is no serialized 
              * state to restore. That means the first time 
              * this Object is created. Possibly a bit redundant
              * because this object is supposed to be created only once
              **/
         }
     }
     
     private bool IsFirstRun(){
         bool firstRun = false;

         if(allEnemies == null){
             allEnemies = new List<GameObject>();
             firstRun = true;
         }
         //successive checks would have firstRun = firstRun && true;
         return firstRun;
     }
 }
 
 /** 
  * The object which is used to access the Monostate, 
  * can be added to as many GameObjects as necessary
  **/
 public class DataHandler : MonoBehaviour{
 
     private static DataSet monostate;
     
     void Awake(){
         monostate = (DataSet)Object.FindObjectOfType(typeof(DataSet));
         if(monostate == null)
             monostate = ScriptableObject.CreateInstance<DataSet>();
     }
     
     public List<GameObject> GetAllEnemies(){
         return monostate.allEnemies;
     }
 }

You can now add DataHandler to any GameObject that needs to access the data, and still only have one set of data to access.

I find this method to be neater than creating singletons... sometimes.

If you want the monostate to be pretty much exactly like a singleton, in that you can access it anywhere, you can do this:

 public static class GameObjectExtension {
 
     public static DataHandler GetDataHandler(this GameObject go){
         DataHandler result = go.GetComponent<DataHandler>();
         if(result == null)
             result = go.AddComponent<DataHandler>();
         result.hideFlags = HideFlags.HideInInspector;
         return result;
     }
 }

This way you don't even have to worry about adding the DataHandler to your prefabs manually. Using it would be as simple as

 gameObject.GetDataHandler().GetAllEnemies();
Comment
vexe

People who like this

1 Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image LightStriker · Oct 07, 2013 at 05:42 PM 0
Share

Little problem... You're using a MonoBehaviour, which forces the existence of a scene and a GameObject. I'm trying to get ride of empty scene that only hold data. I would like to also make all my levels playable without having to pass by a "Loading" scene first and without the need to add GameObject or managers to all my scenes.

avatar image Jamora · Oct 07, 2013 at 05:54 PM 0
Share

You'll need a scene to be able to play your game. You will also need GameObjects to make a game. I don't see the problem.

Any scene will do to initialize the monostate, as long as it has a GameObject that contains an instance of DataHandler . That GameObject could be an enemy prefab, or maybe the player. Any, or both, will do. Heck, have an instance of DataHandler in each GameObject, hide it in the inspector so you can access if from anywhere, like a Singleton.

Depending on how you want to initialize the monostate, it could start with an empty list of enemies, or it could maybe even instantiate a few...

avatar image LightStriker · Oct 07, 2013 at 06:29 PM 0
Share

I agree GameObject are needed... for a level. I just don't see why we should need them to have a scene dedicated only to loading other scene or even handling project-wide data.

I've implemented a solution with ScriptableObject saved as .asset. On top, I got a tool that make sure any level loaded in the editor is playable without passing by a "Loading" level. This setup also means if I add a new "manager", I don't have to add it to any scene at all as it exist as an Asset loaded in-game.

avatar image Jamora · Oct 07, 2013 at 06:35 PM 0
Share

With Monostates, you don't need a Loading scene; the data is initialized when the GameObject that contains an instance of its handler is loaded. That might happen in the main menu, perhaps when the first enemy is killed... maybe never.

If you found a solution that works for you, do post it and mark it as correct so future visitors may be helped as well.

avatar image LightStriker · Oct 07, 2013 at 06:54 PM 0
Share

I just did. Feel free to comment if you think what I did is stupid (it may very well be).

Unity Answers is in Read-Only mode

Unity Answers content will be migrated to a new Community platform and we are aiming to launch a public beta by June 9. Please note, Unity Answers is now in read-only so we can prepare for the final data migration.

For more information and updates, please read our full announcement thread in the Unity Forum.

Follow this Question

Answers Answers and Comments

16 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Failed to load scene after copying project 1 Answer

How to stop loading of a scene that's loading using SceneManager.LoadSceneAsync ? 1 Answer

I cant animate anything in bootcamp demo ?! 1 Answer

Can't drag assets from Project folder to Hierarchy/Scene 2 Answers

Can't find Monobehaviours in Asset Folder when using ObjectPicker 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges