OK So I have a ScriptableObject called ‘ActorIdMap’:
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Game.Framework
{
public class ActorIdMap : ScriptableObject
{
public static ActorIdMap Instance { get { return GetInstance(); } }
private static ActorIdMap _instance;
private static ActorIdMap GetInstance()
{
if (_instance == null)
{
_instance = FindObjectOfType<ActorIdMap>();
if (_instance == null)
{
_instance = CreateInstance<ActorIdMap>();
}
}
return _instance;
}
[SerializeField] private List<GameObject> _map;
public List<GameObject> Map { get { return _map; } }
[UnityEditor.MenuItem("Assets/Create/ActorIdMap")]
public static void Create()
{
ScriptableObjectUtility.CreateAsset<ActorIdMap>();
}
}
}
I create the ActorIdMap manually in the editor, before even hitting the Play button.
I add GameObjects to the ActorIdMap’s ‘Map’ field.
I start the game.
I didn’t include it here, but I actually put a lot of Debug.Log calls in the Awake / OnEnable methods which got called just fine. However, when I try to see what the value of ‘Map’ is, I get a NullReferenceException!!!
Basically, MAP IS ALWAYS NULL!
I have no idea why that is!
I created an ActorIdMap, placed a prefab gameObject into it.
In a test scene, a created a script called ActorIdMapTest and in the Start function told it to log the contents of ActorIdMap[0].
I’m not sure what you did differently, so I’ve uploaded my scene:
{rant}
That damn AssetDatabase.Refresh() had me scratching my head for a few hours until I realized what was going on and I needed to call it manually if I wanted instant updates.
{/rant}
OK I know why it works for you. It’s because you call it like this:
“map.Map[0]”
But I need to call it like this:
“ActorIdMap.Instance.Map[0]”
Calling it through Instance causes it to fail. My guess is, because _instance is always null the first time Instance is called, Instance will try to find ScriptableObjects through FindObjectOfType which fails (just tested). Which will cause it to invoke CreateInstance which will be a non-serialized instance of the object.
don’t put editor in your regular scripts… anything reference ‘UnityEditor’ namespace should be in the ‘Editor’ folder outside of your regular scripts.
What is ‘FindObjectOfType’ for? You haven’t loaded the asset anywhere… that’s just going to return null. This means that you’re getting your instance via ‘CreateInstance’…
When you call ‘CreateInstance’, it doesn’t necessarily instantiate all your objects that are fields of the object. If there is serialized data to deserialize, then yes, it would. But ‘CreateInstance’ doesn’t have ANY data to deserialize, so therefore none of the fields are auto instantiated for you. Since you never instantiate the List in code either, so it’s the default value of a List… which is null.
Hence the null reference.
Rewritten as:
using System.Collections.Generic;
using UnityEngine;
namespace Game.Framework
{
public class ActorIdMap : ScriptableObject
{
public static ActorIdMap Instance { get { return GetInstance(); } }
private static ActorIdMap _instance;
private static ActorIdMap GetInstance()
{
if (_instance == null)
{
_instance = Resources.Load<ActorIdMap>(@"ActorIdMap");
if (_instance == null)
{
_instance = CreateInstance<ActorIdMap>();
}
}
return _instance;
}
[SerializeField]
private List<GameObject> _map;
public List<GameObject> Map { get { return _map; } }
}
}
And the separate editor:
using UnityEngine;
using UnityEditor;
using System.Collections;
using Game.Framework;
public class ActorIdMapEditor
{
[UnityEditor.MenuItem("Assets/Create/ActorIdMap")]
public static void Create()
{
com.spacepuppyeditor.ScriptableObjectHelper.CreateAsset<ActorIdMap>(@"Assets/Resources/ActorIdMap.asset");
}
}
And my test script:
using UnityEngine;
using System.Collections.Generic;
using Game.Framework;
public class TestScript05 : MonoBehaviour
{
void Start()
{
Debug.Log(ActorIdMap.Instance.Map == null);
}
}
Everything works fine for me.
[edit]
I have NO idea why none of that formats correctly… ugh.
Well I beat you by a couple minutes so you can’t claim the genius prize, sorry.
JK. Thanks the resources thing worked beautifully. The only thing is, I don’t want to use a “Resources” folder or require special folder structure or naming to use my assets. I want to name my assets whatever I want and save them wherever I want and have them loaded. Is this possible?
The Resources folder exists for a reason. It tells unity what assets should be included in the project if they aren’t directly referenced by any scene. If there is no reference in the scene, how would unity know to include it? To include it, it would have to include all assets in your entire project… which would bloat up everything.
SO, if you don’t want to put it in the Resources folder. You’ll need to make sure some script somewhere is referencing the ScriptableObject as a member of itself in the inspector.
In this case ‘Resources.Load’ won’t work either, as ‘Resources’ is for loading things in the ‘Resources’ folder (hence its name).
And ‘AssetDatabase’ is editor time only.
Note, this will be scene by scene as well. If you have it referenced in SceneX, that only forces it to be included in your build, not necessarily loaded into memory. So if you haven’t loaded SceneX, it won’t be loaded either.
You could FindObjectsOfTypeAll to search for inactive objects… but this is also slow, and you’ll still need to instantiate it with Object.Instantiate once found, as it only returns the ‘asset’ representation… not an actual live instance.
On another note…
Why do you have a problem with the ‘Resources’ folder? How does having this folder effect your project negatively?
I tried it this way and it seemed to work fine. Do you see any potential problems with this? If not I’ll probably make a GameObject that loads all the resources I want at runtime.
//In monobehaviour
public ActorIdMap map;
private void Start()
{
var s = AssetDatabase.GetAssetPath(map);
Debug.Log(s);
AssetDatabase.LoadAssetAtPath(s, map.GetType());
Debug.Log("Instanced Map is " + ActorIdMap.Instance.Map[0]);
}
yes, I see a big problem with it… you ignored a part of my last post:
and the even earlier:
AssetDatabase is in the ‘UnityEditor’ namespace. The UnityEditor namespace is not included in builds.
AssetDatabase is intended for writing scripts that manipulate assets while you’re creating the game. Not running the game.
OK… then you probably shouldn’t be using game engines. That’s the entire point of the game engine… it has already designed the entire game engine to a specific standard, you adhere to the design, and reap the benefits of not having to write it yourself.
If you don’t like it telling you what to do… you shouldn’t be writing scripts that inherit from MonoBehaviour and are attached to GameObjects following the composite/component design pattern. Since… it’s only the game engine telling you to do that.
But you do… because that’s how Unity does things.
And if you want to include assets at runtime that aren’t directly referenced in a scene, unity requires you to put them in the ‘Resources’ folder.
You can think of this like how the mecanim AnimatorController is… note, they too are basically like ScriptableObjects. And the can’t be accessed unless you drag and drop them onto a script (usually the Animator component). Otherwise it’s not included in the project. That is, unless you include it in the ‘Resources’ folder!