Inspector popup list for custom enum-like class
Hi all.
In Java, enums can have fields. This is really useful! Sorry for mentioning Java by the way, I threw up a little just thinking about it. But this feature is missing from C#, sadly, and I could really use it right about now. So instead, I've created an enum-like class, with static readonly fields for each of the "enum items" and a private constructor - y'know, standard stuff. This works fine, except that I would like to be able to use this in the inspector, with all the options showing as a drop-down list, just like enums.
In fact, I have accomplished this after only about an hour, using EditorGUILayout.Popup, and a dynamically-populated string array which is created in the enum-like class after all the constants are defined. I've created a PropertyDrawer for this, and it does actually even read a field from a GameObject and set its value (or, so it seems).
Problem is, when entering play-mode, the value reverts back to its default.
I have seen many posts about values reverting to defaults upon play, regarding custom inspectors and PropertyDrawers, but none of the answers solve the issue. I suspect this has something to do with the "enum" constants being static, but don't quote me on that! (Inspector fields don't seem to like static stuff).
Okay, time for some code: Blocks (the enum-like class):
[Serializable]
public class Blocks
{
#region Registry
private static readonly Dictionary<ushort, Blocks> BlockRegistry = new Dictionary<ushort, Blocks>();
private static ushort LastID = 0;
#endregion
public static readonly Blocks Air = new Blocks(false, false, 0, 0, 0, 0, 0, 0);
public static readonly Blocks Space = new Blocks(false, false, 0, 0, 0, 0, 0, 0);
#region Pseudo-Enum
public static readonly string[] EnumOptions;
static Blocks()
{
List<string> options = new List<string>();
foreach (KeyValuePair<ushort, Blocks> kvp in BlockRegistry)
{
options.Add(kvp.Value.DisplayName);
}
EnumOptions = options.ToArray();
}
#endregion
public readonly ushort ID;
public readonly string DisplayName;
public readonly bool IsRendered;
public readonly bool IsCollidable;
internal readonly int Texture1;
internal readonly int Texture2;
internal readonly int Texture3;
internal readonly int Texture4;
internal readonly int Texture5;
internal readonly int Texture6;
public Blocks(bool isRendered, bool isCollidable, int tex1, int tex2, int tex3, int tex4, int tex5, int tex6, [CallerMemberName] string displayName = null)
{
ID = LastID++;
DisplayName = displayName;
IsRendered = isRendered;
IsCollidable = isCollidable;
Texture1 = tex1;
Texture2 = tex2;
Texture3 = tex3;
Texture4 = tex4;
Texture5 = tex5;
Texture6 = tex6;
BlockRegistry.Add(ID, this);
}
public static implicit operator ushort(Blocks block)
{
return block.ID;
}
public static implicit operator Blocks(ushort block)
{
return BlockRegistry[block];
}
public static Blocks FromID(int block)
{
return BlockRegistry[(ushort)block];
}
}
and the PropertyDrawer class:
[CustomPropertyDrawer(typeof(Blocks))]
public class BlocksDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
VisualElement container = new VisualElement();
PropertyField field = new PropertyField(property);
container.Add(field);
return container;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
property.serializedObject.Update();
Blocks selected = property.GetValue<Blocks>();
int select = EditorGUI.Popup(position, label.text, (int)selected.ID, Blocks.EnumOptions);
if (select != selected)
{
if (!property.SetValue(Blocks.FromID(select)))
{
Debug.LogError("Failed to set block property!\n");
}
property.serializedObject.ApplyModifiedProperties();
Debug.Log($"Property updated to {property.GetValue<Blocks>().DisplayName}\n");
}
}
}
I am also using this code: this code
As you can see from this screen-cap, it's working as intended in the inspector when not in play mode: https://i.gyazo.com/dde247564ef1d82dfd794e0755df6af0.mp4 , but as soon as I click 'play' the value reverts to default and a test printout in the script's Start() function proves this.
Is this salvagable? Or should I just give up and go back to using an actual enum and an extension class for all of the Block properties? Bear in mind that as I add more properties (which I will), the extension class becomes an absolute nightmare, a complete mess, and I would prefer to have properties instead of functions for storing and returning values - since that's what properties are for - and the time C# gets the long-awaited extension overhaul and Unity catches up with the latest C# version, is a long LONG way away.
Thanks in advance -Logiq
I hope this didn't post multiple times. First the reCAPTCHA (which was a CHECKBOX) told me the characters I'd entered didn't match the image (?!?!), and secondly it was giving me a "bad gateway" error after trying to post for about a $$anonymous$$ute. So, if it multi-posted, sorry, but not my fault - technical issues!
Your answer
Follow this Question
Related Questions
How do you make a script to close out this pop up menu? 1 Answer
How the heck do I save this info for my characters? [Serialization Usage Question] 1 Answer
[Solved] Custom editor resets after Play? 3 Answers
public bool not showing in inspector 1 Answer
Recursive serialization not supported, you can't dereference a PPtr while loading. 1 Answer