Ciao !!
Not entirely sure that what I’m trying to achieve is actually possible.
Are you ready? Let’s start the madness !!!
I have a ScriptableObject that can execute a list of behaviours.
This behaviours are also ScriptableObjects.
Each behaviour type needs parameters to work properly.
Normally, each behaviour type would have serialized fields that can be changed in each instance BUT I don’t want this: each behaviour should just implement an algorithm and I want to inject the necessary data from the ScriptableObject used as “container”.
The critical part is that the container shouldn’t care about the final type of the behaviour: the proper parameters should be displayed and assigned using a custom drawer for a tuple class that defines a behaviour coupled to its data.
OK, the last sentence sounds really complicated… let me create some example classes.
Container class: ScriptableObject that triggers the behaviours.
using UnityEngine;
public class Container : ScriptableObject
{
[SerializeField]
private Tuple[] _behaviours = null;
private void OnEnable()
{
for (int i = 0; i < _behaviours.Length; i++)
{
_behaviours[i].Behaviour.DoStuff(_behaviours[i].BehaviourData);
}
}
}
Behaviour class: common abstract base class for behaviours.
using System;
using UnityEngine;
[Serializable]
public abstract class Behaviour : ScriptableObject
{
[Serializable]
public abstract class Data { }
public abstract void DoStuff(Data d);
}
Tuple class: defines a tuple with a Behaviour and its necessary Data.
using System;
using UnityEngine;
[Serializable]
public class Tuple
{
[SerializeField]
private Behaviour _behaviour = null;
[SerializeField]
private Behaviour.Data _data = null;
public Behaviour Behaviour { get { return _behaviour; } }
public Behaviour.Data BehaviourData { get { return _data; } }
}
Concrete Behaviour classes: the following defines a set of instruction to perform.
using UnityEngine;
public class BahaviourString : Behaviour
{
public class DataInternal : Data
{
public string message = null;
}
public override void DoStuff(Data d)
{
Debug.Log((d as DataInternal).message);
}
}
using UnityEngine;
public class BahaviourInt : Behaviour
{
public class DataInternal : Data
{
public int number = 0;
}
public override void DoStuff(Data d)
{
Debug.Log((d as DataInternal).number);
}
}
using UnityEngine;
public class BahaviourComplex : Behaviour
{
public class DataInternal : Data
{
public string message = null;
public int number = 0;
}
public override void DoStuff(Data d)
{
DataInternal ciao = d as DataInternal;
Debug.Log(ciao.message + " | " + ciao.number);
}
}
I don’t know if it’s clear what I want to achieve: a class that can run a series of actions that are composite from the inspector using ScriptableObjects.
What I’m trying to do and I’m not able to, is to create a custom drawer for the Tuple class that does something like:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(Tuple), true)]
public class MyCustomDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
{
SerializedProperty behaviourProperty = property.FindPropertyRelative("_behaviour");
var behaviour = behaviourProperty.objectReferenceValue;
SerializedProperty dataProperty = property.FindPropertyRelative("_data");
EditorGUI.PropertyField(position, behaviourProperty, true);
if (behaviour == null)
{
EditorGUI.LabelField(position, "Please, add a Behaviour to access its configuration.");
}
else
{
// TODO - make dataProperty to point to an object of type behaviour.GetType().GetNestedType("DataInternal")
// OR - get/create a serialized property that point to an object of type behaviour.GetType().GetNestedType("DataInternal")
// OR - do something so that
EditorGUI.PropertyField(position, dataProperty, true);
// - will show in the inspector the fields needed for whatever the assigned Behaviour is.
// TODO - make sure the Data is properly serialized and saved as Data field of the related Tuple.
}
}
EditorGUI.EndProperty();
}
}
I am out of my mind, am I not?
Maybe there can be a different and less complicated way to do it, but this is the best I was able to come out with.
Please remember: I want the Data needed for each behaviour to be saved in the Container via the Tuple defined there because I don’t want to create 50 different, for example, BehaviourString ScriptableObject instances to print 50 different strings. The behaviour should just be treated as a set of instruction to apply to the passed data. Does it make sense?
Please note: the part that I cannot do is described by the comments of the custom drawer class.
Please note: the shown code will compile and would potentially run but I did cut all the formatting part, therefore, the custom drawer may not show the tuple object correctly.
Many many thanks to all the people that will contribute to sort out this mess !!