PropertyDrawer, local members behave like static members

I’ve come across something that seems pretty odd to me, but I’m no expert at this.

I have a property (Object), for which I want to create a dropdown selector “on the fly”, to set one of its properties.

Everything works, except for members declared in the PropertyDrawer. They behave like they all share the same value, always.

This is my custom Object and the component using them :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace Test
{
	[Serializable]
	public class TestProperty : System.Object
	{
		public string testString = "None";
	}

	public class TestComponent : MonoBehaviour
	{
		public List<TestProperty> properties = new List<TestProperty>();
	}
}

And this is the PropertyDrawer :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

namespace Test
{
	[CustomPropertyDrawer(typeof(TestProperty))]
	public class TestPropertyDrawer : PropertyDrawer
	{
		SerializedProperty stringProp;

		void RefreshProperties ()
		{
			pNames = new string[5];
			for (int i = 0; i < 5; i++)
				pNames *= Random.Range(0, 100).ToString();*
  •  }*
    
  •  public string [] pNames = new string[3] {"Test 1", "Test 2", "Test 3"}; // this behaves like static (common amongst all properties)*
    
  •  private int _pSelection; // this behaves like static (common amongst all properties)*
    
  •  public int pSelection*
    
  •  {*
    
  •  	get {return _pSelection;}*
    
  •  	set*
    
  •  	{*
    
  •  		if (_pSelection != value)*
    
  •  		{*
    
  •  			_pSelection = value;*
    
  •  			stringProp.stringValue = pNames[value];*
    
  •  		}*
    
  •  	}*
    
  •  }*
    
  •  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)*
    
  •  {*
    
  •  	stringProp = property.FindPropertyRelative("testString");*
    

_ var cRect = new Rect (position.x, position.y, position.width0.20f, position.height);_
_ var pRect = new Rect (position.x+position.width
0.20f, position.y, position.width0.40f, position.height);_
_ var sRect = new Rect (position.x+position.width
0.60f, position.y, position.width*0.40f, position.height);_

  •  	EditorGUI.BeginProperty (position, label, property);*
    
  •  	if (GUI.Button(cRect, "refresh"))*
    
  •  		RefreshProperties();*
    
  •  	pSelection = EditorGUI.Popup (pRect, pSelection, pNames);*
    
  •  	EditorGUI.LabelField (sRect, stringProp.stringValue);*
    
  •  	EditorGUI.EndProperty();*
    
  •  }*
    
  • }*
    }
    As a result, the list, and the selection appears to always be the same, although the mechanic works.
    [111226-screen-shot-2018-02-13-at-144353.png|111226]*

That said, it is worth acknowledging that you sometimes need per-property view state data (i.e. data that you don’t actually want to store in your model). In these cases, the typical approach that we use internally is to key such data with the propertyPath for the property being drawn. Something like this:

class MyDrawer : PropertyDrawer {
    class ViewData {
        public int someInteger;
        public string someString = string.Empty;
    }
    
    Dictionary<string, ViewData> m_PerPropertyViewData = new Dictionary<string, ViewData>();

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        ViewData viewData;
        if (!m_PerPropertyViewData.TryGetValue(property.propertyPath, out viewData) {
            viewData = new ViewData();
            m_PerPropertyViewData[property.propertyPath] = viewData;
        }
        // do stuff with viewData as needed
    }
}

A property drawer should never be used to store any state that should be preserved for each instance. Unity will of course reuse the property drawer instance for all properties of the same type. It would be horrible inefficient to create a new property drawer instance for each element. The OnGUI method receives all parameters necessary to draw the property.

If you want to store information for each property seperately you would need to add that information to your “TestProperty” class