I made a custom editor for my component, with some properties, using IMGUI. It works fine, but if I change some values by default editor, they will be painted bold in inspector and will be serialized. If i using my custom editor, changes will be lost and reverted to prefab’s defaults. How can i have both at same time: serializing and custom editor?
Here is my Compoment:
[System.Serializable]
public class ArrayModifier : MonoBehaviour {
// i have tryed public, it does not solves serializing problem
[SerializeField]
int repeat = 1;
[SerializeField]
float offsetX = 3;
[SerializeField]
float offsetY = 0;
[SerializeField]
bool rotateOffset = false;
...
public int Repeat {
get {
return repeat;
}
set {
bool needsRefresh = !MathLvichka.Eq(value, repeat);
repeat = value;
if (needsRefresh) {
Refresh();
}
}
}
public float OffsetX {
get {
return offsetX;
}
set {
bool needsRefresh = !MathLvichka.Eq(value, offsetX);
offsetX = value;
if (needsRefresh) {
Refresh();
}
}
}
public float OffsetY {
get {
return offsetY;
}
set {
bool needsRefresh = !MathLvichka.Eq(value, offsetY);
offsetY = value;
if (needsRefresh) {
Refresh();
}
}
}
etc.
...
}
upd: I have searched through forum for topics like this, the solutions for properties are custom editor and [SerializeField]. Hence I use both, it is probably something wrong with my custom editor.
You need to tell Unity that the object will be changed, you do this with Undo.RecordObject
Also you should check in your properties for the same value being assigned else you will do a refresh when nothing has changed.
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(ArrayModifier))]
public class ArrayModifierEditor : Editor
{
public override void OnInspectorGUI()
{
ArrayModifier arr = (ArrayModifier)target;
DrawDefaultInspector();
EditorGUILayout.TextArea("", GUI.skin.horizontalSlider);
EditorGUI.BeginChangeCheck();
int repeat = EditorGUILayout.IntField("Count", arr.Repeat);
float offsetX = EditorGUILayout.FloatField("Offset X", arr.OffsetX);
float offSetY = EditorGUILayout.FloatField("Offset Y", arr.OffsetY);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Changed Array Modifier");
arr.Repeat = repeat;
arr.OffsetX = offsetX;
arr.OffsetY = offSetY;
}
if (GUILayout.Button("Refresh"))
{
//arr.Refresh();
}
}
}
However its much better to use SerialiedProperties instead as they manage all the undo stuff for you.
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(ArrayModifier))]
public class ArrayModifierEditor : Editor
{
SerializedProperty m_Repeat;
SerializedProperty m_OffsetX;
void OnEnable()
{
m_Repeat = serializedObject.FindProperty("repeat");
m_OffsetX = serializedObject.FindProperty("offsetX");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Repeat);
EditorGUILayout.PropertyField(m_OffsetX);
if (EditorGUI.EndChangeCheck())
{
// arr.Refresh();
}
serializedObject.ApplyModifiedProperties();
}
}
Thank you very much, SerializedProperty solution works just great. Except multiple object editing. Somehow it works only for the last object in my case. Here is my Editor code now:
using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
namespace Lvichki {
[CustomEditor(typeof(ArrayModifier))]
[CanEditMultipleObjects]
public class ArrayModifierEditor : Editor {
List<SerializedProperty> props = new List<SerializedProperty>();
static readonly List<string> prop_names = new List<string> {
"repeat",
"offsetX",
"offsetY",
"rotateOffset",
"orderInLayerProp",
"relativeOffset",
"noAutoRefresh",
"cropLastObject",
"keepPrefabs",
};
void OnEnable() {
props.Clear();
foreach (var prop_name in prop_names) {
var prop = serializedObject.FindProperty(prop_name);
if (prop != null) {
props.Add(prop);
} else {
LogLvichka.Error("Prop " + prop_name + " is not found.");
}
}
}
public override void OnInspectorGUI() {
ArrayModifier arr = (ArrayModifier)target;
serializedObject.Update();
EditorGUI.BeginChangeCheck();
foreach (var prop in props) {
EditorGUILayout.PropertyField(prop);
}
serializedObject.ApplyModifiedProperties();
if (EditorGUI.EndChangeCheck()) {
arr.Refresh();
}
EditorGUILayout.TextArea("", GUI.skin.horizontalSlider);
if (GUILayout.Button("Refresh")) {
arr.Refresh();
}
if (GUILayout.Button("Delete")) {
arr.DestroyArrayClones();
}
if (GUILayout.Button("Create on same level")) {
LogLvichka.Error("Create on same level ist ausgebaut. Neu implementieren, einfach folder nach oben rauspacken, 2 code paths ist zu verwirrend");
}
}
}
}
P.S. One thing i found a little bit strange, that serializedObject.FindProperty(prop_name); uses lower-case property name. So to find property “OffsetX” i need to use “offsetX”. I know it’s breaking of contract, but pure technically i could have two props on my object: Hello and hello. What’s then?
The property name needs to have the same case as the variable. Having 2 properties with the same name but different casing works. To support multi edit you need to add the attribute CanEditMultipleObjects.
This will work fine with PropertyFields, if you want to use none property versions then you need to manage the multi edit yourself like so https://docs.unity3d.com/ScriptReference/EditorGUI-showMixedValue.html
using UnityEngine;
[System.Serializable]
public class Example : MonoBehaviour
{
public int value1;
public float Value1;
}
using UnityEditor;
[CanEditMultipleObjects]
[CustomEditor(typeof(Example))]
public class ExampleEditor : Editor
{
SerializedProperty m_ValueInt;
SerializedProperty m_ValueFloat;
void OnEnable()
{
m_ValueInt = serializedObject.FindProperty("value1");
m_ValueFloat = serializedObject.FindProperty("Value1");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_ValueInt);
EditorGUILayout.PropertyField(m_ValueFloat);
serializedObject.ApplyModifiedProperties();
}
}