- Home /
Undo.RecordObject works with GUI.Button but not with GUILayout.Vector3Field
I have a custom class called Door that's serializable. It basically has a Vector3 Position and a reference field for a connected Room.
I also have a custom class called Room. In it I have an array of doors.
Now I have created my own CustomEditor(typeOf(Room)) in order to display each door in the array as a separate toggle Group. Also I have 2 buttons to add or delete a door from the array.
Now when I make changes in the prefab instance everything works perfectly. However when I make changes to the prefab asset they only get recorded when I add or delete a button. However if I change only the Position (Vector3Field) of the asset the changes cannot be saved and are lost when I click play.
Here is my code:
using ...
[System.Serializable]
public class Door
{
public Vector3 position;
public Room connectedRoom;
//Constructors
}
Room:
using ...
public class Room : MonoBehaviour
{
[HideInInspector]
public Door[] doors;
}
CustomEditor:
using ...
[CustomEditor(typeof(Room))]
public class RoomEditor : Editor
{
Room targetRoom;
int count;
private List<bool> listOfBools = new List<bool>();
private List<Door> tempDoors = new List<Door>();
private void OnEnable()
{
listOfBools.Clear();
tempDoors.Clear();
targetRoom = (Room)target;
count = targetRoom.doors.Length;
for (int i = 0; i < count; i++)
{
listOfBools.Add(true);
tempDoors.Add(targetRoom.doors[i]);
}
}
public override void OnInspectorGUI()
{
//Toggle Groups for doors
for (int i = 0; i < count; i++)
{
listOfBools[i] = EditorGUILayout.BeginToggleGroup("Door " + i.ToString(), listOfBools[i]);
if (listOfBools[i])
{
//DoorGUI function at the bottom
tempDoors[i] = DoorGUI(targetRoom.doors[i]);
}
EditorGUILayout.EndToggleGroup();
}
//Add Button
if (GUILayout.Button("+", GUILayout.Width(25)))
{
count++;
tempDoors.Add(new Door());
listOfBools.Add(true);
}
//delete Button
if (GUILayout.Button("-", GUILayout.Width(25)))
{
count--;
tempDoors.RemoveAt(count);
listOfBools.RemoveAt(count);
}
//save changes
if (GUI.changed)
{
Undo.RecordObject(targetRoom, "target Room changed");
targetRoom.doors = tempDoors.ToArray();
PrefabUtility.RecordPrefabInstancePropertyModifications(targetRoom);
}
}
private Door DoorGUI (Door d)
{
d.position = EditorGUILayout.Vector3Field("Pos: ", d.position);
d.connectedRoom = (Room)EditorGUILayout.ObjectField("Con Room ", d.connectedRoom, typeof(Room), true);
return d;
}
}
Here is a picture of how it looks like:
The "Save" Button where the red arrow points only gets clickable when I add or delete a door via the "+" and "-" button in the inspector. When I only change a value of the Vector3field I cannot save and the changes are lost.
If I alter the Vector3field for an instance instead of the asset itself, the changes are saved.
I'd be very grateful for your help! Thanks in advance!
I tried using SerializedObject and SerializedProperty. Doing that, all changes that I made were saved properly, regardless of whether I was changing an instance or an asset. However I ended up with an inspector that didn't suit my needs. And I couldn't figure out how to manipulate the look of the PropertyField. So should I be setting the scene to dirty after changing properties in the inspector? But then that utility is marked as obsolete and also why does it work with the button but not the Vector3Field?
Answer by Bunny83 · Apr 16, 2019 at 10:09 AM
You have to call "RecordObject" before you apply any changes to the serialized object. Since your "Doors" are classes, they are reference types in memory. So what you do here is you manipulate your door instances directly inside your "DoorGUI" method and you call RecordObject after those changes. So it actually records the already changed state. Since no changes are applied afterwards, no changes are detected.
A simple fix for your specific case here would be to turn your Door class into a Door struct:
public struct Door
{
// [ ... ]
That way you actually get a copy of your Doors when you pull them into your temp list. Though since we don't know how and where that Door instances may be used this may not be possible. In this case you have to ensure to record the object state before you change anything.
Just to make sure you understand what is happening. This line:
targetRoom.doors = tempDoors.ToArray();
Doesn't change anything if your tempDoors list still contains the same class instances as before. The targetRoom.doors array as well as tempDoors both contain the same instances. So if no doors got added or removed this line where you assign the array back has no effect at all. With Door structs you actually have copies of those Door instances and assigning the array back will actually overwrite the array with the modified copies.
Oh my god, that makes sense. Thank you indeed Bunny83.
So my options are 1. making Door a struct or 2. calling the Undo.RecordObject function before I draw my DoorGUI? Is that correct?
Thanks a million.