-
-
Save aarthificial/f2dbb58e4dbafd0a93713a380b9612af to your computer and use it in GitHub Desktop.
using System; | |
using UnityEngine; | |
[Serializable] | |
/// Requires Unity 2020.1+ | |
public struct Optional<T> | |
{ | |
[SerializeField] private bool enabled; | |
[SerializeField] private T value; | |
public bool Enabled => enabled; | |
public T Value => value; | |
public Optional(T initialValue) | |
{ | |
enabled = true; | |
value = initialValue; | |
} | |
} |
using UnityEditor; | |
using UnityEngine; | |
namespace Editor | |
{ | |
[CustomPropertyDrawer(typeof(Optional<>))] | |
public class OptionalPropertyDrawer : PropertyDrawer | |
{ | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
var valueProperty = property.FindPropertyRelative("value"); | |
return EditorGUI.GetPropertyHeight(valueProperty); | |
} | |
public override void OnGUI( | |
Rect position, | |
SerializedProperty property, | |
GUIContent label | |
) | |
{ | |
var valueProperty = property.FindPropertyRelative("value"); | |
var enabledProperty = property.FindPropertyRelative("enabled"); | |
EditorGUI.BeginProperty(position, label, property); | |
position.width -= 24; | |
EditorGUI.BeginDisabledGroup(!enabledProperty.boolValue); | |
EditorGUI.PropertyField(position, valueProperty, label, true); | |
EditorGUI.EndDisabledGroup(); | |
int indent = EditorGUI.indentLevel; | |
EditorGUI.indentLevel = 0; | |
position.x += position.width + 24; | |
position.width = position.height = EditorGUI.GetPropertyHeight(enabledProperty); | |
position.x -= position.width; | |
EditorGUI.PropertyField(position, enabledProperty, GUIContent.none); | |
EditorGUI.indentLevel = indent; | |
EditorGUI.EndProperty(); | |
} | |
} | |
} |
Nice script, but for everyone whose wondering why it isn't working for them, this works only from version of Unity 2020.1.0a18. Maybe you could added as comment.
That's nice, thanks you, but I'm really curious to know why it won't work bellow this version, if someone know and can reply, me and my curiosity will be grateful !
That's nice, thanks you, but I'm really curious to know why it won't work bellow this version, if someone know and can reply, me and my curiosity will be grateful !
Before 2020.1, serializing fields of generic types was not possible.
Before 2020.1, serializing fields of generic types was not possible.
Oww, that's why, thanks you very much !
very useful!
Very clever, and helpful. Thank you !
There a problem with your code. When you want an optional field A inside a custom field B and have a field B in your class then the drawer will calculate the width relative to B rather than to A. I'm not familiar with the property drawer so can someone fix it?
class B
{
public Optional<float> a;
}
class C : MonoBehaviour
{
public B b; // The enabled toggle of b.a will not display correctly.
}
There a problem with your code. When you want an optional field A inside a custom field B and have a field B in your class then the drawer will calculate the width relative to B rather than to A. I'm not familiar with the property drawer so can someone fix it?
Should work now
Thanks, man!
Is it possible to make this work with the Range property attribute?
Is it possible to make this work with the Range property attribute?
Unfortunately it's not possible currently, at least not while using the PropertyDrawer class.
Might be possible to use an extra [Optional] attribute and draw with attribute drawer.
I've added C# operators support so we don't have to write extra parts.
You can find the gist here or also on "Forks" tab of this gist.
// Assigning default values without writing extra new Optional<float>(100.95f);
part.
[SerializeField] Optional<float> optionalFloat = 100.95f;
[SerializeField] Optional<int> optionalInt = 50;
[SerializeField] Optional<Transform> optionalTransform = null;
// Add, subtract, multiply etc values without writing extra .Value
part
optionalFloat += 1.1f;
optionalTransform = null;
// Comparisons & Null checks
if (optionalTransform == null) { Debug.Log("optionalTransform is null"); }
if (optionalInt == optionalInt2) { Debug.Log("optionalInt and optionalInt2 have the same value."); }
// Check if it's enabled without needing to write .Enabled
if (optionalInt) { Debug.Log("optionalInt is enabled."); }
Can you provide code examples of how you use Optional?
Can you provide code examples? It's hard to help when you don't show anything to fix.
@MathiasYde the video by author of this gist contains a decent code example as well as the reason for creating the system: Optional Variables - Unity Tips [2020.1+] , hope that helps!
I was trying to help another person, but it seems he deleted his comments. I am fully aware of this situation, no further elaboration is necessary :)
Ahh, makes sense, only (re)discovering this entire project now and binging all the videos 😛
Hi, I did make a custom editor for this Optional which also support passing attributes down to the "T value".
Just want to share this to the community.
Oh, you also need to have Odin Inspector packet
OptionalAttributeProcessor.cs
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEngine;
namespace Maniac.Utils.Editor
{
public class OptionalAttributeProcessor<T> : OdinAttributeProcessor<Optional<T>>
{
private static readonly List<Type> KeepAttributeTypes = new List<Type>
{
typeof(SerializeField),
typeof(SerializableAttribute),
typeof(HeaderAttribute),
typeof(InfoBoxAttribute),
typeof(JsonIgnoreAttribute),
};
private List<Attribute> parentAttributesToPassDown = new List<Attribute>();
public override void ProcessSelfAttributes(InspectorProperty property, List<Attribute> attributes)
{
// Store attributes to pass down
parentAttributesToPassDown.Clear();
foreach (var attribute in attributes)
{
if (!KeepAttributeTypes.Contains(attribute.GetType()))
{
parentAttributesToPassDown.Add(attribute);
}
}
// Remove attributes that are not to be kept on the parent
attributes.RemoveAll(attr => !KeepAttributeTypes.Contains(attr.GetType()));
base.ProcessSelfAttributes(property, attributes);
}
public override void ProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member, List<Attribute> attributes)
{
// If the member is the 'value' field of the Optional<T> class, propagate selected attributes to it
if (member.Name == "value")
{
attributes.AddRange(parentAttributesToPassDown);
}
base.ProcessChildMemberAttributes(parentProperty,member, attributes);
}
}
}
OptionalDrawer.cs
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
using UnityEngine;
namespace Maniac.Utils.Editor
{
public class OptionalDrawer<T> : OdinValueDrawer<Optional<T>>
{
private PropertyTree propertyTree;
private readonly string enabledLabel = nameof(Optional<T>.Enabled).AddColor(Color.cyan);
private readonly string disabledLabel = nameof(Optional<T>.Enabled).AddColor(Color.black);
protected override void DrawPropertyLayout(GUIContent label)
{
var valueEntry = this.ValueEntry;
var optional = valueEntry.SmartValue;
EditorGUILayout.BeginVertical(GUI.skin.box);
{
EditorGUILayout.BeginHorizontal();
{
EditorGUILayout.LabelField(label.AddColor(Color.white), new GUIStyle
{
alignment = TextAnchor.MiddleLeft,
richText = true
});
GUILayout.FlexibleSpace();
var correctLabel = optional.Enabled ? enabledLabel : disabledLabel;
var userClicked = GUILayout.Button(correctLabel, new GUIStyle(GUI.skin.button)
{
richText = true
});
if (userClicked)
{
optional.Enabled = !optional.Enabled;
}
}
EditorGUILayout.EndHorizontal();
if (optional.Enabled)
{
var valueProperty = GetValueChildren(valueEntry.Property);
if (valueProperty == null)
{
SirenixEditorGUI.WarningMessageBox($"Cannot find value in {valueEntry.Property}. Please ensure that it's serializable!");
}
else
{
EditorGUILayout.BeginVertical(GUI.skin.box);
{
var childLabel = valueProperty.Label;
childLabel.text = string.Empty;
valueProperty.Draw(childLabel);
}
EditorGUILayout.EndVertical();
}
}
}
EditorGUILayout.EndVertical();
valueEntry.SmartValue = optional;
}
private InspectorProperty GetValueChildren(InspectorProperty parent)
{
foreach (var child in parent.Children)
{
if (child.Name == "value")
{
return child;
}
}
return null;
}
}
public static class Extension
{
private static readonly string colorFormat = "<color={1}>{0}</color>";
public static string AddColor(this object origin, string colorInHex)
{
return string.Format(colorFormat, origin.ToString(), colorInHex);
}
public static string AddColor(this object origin, Color color)
{
return string.Format(colorFormat, origin.ToString(), ToRGBHex(color));
}
public static string ToRGBHex(Color c)
{
return $"#{ToByte(c.r):X2}{ToByte(c.g):X2}{ToByte(c.b):X2}";
}
private static byte ToByte(float f)
{
f = Mathf.Clamp01(f);
return (byte)(f * 255);
}
}
}
Nice script, but for everyone whose wondering why it isn't working for them, this works only from version of Unity 2020.1.0a18. Maybe you could added as comment.