I’ve been developing tools for the Unity editor for quite some time now, but I keep stumbling over basics. Today, I went over a few concepts and thought I’d share my results with the community. Some of this may help beginners understand different systems and for advanced users, I would be happy to ask some questions myself.
My test setup consists of a MonoBehaviour component added to a GameObject in the scene. The GameObject is then turned into a prefab and duplicated. We can now test the following cases:
- Edit a prefab instance in the scene via the inspector
- Edit a prefab in prefab mode
- Multi-Edit prefab instances in the scene
- Edit via SceneView Handles
And here is my test code:
using UnityEditor;
using UnityEngine;
public class PrefabEditTest : MonoBehaviour
{
public Vector3 someValue;
public bool useCustomSceneViewGUICallback = true;
}
[CustomEditor(typeof(PrefabEditTest))]
[CanEditMultipleObjects]
public class PrefabEditTestEditor : Editor
{
public override void OnInspectorGUI()
{
DrawToggle();
DrawSerializedProperty();
DrawCustomControl();
}
private void DrawToggle()
{
// This is only here to easily switch between using Editor.SceneViewGUI and
// a custom delegate added to SceneView.duringSceneGui.
serializedObject.Update();
var toggleProp = serializedObject.FindProperty("useCustomSceneViewGUICallback");
EditorGUILayout.PropertyField(toggleProp);
serializedObject.ApplyModifiedProperties();
}
private void DrawSerializedProperty()
{
// Regular inspector fields via SerializedProperty.
// Everything works and is handled automatically:
// - Undo
// - Prefab instance overrides
// - Editing in prefab mode
// - Setting scene dirty and saving changes.
// - Multi-object-editing
// Update the object representation from serialization. This ensures that the MonoBehaviour
// class instance of PrefabEditTest will have the most recent values that are actually stored on disk.
serializedObject.Update();
SerializedProperty prop = serializedObject.FindProperty("someValue");
// This automatically records undo und handles prefab overrides (bold text and blue markings).
// It basically does everything Unity supports in one call.
EditorGUILayout.PropertyField(prop);
// Make sure we write changed values to disk.
serializedObject.ApplyModifiedProperties();
}
private void DrawCustomControl()
{
// Try to handle the same functionality with individual steps (because sometimes we need control).
// This time we directly edit the reference to the target object (our MonoBehaviour class).
PrefabEditTest script = (PrefabEditTest)target;
// However, we still need the SerializedProperty to retrieve information such as prefab overrides.
SerializedProperty prop = serializedObject.FindProperty("someValue");
float height = EditorGUIUtility.wideMode ? EditorGUIUtility.singleLineHeight : EditorGUIUtility.singleLineHeight * 2f;
Rect rect = EditorGUILayout.GetControlRect(true, height);
// Handles bold label when this property is a prefab override and shows mixed values indicator when multi-editing.
// However, small ISSUE_A: When a Vector3 component is overriden but only the x value is different,
// it makes all three components bold, although only x should be shown as a bold override.
var label = EditorGUI.BeginProperty(rect, new GUIContent(prop.displayName), prop);
// Only do something if the values from our inspector control have actually changed.
EditorGUI.BeginChangeCheck();
// Do not write the value back to the script immediately, because we first need to record an Undo step.
var value = EditorGUI.Vector3Field(rect, label, script.someValue);
if (EditorGUI.EndChangeCheck())
{
// Handle multi-object editing. Apply changes to all instances, not only the first one.
for (int i = 0; i < targets.Length; i++)
{
var target = targets[i];
script = (PrefabEditTest)target;
// Record undo of previous values on object (works nicely).
Undo.RecordObject(target, "Change Value");
// Now change the values on the MonoBehaviour class.
script.someValue = value;
#region ISSUE_B
// Documentation says: "If this method is not called, changes made to the instance are lost."
// However, in my tests, this does not change the behaviour at all, changes don't seem to be lost if I don't use it.
// Also, should I only call this on prefab instances in the scene or can I safely call it on all GameObjects or Prefabs?
// For Example, should I check for '!EditorUtility.IsPersistent(target)' first?
PrefabUtility.RecordPrefabInstancePropertyModifications(target);
#endregion
}
#region ISSUE_C
// At this point, most things seem to work, except:
// When multi-editing targets with multiple different values, their values all become the same under the hood (which is correct),
// however, when moving the slider or changing values in the field, the inspector still shows "mixed values" until the
// target is deselected and selected again. Basically, prop.hasMultipleDifferentValues is still true and only reset
// once the editor is recreated. Updating or applying the serializedObject does not help.
// To fix this, it seems, I need to assign the values back to the SerializedProperty and apply the SerializedObject.
prop.vector3Value = script.someValue;
serializedObject.ApplyModifiedProperties();
#endregion
}
EditorGUI.EndProperty();
}
public void OnSceneGUI()
{
// Using serializedObject in this method results in an error:
// "The serializedObject should not be used inside OnSceneGUI or OnPreviewGUI. Use the target property directly instead."
// It seems, multi-object-editing cannot be easily implemented. At least, if we want to draw
// a single handle control and change the values of multiple instances.
// Instead, this method will draw multiple handles, one for each instance separately.
PrefabEditTest script = (PrefabEditTest)target;
// Disable this example if we are using the custom callback.
if (script.useCustomSceneViewGUICallback)
return;
EditorGUI.BeginChangeCheck();
Vector3 value = Handles.Slider(script.someValue, Vector3.right);
if (EditorGUI.EndChangeCheck())
{
// Next error: when trying to use the targets array to support multi-editing:
// "The targets array should not be used inside OnSceneGUI or OnPreviewGUI. Use the single target property instead."
// Record undo of previous values on object (works nicely).
Undo.RecordObject(target, "Change Value");
// Now change the values on the MonoBehaviour class.
script.someValue = value;
#region ISSUE_B
// Documentation says: "If this method is not called, changes made to the instance are lost."
// However, in my tests, this does not change the behaviour at all, changes don't seem to be lost if I don't use it.
// Also, should I only call this on prefab instances in the scene or can I safely call it on all GameObjects or Prefabs?
// For Example, should I check for '!EditorUtility.IsPersistent(target)' first?
PrefabUtility.RecordPrefabInstancePropertyModifications(target);
#endregion
}
#region ISSUE_D
// Using Handles in OnSceneGUI this way, results in the basic functionality working, but multi-object-editing is not fully supported.
#endregion
}
private void OnEnable()
{
// Here we add a callback to the delegate, which hooks into the
// same loop as OnSceneGUI, but allows us to use SerializedProperty.
SceneView.duringSceneGui += MySceneGUI;
}
private void OnDisable()
{
SceneView.duringSceneGui -= MySceneGUI;
}
private void MySceneGUI(SceneView obj)
{
PrefabEditTest script = (PrefabEditTest)target;
// Disable this example if we are not using the custom callback.
if (!script.useCustomSceneViewGUICallback)
return;
// With a custom callback, we can support multi-editing simply by using serializedObject.
serializedObject.Update();
// This also makes the rest of the code very easy.
SerializedProperty prop = serializedObject.FindProperty("someValue");
prop.vector3Value = Handles.Slider(prop.vector3Value, Vector3.right);
serializedObject.ApplyModifiedProperties();
}
}
So here are my own questions:
ISSUE_A

When I use a custom control to draw “Some Value” I use BeginProperty and EndProperty to make sure that the label is drawn bold and the blue line is added to indicate a prefab override. However, it makes all three vector components x, y and z bold as well, whereas the SerializedProperty implementation only highlights x. How do I implement this correctly? Or is this a bug in BeginProperty?
ISSUE_B
The documentation advises to call PrefabUtility.RecordPrefabInstancePropertyModifications(target) after changes have been made to a prefab instance. In my test, however, there was no observable difference. Values were still saved. Do I really need to call this? Are there any cases which I haven’t tested which are affected by this? And do I need to first test, if the target is actually a prefab instance, e.g. use (target) ?
Overall I feel like there are a few things I would like to see in the documentation. For example, why can’t I use serializedObject in Editor.OnSceneGUI, but it works fine in SceneView.duringSceneGUI? And what are the recommended approaches for Handles and multi-editing?