- Home /
Serializable settings for custom PropertyDrawer
Setup
I have constructed a custom PropertyDrawer for a custom PropertyAttribute to create something similar to the RangeAttribute
, but for all data types. My PropertyDrawer requires an int to draw the inspector UI because 'the int' dictates the UI. In most other PropertyDrawers you can assume the data type under the attribute (i.e. float, Vector2, etc.) and have the PropertyDrawer read and write to that data type.
Question
However, I can't assume the data type because I'd like this attribute to be available for all data types. Therefore, I can't store 'the int' in the method mentioned above. I need a way to serialize 'the int' so that it persists through assembly reload and Unity closing. Got any ideas?
Attempts
Creating a serializable ScriptableObject class to hold 'the int' and reference that in either my custom PropertyDrawer or PropertyAttribute.
Throw around [SerializeField] and [Serializable] attributes on 'the int' and PropertyDrawer and PropertyAttribute classes
Try to learn more about serialization, but PropertyDrawers or PropertyAttributes are never mentioned.
Example
public class MyBehavior : MonoBehaviour { [My] public Vector2 data1; [My(2)] public SomeSerializedClass data2; } [AttributeUsage(AttributeTargets.Field)] public class MyAttribute : PropertyAttribute { public int value; // what needs to be serialized public MyAttribute(int startingValue = 1) { value = startingValue; } } [CustomPropertyDrawer(typeof(MyAttribute))] public class MyDrawer : PropertyDrawer { private int value; // or maybe this can be serialized instead? public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var attr = attribute as MyAttribute; value = attr.value; // Here I need `value` to be persistent upon Unity open/close // and script reloading, hence the issue. value = EditorGUI.IntSlider(position, value, 1, 2); // ... draw `property` based on `value` ... } }
value
must be serialized within MyAttribute
or MyDrawer
due to value
's independence on property
's underlying type.
Please let me know what I need to clarify. Thanks for any help! :)
You have to add System.Serializable on top of the class scriptableObject name too. And you have to set Custom editor for ScriptableObject to dirty too. In newer Unity version, you have to use Undo property for each serialize variables when you set it.
But most people use Odin Inspector to make custom Property or Drawer now because it save lots of time coding custom Editor. $$anonymous$$aybe you want to try to learn a thing or 2.
Thanks for the quick response. I have tried making the ScriptableObject serializable to no avail. What do you mean when you say "you have to set Custom editor for ScriptableObject to dirty too"? I'm only using the ScriptableObject to hold data, I don't see the need for a custom editor.
Answer by Adam-Mechtley · May 22, 2018 at 08:12 AM
I'm not 100% clear what you're trying to do, but depending what meets your needs, I would recommend one of the following (in order of decreasing recommendation):
Use property.propertyType to find out what type of field you're drawing and respond accordingly
Use PropertyDrawer.fieldInfo to find out exactly what field you're drawing and respond accordingly
Use PropertyDrawer.attribute to find out metadata for the field you're drawing and respond accordingly
To clarify case 3, it would look something like this:
public enum MyCustomMetadata { Something, SomethingElse, AndSoOn }
public class MyCustomAttribute : PropertyAttribute {
public readonly MyCustomMetadata CustomMetadata;
public MyCustomAttribute(MyCustomMetadata customMetadata) {
CustomMetadata = customMetadata;
}
}
public class MyBehavior : MonoBehaviour {
[SerializeField, MyCustomAttribute(MyCustomMetadata.Something)]
private int m_MyIntField;
}
[CustomPropertyDrawer(typeof(MyCustomAttribute))]
public class MyCustomDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
MyCustomMetadata customMetadata = (attribute as MyCustomAttribute).CustomMetadata;
// do something based on value of customMetadata
}
}
I added some example code to give a better idea of the problem.
I can't pursue solution 1 or 2 because the attribute is for any and all fields including other custom serializable classes.
Solution 3 only provides a constant to reference (I would need a variable), but gave me an idea to pass a variable into the attribute by reference, but that's also impossible :(.
Hope my example can lead to some other ideas. Thanks a ton!
Thanks for adding more context! I think the best (bad) approach here will depend on the behavior you ultimately want.
If the serialized data is specific only to the field type (i.e. all decorated
SomeSerializedClass
fields will share the same setting), then probably use EditorPrefs.If the serialized data is specific to each target object instance (i.e. each SomeSerializedClass on each $$anonymous$$yBehavior might have a different setting) then you probably want to save the serialized data to another field on the target object.
In the second case, you basically need to supply some string to your attribute that lets you find the serialized data of interest. For example:
class $$anonymous$$yAttribute : PropertyAttribute {
public readonly string settingPath;
public $$anonymous$$yAttribute(string settingPath) {
this.settingPath = settingPath;
}
}
class $$anonymous$$yBehavior : $$anonymous$$onoBehaviour {
[SerializeField, $$anonymous$$y("m_Vector2Setting")]
private Vector2 m_Vector2;
[SerializeField, HideInInspector]
private int m_Vector2Setting;
}
class $$anonymous$$yDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
var settingPath = (attribute as $$anonymous$$yAttribute).settingPath;
// this is naive and assumes the field being drawn isn't nested in some struct/class/array path
// a more complete approach would walk one level up property.propertyPath if needed and use property.FindPropertyRelative
var setting = property.serializedObject.FindProperty(settingPath);
}
}
Omg! Passing a string through the attribute constructor is a great idea! Totally forgot about Unity's overuse of strings and the FindProperty method. I just tested it and it works great :D. Thanks!
To anyone else: make sure the property you're referencing in the string is public facepalm