Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Help with creating ScriptableObject instances (and using them) (Errors)

Discussion in 'Scripting' started by MDragon, Jan 17, 2014.

  1. MDragon

    MDragon

    Joined:
    Dec 26, 2013
    Posts:
    329
    Edit: For anyone trying to follow this and want to see what I originally had, this is my first thread:
    http://forum.unity3d.com/threads/22...nce-object-from-string-(or-other-suggestions)

    Hello, this is pretty much a follow up of my last thread, where I asked about how to match a string (typed into the editor) with an instance of a class.

    The top answer I got was to use ScriptableObjects, save to an asset, etc. I'm not sure yet how to save the objects to an asset nor how to use a string to find the whole instance by the instance's name (advice on that would also be helpful), but I'm stuck on creating the instances themselves currently.

    So, the best instructions I could find was this:
    http://answers.unity3d.com/questions/310847/how-to-create-instance-of-scriptableobject-and-pas.html
    I tried every example, but just couldn't do it.

    Here's what I originally had:
    http://forum.unity3d.com/threads/22...-(or-other-suggestions)?p=1487196#post1487196

    Here's what I've been trying to do: (the last example by vexe seemed the most complete. I have very little experience with C# classes and classes within classes and such, so I'm not sure if I'm doing this right... However, vexe seemed to have the most thorough example)
    Please note that I put quite a few questions and snippets of my "understanding" of ScriptableObjects in there in many comments.
    Code (csharp):
    1.     // By the way, script is a normal script on an empty GameObject that also does
    2.     // other things and is called on constantly. Do I have to make this it's own script?
    3.     // Anything else I'm potentially missing?
    4.     [System.Serializable]        // I know this is verrry necessary on the BASE class
    5.     public class levelData : ScriptableObject{        // Is the scriptableobject part always
    6.                                                     // necessary?
    7.     // Read to use below on every field, but also read it's not necessary on public ones...?
    8. // Read = past tense of read... Realized the above wasn't clear
    9.         [SerializeField]
    10.         public string worldID{ get; set;}
    11.         [SerializeField]
    12.         public string levelID{ get; set;}
    13.         [SerializeField]
    14.         public string levelName{ get; set;}
    15.         [SerializeField]
    16.         public int totalPickups{ get; set;}
    17.         [SerializeField]
    18.         public bool secretCheck{ get; set;}
    19.     }
    20.  
    21.     // Below is my more recent testing example, doing this slowly one by one so far
    22.     [System.Serializable]
    23.     public class worldData : ScriptableObject{
    24.         [SerializeField]
    25.         public string worldID{ get; set;}
    26.         [SerializeField]
    27.         public int levelsTotal{ get; set;}
    28.         [SerializeField]
    29.         public int secretsTotal{ get; set;}
    30.    
    31.     // Function in a class... Okay
    32.         public void Init(string worldID, int levelsTotal, int secretsTotal)
    33.         {
    34.             this.worldID = worldID;
    35.             this.levelsTotal = levelsTotal;
    36.             this.secretsTotal = secretsTotal;
    37.         }
    38.  
    39.         public static worldData CreateInstance(string worldID, int levelsTotal, int secretsTotal)
    40.         {
    41.             var data = ScriptableObject.CreateInstance<Control.worldData>();
    42.                     // Tried only worldData, then tried Control.worldData
    43.                     // as Control is the name of the MonoBehaviour overarching script
    44.             data.Init(worldID, levelsTotal, secretsTotal);
    45.             return data;
    46.         }
    47.    
    48.     }
    49.    
    50.     // The below is done right AFTER, not even in a Start() function...
    51.         // ...this is correct right?
    52.         // and also, how do I NOT create a double instance
    53.         // since I'm assuming this is done every time the script is used
    54.     worldData W1 = worldData.CreateInstance("W1", 4, 0);
    Also, here are the errors I got: (the ones with this most recent version)
    • NullReferenceException: Object reference not set to an instance of an object
      Control+worldData.CreateInstance (System.String worldID, Int32 levelsTotal, Int32 secretsTotal) (at Assets/Scripts/Control.cs:71)
      Control..ctor ()
    • NullReferenceException: Object reference not set to an instance of an object
      Control+worldData.CreateInstance (System.String worldID, Int32 levelsTotal, Int32 secretsTotal) (at Assets/Scripts/Control.cs:71)
      Control..ctor ()
    • CreateInstanceFromType can only be called from the main thread.
      Constructors and field initializers will be executed from the loading thread when loading a scene.
      Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function. Wait... How else am I supposed to use this properly?
    • ArgumentException: CreateInstanceFromType can only be called from the main thread.
      Constructors and field initializers will be executed from the loading thread when loading a scene.
      Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
      UnityEngine.ScriptableObject.CreateInstance (System.Type type) (at C:/BuildAgent/work/d3d49558e4d408f4/artifacts/EditorGenerated/BaseClass.cs:602)
      UnityEngine.ScriptableObject.CreateInstance[worldData] () (at C:/BuildAgent/work/d3d49558e4d408f4/artifacts/EditorGenerated/BaseClass.cs:613)
      Control+worldData.CreateInstance (System.String worldID, Int32 levelsTotal, Int32 secretsTotal) (at Assets/Scripts/Control.cs:70)
      Control..ctor ()
    • Warning: The class defined in script file named '' does not match the file name!
      UnityEngine.ScriptableObject:CreateInstance()
      worldData:CreateInstance(String, Int32, Int32) (at Assets/Scripts/Control.cs:70)
      Control:.ctor()
      UnityEditorInternal.InternalEditorUtility:GetGameObjectInstanceIDFromComponent(Int32)
      UnityEditor.DockArea:OnGUI()
    • NullReferenceException: Object reference not set to an instance of an object
      Control+worldData.CreateInstance (System.String worldID, Int32 levelsTotal, Int32 secretsTotal) (at Assets/Scripts/Control.cs:71)
      Control..ctor ()
      UnityEditorInternal.InternalEditorUtility:GetGameObjectInstanceIDFromComponent(Int32)
      UnityEditor.DockArea:OnGUI()

    Sorry for the huge post, all the messy errors, and all my confusion over this topic. And thank you!

    ---------------------------------------------------

    Note: I realized a few things just while typing up this whole post, but these are my guesses and I couldn't find out for sure

    1. "ScriptableObjects" should be its own script (like normally each script is public class NameHere : Monobehaviour{}, and levelData and worldData should be their own files
    2. The instance stuff should be done in the Start() functions
    However, if the above is true, I'm even more confused on how to use these; if I'm making an instance each time I make these files, won't I have multiple instances? Won't they technically be temporary? Even if I save them to an asset, how do I retrieve them whole and use them, particularly by finding them with a string?

    Oh wait... I think I've found the answers to those last questions a little while before, except for that last one. That last one is still eluding my searches, and I'm still unsure if my (newest) conceptions are correct. A little advice would be more than simply appreciated :D

    Thank you for reading this extremely loaded arrays of questions.

    Edit2: I am going to guess my C# skills are quite shaky. Glanced at another thread, and other classes are actually outside of the whole "main" Monobehaviour class. However, I'm then still not sure if the other public class should have it's own Start() function or such to initialize instances of it, how/when/where to save it exactly, etc...

    It may be time for me to actually get a programming book or actually go through some real C# tutorials online. But that's after the month of January, when I actually have time for that :)
     
    Last edited: Jan 19, 2014
  2. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Frankly, I'm not sure how you manage to call CreateInstance from outside Unity's main thread. The "Use Start or Awake" is only an advice. The only limitation is that it must be called from within Unity's main thread.

    Let's do an example real quick...

    Code (csharp):
    1.  
    2. public class MyData : ScriptableObject
    3. {
    4.     public string text;
    5. }
    6.  
    The above should be in MyData.cs

    Not to hard so far... right?

    Now, let's make a editor to create instances of that.

    Code (csharp):
    1.  
    2. using UnityEditor;
    3.  
    4. public class DataGenerator
    5. {
    6.     [MenuItem("Data/Make Some Data")]
    7.     static void Init()
    8.     {
    9.         MyData data = ScriptableObject.CreateInstance<MyData>();
    10.         data.text = "Hello World!";
    11.         AssetDatabase.CreateAsset(data, "Assets/data.asset");
    12.     }
    13. }
    14.  
    The above should be in /Editor/DataGenerator.cs

    You should now have a new menu (you may need to click the menu bar to refresh it) with Data > Make Some Data.
    Selecting it should make a new file named "data.asset" in your Assets folder.

    If you select that file in your Project panel, it should show "Hello World!" in the inspector.
     
    Last edited: Jan 17, 2014
    Carterryan1990 likes this.
  3. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Let's review your code...


    Code (csharp):
    1.  
    2.     [System.Serializable]        // I know this is verrry necessary on the BASE class
    3.  
    But not needed on a class deriving from ScriptableObject. Every ScriptableObject are serializable. I guess it could stay there for readability.

    Code (csharp):
    1.  
    2.     public class levelData : ScriptableObject{        // Is the scriptableobject part always necessary?
    3.  
    Necessary? You do know how inheritance works, right?

    http://msdn.microsoft.com/en-us/library/ms173149.aspx

    Code (csharp):
    1.  
    2.     // Read to use below on every field, but also read it's not necessary on public ones...?
    3. // Read = past tense of read... Realized the above wasn't clear
    4.  
    5.         [SerializeField]
    6.         public string worldID{ get; set;}
    7.  
    What you wrote there is not a field, but an auto-implemented property. The attributes "SerializeField" should only be used on a field, not a property.

    "Attributes are permitted on auto-implemented properties but obviously not on the backing fields since those are not accessible from your source code. If you must use an attribute on the backing field of a property, just create a regular property."

    http://msdn.microsoft.com/en-us/library/bb384054.aspx

    So, you can do;

    Code (csharp):
    1.  
    2. public string worldID;
    3.  
    Which is a public field. Public fields are always serializable by Unity.

    or;

    Code (csharp):
    1.  
    2. [SerializeField]
    3. private string worldID;
    4.  
    By default, private fields are not serializable. However, the attribute "SerializeField" make them serializable.
     
  4. MDragon

    MDragon

    Joined:
    Dec 26, 2013
    Posts:
    329
    The line "Let's review your code..." made me appropriately quite nervous. After all, it basically says "You have a ton of problems, but let's go through this slowly..." :D Also, I wasn't sure what "ScriptableObject" actually meant once a class was declared to be one, so just in case, I used [System.Serializable].

    It all works perfectly LightStriker as you described. Now all I have to do is customize this to easily create my data seamlessly from the Data menu, including the ability to specify name and put in all the properties right from there. Not only did you give me every detail from the basics for serialization and ScriptableObjects that I've been looking for two days straight, but you also gave me an intro to creating a custom editor.

    Once again - from all these attempts to educate me on these topics - thank you, not only for directing me so seamlessly and easily, but for also having patience with me giving more questions to your answers.

    And hopefully I can easily accomplish my ultimate goal without anymore questions: using a string to find the appropriate class and its data. :)
     
  5. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Well, I didn't talk about making an EditorWindow just yet... The Editor class is usually used for making a block show up in the Inspector, which is not the case here. Just looking again at the code I posted, I could have NOT derived from Editor and it would have worked anyway. My error for copy pasting stuff quickly from another implementation.

    Edited my first post to remove the Editor inheritance.

    Editor is when you want to customize how the Inspector display something. http://docs.unity3d.com/Documentation/ScriptReference/Editor.html

    EditorWindow is when you want to make a custom window. http://docs.unity3d.com/Documentation/ScriptReference/EditorWindow.html

    MenuItem is an Attribute you can put on any static method to have a new item in the menu. The class itself doesn't have to derive from anything. http://docs.unity3d.com/Documentation/ScriptReference/MenuItem.html
     
    Last edited: Jan 17, 2014
  6. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    This should be pretty straightforward. If you're creating specific things like:

    SuperBossEnemy1
    and
    SuperBossEnemy2

    They can be the same class type and you can just save them with a different asset file name... so no fancy string work is required. Just load the correct asset file based on whatever key you decide to identify them by (which will become the file name).

    i.e. "Assets/superboss1.asset"
    and
    "Assets/superboss2.asset"
     
  7. MDragon

    MDragon

    Joined:
    Dec 26, 2013
    Posts:
    329
    Thanks, Dustin, that makes sense, seeing as I can even make the file name using a string.

    By the way, LightStriker, an EditorWindow can technically do everything an Editor can for the most part, correct? And I'm going to have to read all those links as soon as possible, once I get back on schedule.

    And, as for an update, I'm doing very well at the moment. I'll post my current two scripts for anyone who falls into the same ditch as me. All I have to do now is actually read the data, which should be the easiest part.

    Here's my LevelData.cs script, the ScriptableObject
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class LevelData : ScriptableObject
    5. {
    6.     public string fileName;
    7.     public string worldID;
    8.     public string levelID;
    9.     public string levelName;
    10.     public int totalPickups;
    11.     public bool secretCheck;
    12. }
    Here's my CreateLevelData.cs script, which works quite well:
    Code (csharp):
    1. #pragma warning disable 0219 // variable assigned but not used.
    2. // above is to ignore the warning that window is assigned but never used
    3.     // Comment it out before building the game
    4.  
    5. /*\ Note to self:
    6.  *  1) Create more options
    7.  *  2) Make more general/expansive to work with multiple types
    8.  *     3) Make another function so another file can easily be created
    9.  *         without closing the window
    10. \*/
    11.  
    12. using UnityEngine;
    13. using UnityEditor;
    14.  
    15. public class CreateLevelData : EditorWindow {
    16.     string fileName = "";
    17.     string worldID = "";
    18.     string levelID = "";
    19.     string levelName = "";
    20.     int totalPickups = -1;
    21.     bool secretCheck = false;
    22.  
    23.     bool groupEnabled = false;        // Flag for optional field(s)
    24.    
    25.     // Add menu named "My Window" to the Window menu
    26.     [MenuItem ("Data/Create Level Data")]
    27.     static void Init () {
    28.         // Get existing open window or if none, make a new one:
    29.         CreateLevelData window = (CreateLevelData)EditorWindow.GetWindow (typeof (CreateLevelData));
    30.     }
    31.    
    32.     void OnGUI () {
    33.         GUILayout.Label ("Base Settings", EditorStyles.boldLabel);
    34.         fileName = EditorGUILayout.TextField ("Insert file name", fileName);
    35.         worldID = EditorGUILayout.TextField ("Insert the world ID", worldID);
    36.         levelID = EditorGUILayout.TextField ("Insert the level ID", levelID);
    37.         totalPickups = EditorGUILayout.IntField ("Insert Total Pickups", totalPickups);
    38.         secretCheck = EditorGUILayout.Toggle ("Secret in level?", secretCheck);
    39.  
    40.         // Optional stuff below
    41.         groupEnabled = EditorGUILayout.BeginToggleGroup ("Optional Settings", groupEnabled);
    42.         levelName = EditorGUILayout.TextField ("Inner Level Name", levelName);
    43.         EditorGUILayout.EndToggleGroup ();
    44.  
    45.         // Button to finalize the data, create the file, and close the window...
    46.         if(GUILayout.Button("Create!")){
    47.             Create ();
    48.         }
    49.     }
    50.  
    51.     void Create(){
    52.  
    53.         LevelData data = ScriptableObject.CreateInstance<LevelData>();
    54.         data.fileName = fileName;
    55.         data.worldID = "";
    56.         data.levelID = "";
    57.         if(!groupEnabled)
    58.             data.levelName = fileName; // If no custom levelName, same name as fileName
    59.         else
    60.             data.levelName = levelName;    
    61.         data.totalPickups = totalPickups;
    62.         data.secretCheck = secretCheck;
    63.         AssetDatabase.CreateAsset(data, "Assets/Resources/" + fileName + ".asset");
    64.         this.Close ();
    65.     }
    66. }
    Edit: And if anyone wants help on doing the very last part (accessing the asset and reading from it..)

    Code (csharp):
    1.     // This is in a normal Monobehaviour script
    2.     LevelData thisLevel;
    3.    
    4.     void Start(){
    5.         thisLevel = (LevelData)AssetDatabase.LoadAssetAtPath("Assets/Resources/W1L1.asset",
    6.                                                             typeof(LevelData));
    7.     }
     
    Last edited: Jan 18, 2014
    monremobile likes this.
  8. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,788
    Uh, that's not going to work in a build outside of the Unity Editor. You can't use AssetDatabase class or anything else that belongs to the UnityEditor namespace - use Resources.Load instead and make sure your asset is in a Resources folder. Or even better, make thisLevel a public field so that you can edit its value in the Inspector, then you won't even have to load it manually like that, Unity will do that for you ;)
     
    monremobile likes this.
  9. MDragon

    MDragon

    Joined:
    Dec 26, 2013
    Posts:
    329
    Ohh... That's the alternative. I put "using UnityEditor;" at the top of this script. :)

    And is there much of a performance difference? I'll probably change it in a bit, but I'm curious if putting the UnityEditor part on top and using AssetDatabase will notably drop the performance. (Although every little bit helps with a mobile game anyways ;) )
     
  10. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    There's no performance issue, because AssetDatabase cannot be used in a deployed game. When you put "using UnityEditor" in a script, this script must be inside the Editor folder otherwise it won't compile properly when you build your game. (Or use compile time operator)

    AssetDatabase is an editor class aimed at managing and building your assets. However, once deployed, you have no use in managing or creating a database of assets. Resources.Load is there to allow you to load manually files created by AssetDatabase. "Resources" is not in UnityEditor, but in UnityEngine, which means you can deploy it.

    You create with one, you load with the other.
     
    dylanfries likes this.
  11. MDragon

    MDragon

    Joined:
    Dec 26, 2013
    Posts:
    329
    Thanks, that clarifies it all. Took me quite a bit to work with the errors, because I pretty much copied and pasted the example from the Script Reference. In the end, nearly the same as AssetDatabase, but even easier as it already looks in the Resources folder and omits the need for the extension.

    For anyone out there...
    Code (csharp):
    1. thisLevel = (LevelData)Resources.Load (levelName, typeof(LevelData));
    Also, FlipBookee, I did it to easily create and change level data without going into every scene. AKA laziness? ;)
    Oh yeah, another reason why I did it is so the data can be accessed anywhere. For example, in the Level Select menu, some of the data for each level will be dynamically displayed (level name, total number of items in level, etc).
     
    Last edited: Jan 19, 2014
    monremobile likes this.
  12. dylanfries

    dylanfries

    Joined:
    Jul 11, 2012
    Posts:
    16
    Thanks both of you for sharing this breakdown and taking the time to unpack and share it all!:)
     
  13. Fetzu-ma

    Fetzu-ma

    Joined:
    Jan 13, 2018
    Posts:
    14
    You are a powerful Necromancer my friend.. be sure to wield your might carefully.. :p
     
  14. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    He was thanking some folks from years ago who happened to have solved an issue for him today.

    I think this display of gratefulness is a good thing. Your snarky retort is not so good.
     
  15. Fetzu-ma

    Fetzu-ma

    Joined:
    Jan 13, 2018
    Posts:
    14
    Snarky? I was going for funny.. Yours however is most definitely snarky. Have a good day mate.
     
  16. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    well since we're thrashing at this dead horse...

    There is a "LIKE" button on every post, it lets the dead rest in peace and pings a notification to the person you liked that it happened. This is much better than resurrecting an old thread with a response which won't tell anyone but those looking at the latest posts on this forum (we're a problem solving sub forum so getting old threads pop up with nothing added is "clutter"), especially since the thread is by someone who hasn't been on the forums in 3 years (that info in the user profile btw) so they're most likely not going to see this at all.
     
    MitchStan likes this.
  17. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    You as well. And try and work on your sense of humor.
     
  18. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    Excellent, positive, constructive suggestion.
     
  19. HB-WorX

    HB-WorX

    Joined:
    Oct 2, 2018
    Posts:
    2