- Home /
How do PropertyAttribute instances work in an array of structs?
So I've been wrestling with this problem pretty much all day to no avail. I'm trying to make a spritename PropertyDrawer for NGUI sprites so I don't have to write them in by hand and it's almost working fully now except in this one instance.
The PropertyDrawer and PropertyAttribute are as follows:
public class SpriteNameAttribute : PropertyAttribute {
public UIAtlas atlas;
}
and
[CustomPropertyDrawer(typeof(SpriteNameAttribute))]
public class SpriteNamePropertyDrawer : PropertyDrawer {
private SpriteNameAttribute _attributeValue = null;
private SpriteNameAttribute attributeValue {
get {
if (_attributeValue == null) {
_attributeValue = (SpriteNameAttribute) attribute;
}
return _attributeValue;
}
}
public override float GetPropertyHeight (SerializedProperty property, GUIContent label)
{
// Double the default height if we're showing the error box. Annoyingly can't figure out how to calculate an exact height that accounts for word wrapping in the HelpBox
if(property.propertyType != SerializedPropertyType.String) {
return 32;
} else {
return base.GetPropertyHeight (property, label);
}
}
public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label) {
// Show the prefix label
label = EditorGUI.BeginProperty(position, label, prop);
Rect contentPosition = EditorGUI.PrefixLabel(position, label);
// Leave if we're not dealing with a string
if(prop.propertyType != SerializedPropertyType.String) {
EditorGUI.HelpBox(contentPosition, "[SpriteNameAttribute] may only be used on string values", MessageType.Error);
return;
}
contentPosition.width = (contentPosition.width - 10) * 0.5f; // Divide the content width in half for the two buttons minus a 10px spacer
EditorGUI.indentLevel = 0;
// Show atlas selector button. Name it according to whatever's currently selected
if (GUI.Button(contentPosition, (attributeValue.atlas != null) ? attributeValue.atlas.name : "<select atlas>")) {
// Show the componenet selector. Use a Lambda expression for the callback that calls SetDirty so the inspector is redrawn as soon as an atlas is selected.
ComponentSelector.Show<UIAtlas>(x => {
attributeValue.atlas = (UIAtlas)x;
EditorUtility.SetDirty(prop.serializedObject.targetObject);
});
}
// Shift the content position forward by the button width + 10px spacer
contentPosition.x += contentPosition.width + 10;
// Only show this button if an atlas is selected or there's already a sprite name set
if(attributeValue.atlas != null || !prop.stringValue.Equals("")) {
if(GUI.Button(contentPosition, (!prop.stringValue.Equals("")) ? prop.stringValue : "<select sprite>")) {
// Set NGUI's static variables so the proper sprite is selected in the SpriteSelector when it's shown
NGUISettings.atlas = attributeValue.atlas;
NGUISettings.selectedSprite = prop.stringValue;
// Have to use a Lambda expression here since the property value is only visible in this method. Saving it to a member variable and changing that in a callback function DOESN'T WORK.
SpriteSelector.Show(x => {
prop.stringValue = x;
prop.serializedObject.ApplyModifiedProperties();
});
}
}
EditorGUI.EndProperty();
}
}
That works perfectly fine for simple public string members and arrays of strings, however I'm now trying this:
public Breakable[] breakables;
[System.Serializable]
public struct Breakable {
[SpriteNameAttribute]
public string wholeSprite;
[SpriteNameAttribute]
public string topSprite;
[SpriteNameAttribute]
public string bottomSprite;
}
And I'm seeing something bizzarre. Everything draws fine, but when I select an atlas for one of the members ALL instances of that member in the array get set to that value.
E.G. Selecting Game08ReferenceAtlas for the wholeSprite in the first Breakable object sets every wholeSprite's atlas in the entire array to Game08ReferenceAtlas like the picture below.
So my question is this: How are the PropertyAttribute objects instantiated for an array like this? You would think that each property to be drawn would create its own instance of the SpriteNameAttribute to reference the atlas from, but it appears that every instance of Breakable in the array is sharing the same three instances of SpriteNameAttribute for the three members.
Am I missing something simple here or is it just a case of trying to use a PropertyDrawer for more than it was meant to do? It's not an essential fix since I usually want to use all the same atlas anyway but it's driving me crazy trying to figure out why this is happening and would be useful if I want to mix sprites from my game and ui atlases.
Answer by burtonposey · Sep 24, 2016 at 12:18 AM
I know this is way late, but I just fixed this bug in my own code.
I don't understand why this is, but I believe the problem is that every element in your array is using the same private variables in your property drawer. I based my code off of some I inherited and when I made an array of the objects that utilized the property drawer, they all started getting the same values.
The solution is to find a way to manage the state without storing anything inside of the PropertyDrawer class. Once you stop doing that, you should find that you no longer are seeing a replication of values for all members in your collection.
Your answer
Follow this Question
Related Questions
SerializedProperty isn't being detected as an array? 1 Answer
How to prevent overlap when using nested arrays in CustomPropertyDrawers? 1 Answer
EditorGUI.IntField always 0 after GameStart 0 Answers
Custom Editor/Custom Property Drawer combo, breaks Enable button 1 Answer
Custom PropertyDrawer for attributes on array members 2 Answers