- Home /
Showing an array with enum as keys in the property inspector
I've got an object where components have individual HP, stored in an array; the array will be accessed via an enum set of the component names. This is a simplified version, there are other things like statuses (on fire, leaking) that each component might have.
public class Body : MonoBehaviour {
protected enum COMPONENTS {
Head,
Torso,
LeftArm,
RightArm,
LeftLeg,
RightLeg
}
protected static int COMPONENTS_COUNT = 6;
//maximum HPs for components - visible in editor
public int[] component_hp_max = new int[COMPONENTS_COUNT];
//updated current HP for components
protected int[] component_hp_current = new int[COMPONENTS_COUNT];
void Start () {
//for each component...
for (int i = 0; i < COMPONENTS_COUNT; i++){
//current hp is the max hp
component_hp_current[i] = component_hp_max[i];
}
}
}
I want to be able to iterate over the objects, so I think using a dictionary isn't ideal. I'm new to Unity and C#, so perhaps there's a more elegant way.
However, in the property window, the elements get listed like Element 0, Element 1:
Is it possible to use the COMPONENTS enum as the "keys" for the array in the property inspector, so they're listed as Head, Torso, etc.?
(Second question: is there a more elegant way of getting the enum's length than just storing the size in a separate int..?)
Answer by bonzairob · Jan 12, 2019 at 02:11 PM
I have made a custom PropertyDrawer to accomplish this.
Assets/EnumNamedArrayAttribute.cs
using UnityEngine;
public class EnumNamedArrayAttribute : PropertyAttribute
{
public string[] names;
public EnumNamedArrayAttribute(System.Type names_enum_type)
{
this.names = System.Enum.GetNames(names_enum_type);
}
}
Assets/Editor/DrawerEnumNamedArray.cs
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
[CustomPropertyDrawer(typeof(EnumNamedArrayAttribute))]
public class DrawerEnumNamedArray : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EnumNamedArrayAttribute enumNames = attribute as EnumNamedArrayAttribute;
//propertyPath returns something like component_hp_max.Array.data[4]
//so get the index from there
int index = System.Convert.ToInt32(property.propertyPath.Substring(property.propertyPath.IndexOf("[")).Replace("[", "").Replace("]", ""));
//change the label
label.text = enumNames.names[index];
//draw field
EditorGUI.PropertyField( position, property, label, true );
}
}
Usage:
[EnumNamedArray( typeof(COMPONENTS) )]
public int[] component_hp_max = new int[COMPONENTS_COUNT];
It's a shame about the typeof() but I couldn't get it to work without it.
Really simple and great solution, not sure why it doesnt have more upvotes. By the way, i tried this with a struct (with one single float atm) instead of an int and it kinda works out of the box, except theres some overdraw. Any idea how to fix it? Guess height needs to be adjusted for each row..
To prevent errors when adding too many items to the array change line 20:
if (index < enum_attr.names.Length)
{
label.text = enum_attr.names[index];
}
If we don't assign label, then they keep their default "Element 4" names.
Also: to support this attribute in a Serializable struct that's inside a list, change IndexOf
to LastIndexOf
.
Then you should be able to do something like:
[Serializable]
public class Health {
[EnumNamedArray( typeof(CO$$anonymous$$PONENTS) )]
public int[] component_hp_max = new int[CO$$anonymous$$PONENTS_COUNT];
}
[EnumNamedArray( typeof(CO$$anonymous$$PONENTS) )]
public Health[] component_hp_max = new Health[CO$$anonymous$$PONENTS_COUNT];
if you have a struct or a class as array elements, expanding them wont work because the required height of the elements wont change.
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}
adding this to DrawerEnumNamedArray.cs, fixed it for me.
Answer by dan_wipf · Jan 11, 2019 at 10:46 PM
Maybe a different approach of your Question could solve it, if it's a fixed length of array I'd make a new Struct and display this in the Inspector.
like this:
public class Body : MonoBehaviour {
[System.Serializable] public struct COMPONENTS{
public int Head;
public int Torso;
public int LeftArm;
public int RightArm;
public int LeftLeg;
public int RightLeg;
}
public COMPONENTS YourNewStruct;
}
you then call values from COMPONENTS like this:
int x = YourNewStruct.Head;
int y = YourNewStruct.LeftLeg;
YourNewStruct.Head = y;
//etc.
result:
The trouble is I want to add some other things per-component, like debuffs ("on fire"), or a set of has_died booleans. $$anonymous$$aybe I can work around that though...
This solution was way simplier in my opinion and achieved what I wanted to archieve
This was simple and exactly what I needed. For my use case I ended up with a Serializable struct which held several named data objects which were also Serializable structs. This gave me a tidy way to use the inspector define a big set of textures to switch around depending on the setting. I just switch out the data object and update the materials with the new textures.
// in my class
public Organizations organizations;
// below my class in the same file
[Serializable]
public struct Organizations
{
public TextureSet Org1;
public TextureSet Org2;
public TextureSet Org3;
}
[Serializable]
public struct TextureSet
{
public Texture someTexture;
public Texture otherTexture;
}
Your answer
