- Home /
"Unbroken Reference" problem when using a custom Editor to Save/Load a ScriptableObject Asset
Hi,
I'm relatively new to Unity and C#, and very new to ScriptableObjects and custom Editors, so please forgive me if my description of the problem is unnecessarily lengthy or confusing.
I've used relevant code from my project and made a small demo project, that tries to highlight only the problem I'm facing, and I've included the scripts below.
Classes:
MyScriptableObject (MyScriptableObject.cs) is a Class derived from ScriptableObject. It has two string fields, and a field of type OtherScriptableObject.
OtherScriptableObject (OtherScriptableObject.cs) is a Class derived from ScriptableObject. It has two string fields.
ObjectContainer (ObjectContainer.cs) is a plain Class. It has a string field, and a List of MyScriptableObjects.
ObjectContainerList (ObjectContainerList.cs) is a Class derived from SO. It has a List of ObjectContainers.
ListEditor (ListEditor.cs) is a Class derived from EditorWindow, that allows me to create/edit an ObjectContainerList, and Save/Load it as a ScriptableObject Asset file.
Description:
The ListEditor works as expected while starting from scratch:
I'm able to create empty ObjectContainer 'windows', in which I can Add/Remove rows of MyScriptableObject fields, and assign SO files to those fields.
Saving works fine, the ListEditor lets me save a list of these 'containers' as a ScriptableObject Asset file (of type ObjectContainerList) while keeping all references to the MyScriptableObjects (and also keeps references to the OtherScriptableObject within them).
Loading the Asset file back into the ListEditor also works, it shows the containers and their Lists exactly as they were saved.
Problem:
The problem arises after loading, where any change I now make to containers in the ListEditor is immediately reflected in the Asset file, even when the code responsible for saving the Asset file has not been executed.
For example, when I add a new row to one of the containers, immediately in the Asset file a new element gets added within that container's List.
My best guess is that there is somehow an "unbroken reference" that gets created (between the ObjectContainerList Asset file, and the List of ObjectContainers in the ListEditor) while initially saving (or loading) the Asset file.
Is there a way to prevent this?
I had some luck with a third-party 'ObjectCopier' script earlier, but that was before I needed nested ScriptableObjects.
Also, since I'm new to ScriptableObjects and the concept of Serialization in general, could the problem simply be a result of my using the wrong modifiers ([SerializeField] and [System.Serializeable]) for Classes and their fields.
If I could provide extra information or screenshots to describe the problem better, please let me know.
I apologize in advance if this question has already been asked and answered. Thanks.
Scripts :
MyScriptableObject.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[CreateAssetMenu(menuName = "MyScriptableObject")]
[System.Serializable]
public class MyScriptableObject : ScriptableObject {
[SerializeField] public string myScriptableObjectName;
[SerializeField] public string myData;
[SerializeField] public OtherScriptableObject otherScriptableObject;
}
OtherScriptableObject.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "OtherScriptableObject")]
public class OtherScriptableObject : ScriptableObject {
[SerializeField] public string otherSoName;
[SerializeField] public string otherData;
}
ObjectContainer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
[System.Serializable]
public class ObjectContainer{
public string containerId;
public List<MyScriptableObject> myScriptableObjectList;
[HideInInspector] public Rect containerRect;
float defaultHeight = 50f;
public ObjectContainer(float posX)
{
containerId = Guid.NewGuid().ToString();
containerRect = new Rect(posX, 20f, 200f, 100f);
myScriptableObjectList = new List<MyScriptableObject>();
}
public void DrawContainer()
{
float extraHeight = 0f;
extraHeight += (myScriptableObjectList.Count * 20f);
containerRect.height = defaultHeight + extraHeight;
GUILayout.BeginVertical();
if(GUILayout.Button("+",GUILayout.Width(190f))){AddObject();}
for(int i = 0; i < myScriptableObjectList.Count; i++)
{
GUILayout.BeginHorizontal();
if(GUILayout.Button("X")){ DeleteObject(i); break; }
myScriptableObjectList[i] = (MyScriptableObject) EditorGUILayout.ObjectField(myScriptableObjectList[i],typeof(MyScriptableObject),false);
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
}
void AddObject()
{
myScriptableObjectList.Add(null);
}
void DeleteObject(int index)
{
myScriptableObjectList.RemoveAt(index);
}
}
ObjectContainerList.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectContainerList : ScriptableObject{
[SerializeField] public List<ObjectContainer> containerList;
}
ListEditor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class ListEditor : EditorWindow {
private static List<ObjectContainer> containerList = new List<ObjectContainer>();
string fileName = "ContainerListFile";
string filePath = "Assets/Resources";
[MenuItem("Window/Container List Editor")]
static void ShowEditor()
{
ListEditor editor = EditorWindow.GetWindow<ListEditor>(false,"Container List Editor");
editor.wantsMouseMove = true;
editor.Show();
EditorWindow.FocusWindowIfItsOpen<ListEditor>();
}
void OnGUI()
{
DrawContainers();
DrawMenu();
if (GUI.changed) Repaint();
}
private void DrawMenu()
{
Rect menuBar = new Rect(0, 0, position.width, 20f);
GUILayout.BeginArea(menuBar, EditorStyles.toolbar);
GUILayout.BeginHorizontal();
if (GUILayout.Button(new GUIContent("Save"), EditorStyles.toolbarButton)) Save();
if (GUILayout.Button(new GUIContent("Load"), EditorStyles.toolbarButton)) Load();
if (GUILayout.Button(new GUIContent("Add Container"), EditorStyles.toolbarButton)) AddContainer();
if (GUILayout.Button(new GUIContent("Delete Last Container"), EditorStyles.toolbarButton)) DeleteContainer();
GUILayout.EndHorizontal();
GUILayout.EndArea();
}
void DrawContainers()
{
BeginWindows();
for(int i = 0; i < containerList.Count; i++)
{
containerList[i].containerRect = GUI.Window(i, containerList[i].containerRect, Draw, "container "+i);
}
EndWindows();
}
void Draw(int index)
{
containerList[index].DrawContainer();
}
void AddContainer()
{
ObjectContainer newContainer = new ObjectContainer(containerList.Count * 200f);
containerList.Add(newContainer);
}
void DeleteContainer()
{
if(containerList.Count!=0)containerList.RemoveAt(containerList.Count-1);
}
// the problem is probably an "unbroken reference" between 'containerList' (list that represents containers in the Editor),
// and 'objectContainerListSO.containerList' (list of containers within the SO Asset)
private void Save()
{
ObjectContainerList objectContainerListSO = ScriptableObject.CreateInstance<ObjectContainerList>();
objectContainerListSO.containerList = new List<ObjectContainer>();
foreach (var container in containerList)
{
objectContainerListSO.containerList.Add(container); //...this is probably where the unbroken-reference gets created
}
AssetDatabase.CreateAsset(objectContainerListSO, filePath + "/" + fileName + ".asset");
AssetDatabase.SaveAssets();
}
private void Load()
{
ObjectContainerList objectContainerListSO = ScriptableObject.CreateInstance<ObjectContainerList>();
objectContainerListSO = AssetDatabase.LoadAssetAtPath(filePath + "/" + fileName + ".asset", typeof(ObjectContainerList)) as ObjectContainerList;
if(objectContainerListSO == null)Debug.LogError("File Not Found!");
else
{
containerList = new List<ObjectContainer>();
foreach (var container in objectContainerListSO.containerList)
{
containerList.Add(container);//...or maybe here?
}
}
}
}
Your answer
Follow this Question
Related Questions
Serializing a ScriptableObject without creating an asset for it? 2 Answers
ScriptableObject with Custom Editor resetting data in inspector 1 Answer
Storing and Retrieving Data from a ScriptableObject 1 Answer
CustomEditor for an ScriptableObject asset only works after recompile. 1 Answer
[Solved]How to serialize Dictionary with Unity Serialization System 6 Answers