Is it possible for a Scriptable Object to have a field of a specific type, and have that type be determined based on the type of another field? For example:
public class ItemObject : ScriptableObject
{
public ItemType type;
public Item data = new Item();
}
ItemType is an enum
public enum ItemType
{
Physical,
Magical
}
Item is a basic class, with 2 other classes inheriting from it, MagicalItem and PhysicalItem.
[System.Serializable]
public class MagicalItem : Item {
public string passive;
public MagicalItem() {
passive = "";
}
}
ect.
I'd like ItemObject to be able to choose in the editor from either of these options, or many more as I add new item types to my game. I haven't been able to figure out a solution but my thought is to have the data type be set on Awake() based on the type, but I haven't been able to get that to work. IE something like this:
private void Awake() {
if(type == ItemType.Magical) {
data = new MagicalItem();
} else {
data = new PhysicalItem();
}
}
Is there a better way to solve this problem?
Thanks!
Had fun solving this one.
The trick to this that I found is actually a new thing, [SerializeReference], which does a few things, one of which lets you do polymorphic serialization, which this question requires.
The major steps to solving this:
[SerializeReference] to the data field.type field for changes, and if it changes, create a new instance with the above method and assign it to the data fieldAltogether, this gives you this kind of result:

[CreateAssetMenu(fileName = "Data", menuName = "Inventory/Item", order = 1)]
public class ItemObject : ScriptableObject
{
[CustomEditor(typeof(ItemObject))]
public class ItemObjectInspector : Editor
{
SerializedProperty m_type;
SerializedProperty m_data;
private void OnEnable()
{
m_type = serializedObject.FindProperty("type");
m_data = serializedObject.FindProperty("data");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_type);
if (EditorGUI.EndChangeCheck())
{
m_data.managedReferenceValue =
ItemObject.CreateBlankData((ItemType)m_type.intValue);
}
// avoid drawing type a second time
// avoid drawing annoying Script field
DrawPropertiesExcluding(serializedObject, new string[] { "type", "m_Script" });
serializedObject.ApplyModifiedProperties();
}
}
[System.Serializable]
public enum ItemType
{
Physical,
Magical
}
// Determines how to create an Item from each ItemType
public static Item CreateBlankData(ItemType type)
{
switch (type)
{
default:
case ItemType.Physical:
return new PhysicalItem();
case ItemType.Magical:
return new MagicalItem();
}
}
[System.Serializable]
public class Item
{
[SerializeField] public string typeDescription;
}
[System.Serializable]
public class MagicalItem : Item
{
[SerializeField] string elementName;
public MagicalItem() { typeDescription = "Magical"; elementName = "fire"; }
}
[System.Serializable]
public class PhysicalItem : Item
{
[SerializeField] float materialHardness;
public PhysicalItem() { typeDescription = "Physical"; materialHardness = 1f; }
}
public ItemType type;
[SerializeReference] public Item data;
}
Note: all the above classes are only inner classes for the sake of brevity. They can be separate classes or inner classes with no issues.
Just as an addition to Ruzihm's good answer.
For this kind of stuff you could actually skip the overhead of implementing a custom editor and simply use OnValidate
This function is called when the script is loaded or a value is changed in the Inspector (Called in the editor only).
So simply e.g.
private void OnValidate()
{
switch(type)
{
case ItemType.Magical:
// To avoid losing already filled in data
// simply check if the current data already has the correct type
if(data != null && data.GetType != typeof(MagicalItem))
{
data = new MagicalItem();
}
break;
case ItemType.Physical:
default:
if(data != null && data.GetType != typeof(PhysicalItem))
{
data = new PhysicalItem();
}
break;
}
}