- Home /
SerializedObject.FindProperty returning null
Firstly I am missing my inheritFrom field when using the normal Inspector (not anymore when I inherit from UnityEngine.Object, then there's just 1 field):
[DataContract]
[System.Serializable]
public class BotPersonality : UnityEngine.Object
{
[DataMember]
[JsonProperty]
[SerializeField]
private BotPersonality _inheritFrom;
[DataMember]
[JsonProperty]
[SerializeField]
private string[] _possibleNames = {"test1", "test2"};
I want to write a custom inspector for inheriting / copying fields from another object with _inheritFrom
.
However I am getting NullReferenceExceptions
( SerializedObject.FindProperty
returns null
) for all my fields.
it says it can't find the skill
property, but it is clearly defined and it works in the first image.:
[DataMember]
[SerializeField]
public int skill = 1;
The skill
variable is serializable.
Below is my PropertyDrawer class
[CustomPropertyDrawer(typeof(BotPersonality))]
public class BotPersonalityDrawer : PropertyDrawer
{
private readonly Type _type = typeof(BotPersonality);
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
BotPersonality botPersonality =
Utils.GetActualObjectForSerializedProperty<BotPersonality>(this.fieldInfo, property);
this.ShowPersonality(botPersonality, property);
}
private void ShowPersonality(BotPersonality bot, SerializedProperty property)
{
int prevIndentLevel = EditorGUI.indentLevel;
foreach (FieldInfo fieldType in this._type.GetFields()
.Where(x => x.IsDefined(typeof(DataMemberAttribute), false)))
{
...
var serializedProperty = property.serializedObject.FindProperty(fieldType.Name);
EditorGUILayout.PropertyField(serializedProperty);
}
}
}
The code for GetActualObjectForSerializedProperty can be found here: http://sketchyventures.com/2015/08/07/unity-tip-getting-the-actual-object-from-a-custom-property-drawer/ (I changed it so it works with lists too ), but this an object that is not null.
Help would be much appreciated. I can't see what I'm doing wrong.
EDIT: To clarify, I'm not looking for help with the inheritance / copy an object.
EDIT2: I've created it as small as I can and it still gives the same NullRef Exception:
[DataContract]
[System.Serializable]
public class BotPersonalitySmall : MonoBehaviour
{
[DataMember]
[SerializeField]
public int skill = 1;
[DataMember]
[SerializeField]
private BotPersonalitySmall _inheritFrom;
[DataMember]
[Overridable]
public BotPersonality.WaypointSteeringInfo waypointSteering;
}
The class I'm trying to serialize
The PropertyDrawer:
[CustomPropertyDrawer(typeof(BotPersonalitySmall))]
public class BotPersonalitySmallDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Using BeginProperty / EndProperty on the parent property means that
// prefab override logic works on the entire property.
EditorGUI.BeginProperty(position, label, property);
var serializedProperty = property.serializedObject.FindProperty("skill");
EditorGUILayout.PropertyField(serializedProperty);
EditorGUI.EndProperty();
}
}
It still can't find the property skill
. I've changed the inheritance on BotPersonality
to UnityEngine.Object
or keep it empty, but that didn't help.
Answer by Bunny83 · Nov 27, 2017 at 06:57 PM
This is quite confusing. You showed random bits of your code and your inspector. What does "BotPersonality" look like? Is it a serializable class / struct? What Tyoe does "this._type" refer to?
This line looks suspicious:
var serializedProperty = property.serializedObject.FindProperty(fieldType.Name);
You may have the wrong idea in mind what a SerializedObject is. SerializedObjects are only those objects which are derived from UnityEngine.Object. Custom serializable classes are not objects from the serialization systems point of view. Custom serializable classes get simply serialized "inline" as sub fields.
The SerializedProperty class has a method called FindPropertyRelative which can search for a sub property. It's possible to use FindProperty on the serializedObject when you use a correct property path. In your case it would be something like "_inheritFrom.skill".
Also keep in mind that you can not use polymorphism / inheritance for custom serializable classes. You may want to have a look at the script serialization documentation
I made some edits to answer some of your questions, but I'll answer them here too: BotPersonality
is a serializable
class.
this._type = typeof(BotPersonality)
I changed the class BotPersonality
to inherit from UnityEngine.Object
, but that didn't resolve the issue. It still can't find the fields.
I'm not looking for help _inheritFrom.skill,
just the normal object.`skill`. That'll be a problem for another day. So it would just be FindProperty("skill")
, which is what happens with the looping over the fields.
I thinks you still don't get my point. SerializedObject can only be used on actual serialized objects that Unity can serializes as standalone assets. Also you are not supposed to derive any class directly from UnityEngine.Object. The only classes you can use as base classes are $$anonymous$$onoBehaviour and ScriptableObject. ScriptableObjects need to be created with CreateInstance and must not be created with "new".
ScriptableObjects as well as $$anonymous$$onoBehaviours instances are always serialized on their own. The helper function (Utils.GetActualObjectForSerializedProperty) is not needed for classes that are implicitly derived from UnityEngine.Object. At access a referenced object (that is derived from $$anonymous$$onoBehaviour or ScriptableObject) you would simply use SerializedProperty.objectReferenceValue.
btw the article you've linked only works for custom serialized classes which are not derived from UnityEngine.Object (so not for $$anonymous$$onoBehaviours or ScriptableObjects). It also makes many assumptions that you only have 1 layer or nesting. To actually support any serialized property you would need something like it was posted over here-
However again this does not apply to properties which are actual serialized references (any reference to a UnityEngine.Object derived type).
About your simplified case: This should work just fine. However you should keep in $$anonymous$$d that you are creating a propertydrawer here. So it would be shown for the field "_inheritFrom" only. But ins$$anonymous$$d of showing anything from the referenced object you show the skill field of the containing class. This doesn't seem to make much sense. $$anonymous$$aybe you wanted to create a CustomEditor / Editor for your class? It's really hard for us to actually understand your goal and therefore your problem.
Finally I just realized that you use GUILayout elements inside the PropertyDrawer. This doesn't work. PropertyDrawers do not use the layout system. They get the rect they have to use as parameter. If you need more space for this property you have to override GetPropertyHeight and return how much space you need. However you have to use the GUI / EditorGUI functions inside a propertydrawer.
Answer by SHEePYTaGGeRNeP · Nov 29, 2017 at 11:00 AM
I found out how to copy using Json. So I'll just use that instead of this (was my original plan anyway)
"idName": "test2",
"copyValuesFromId": "test",
And then I loop through all the fields
foreach (BotPersonality bp in this.botPersonalities)
{
try
if (String.IsNullOrEmpty(bp.copyValuesFromId) ||
bp.copyValuesFromId.Equals("none", StringComparison.CurrentCultureIgnoreCase)) continue;
BotPersonality copyFrom = this.botPersonalities.Find(x =>
String.Equals(x.idName, bp.copyValuesFromId, StringComparison.CurrentCultureIgnoreCase));
Assert.IsNotNull(copyFrom, "Id was not found in CopyValuesFromId " + bp.copyValuesFromId);
foreach (FieldInfo fi in typeof(BotPersonality).GetFields(_BIND_FLAGS)
.Where(x => x.IsDefined(typeof(DataMemberAttribute), false)))
{
// we only copy values that are null
if (!ShouldCopyValue(fi, bp)) continue;
fi.SetValue(bp, fi.GetValue(copyFrom));
}
}
Then ShouldCopyValue checks if it set to the default values
private static bool ShouldCopyValue(FieldInfo fi, BotPersonality bp)
{
object value = fi.GetValue(bp);
if (value == null)
return true;
Type t = value.GetType();
if (t == typeof(int))
return (int) value == Int32.MinValue;
if (t == typeof(float))
return ((float) value).AboutEqualTo(Single.MinValue);
if (t.IsPrimitive)
throw new Exception("Type " + t + " is not supported yet for bp: " + bp.idName);
return false;
}
Thank you anyway @bunny83 for all the explanations :)