Prefab loses Scriptable Object reference

I am creating a CutScene system extension. In this system, the user would describe a cut scene by using a set of pre programmed nodes, in an execution list. The system already works, but I am having this problem that I can’t work out.
When I create any prefab of GameObjects using my custom scripts, they lose the references to all the scriptable objects they should have.
I’m having this trouble for a while, and tried to solve it in many different ways. But none of them work. This is my problem:

I have a MonoBehaviour class called CutScene, and it is as follows:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;


public class CutScene : MonoBehaviour {

    [HideInInspector]
    public GameSwitch gameSwitch;

    public List<CutSceneNodes> nodeList = new List<CutSceneNodes>(); //CutSceneNode Class Inherits from the ScriptableObject Class

    public bool pauseGame = true;

    public CutSceneSystem css;

    [HideInInspector]
    public int indexOfSwitch = 0;

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

The CutScene Class have a property called nodeList, as seen above, which is a List of CutSceneNodes.
The CutSceneNodes class inherits from the ScriptableObject Class.

When a Game Object with the CutScene MonoBehaviour class as a component is dragged from the hierarchy to the project files to create a prefab of that game Object, the resulting prefab loses the reference to the nodeList property and all other references to any ScriptableObjects.

It’s worth mentioning, that the initialisation of the list and its contents, are made in a editor script, which instantiate the CutSceneNodes with the code:

ScriptableObject.CreateInstance<CutSceneNodes>();

The CutSceneNodes class is marked as [Serializable], and all its properties are public.

Thank you :slight_smile:

1 Like

I did some work on this issue a while ago. Never quite solved it completely, it turned out to be easier to use some custom serialization than working out all corner cases of ScriptableObjects. Even though in theory, it would be perfect for scenarios such as yours.

First of, there is quite a bit going on behind the curtain when creating a prefab, which took me a while to figure out. For comparison, let’s say you have a field on a script that references a GameObject in your scene. If you drag this into your Project to create a Prefab, the reference to the GameObject will also be lost, because the GameObject lives in a specific scene, whereas the Prefab could be created anywhere. Therefore, Unity copies all values to the prefab and everything that has a [Serializable] attribute via its serializer, but references to (Unity)Objects are lost intentionally.

If you have a ScriptableObject referenced by your script (e.g., as you say, created by an editor script) it may appear to belong to your class, but for Unity, it is in fact saved as part of the scene similar to any GameObject and component. When you create the prefab, Unity sees that there is a reference to a scene object and just skips the SerializedObject.

I’d be great if Unity could handle that case independently, as it makes more sense to save the SerializedObject with the prefab. You’d need to do this by hand.
In principle, you can create an Asset for you SerializedObject and pack them into the prefab. In your Editor script use something like

MyBehaviour myBehaviour = target as MyBehaviour;
[...]
AssetDatabase.AddObjectToAsset(myBehaviour.data, PrefabUtility.GetPrefabObject(myBehaviour));

In your case, you should add every CutSceneNode from your List as a separate Asset. Nice thing, you can hide these sub-assets using myBehaviour.data.hideFlags = HideFlags.HideInHierarchy.

But how do you know when a prefab was created? That’s the tricky part. You might be able to check whether the object is a prefab using
if(PrefabUtility.GetPrefabObject(myBehaviour) != null) and then check if the assets are already created. I used the editor script to merely add a button “Create Prefab” that does the creation.

However, you’ll encounter a new issue: When instantiating from this prefab, the ScriptableObjects are never copied, because the references will stick to the assets in the prefab. You end up editing the prefab data, so when instantiating, you’ll also need to instantiate the ScriptableObjects by hand:

// Instantiation from prefab will lead to myBehaviour belonging to a scene and myBehaviour.data belonging to a prefab
if (myBehaviour.data != null && AssetDatabase.GetAssetOrScenePath(myBehaviour) != AssetDatabase.GetAssetOrScenePath(myBehaviour.data))
{
    myBehaviour.data = Instantiate(myBehaviour.data);
}

Even if you don’t use prefabs, if you copy your component to another GameObject in the same scene, the references are intact, but both the original and the copy will reference the same ScriptableObject. This was becoming to crazy to manage so I ditched this approach completely.

In the end, I used ISerializationCallbackReceiver to serialize my ScriptableObjects into a string using JSON.net and on deserialization, I recreate the ScriptableObjects from the string.
Maybe above still helps you!

5 Likes

@SanityIsOverrated That’s useful info, thanks.

Does this happen with scriptable objects that are created and referenced in scenes? I have a scriptable object asset file that lives in a folder in my project which retains its references correctly when used in a prefab.

Just want to be aware of when this problem happens specifically!

@flashframe Having ScriptableObjects as assets works.
If you have a prefab that is referencing a ScriptableObject in an asset, then both “live” in your project and references work fine.
Also, any scene object can refer to ScriptableObjects that are saved in the project.

But nothing in you project (EDIT: things in the project view) can reference scene objects. The problematic scenario is that ScriptableObjects created in memory, e.g., in an editor script are considered part of the scene. References are lost if the component is turned into a prefab.

My long post illustrates that in principle, one can convert the ScriptableObjects from scene to assets on demand, but it appears way to error-prone.

2 Likes

Thank you, that cleared it up in my head!

@SanityIsOverrated
Thank you for this amazing answer!
Very detailed and explanatory.

In the end I just didn’t use any cutscene as a prefab… It was a bit more difficult, but it worked OK.

I’ll try the way you did, with JSON.

Thanks for the answer and sorry for taking so long to respond