Use your interface
Handily enough, you already have your answer. Leave the speed
field public, but never pass around or declare references to that class. Only ever use the interface it implements.
“But wait!” you say. “This doesn’t stop me from shooting myself in the foot!”
Well, no. It doesn’t. You could still sneak around this by declaring a reference to that class anyway. You could give the class a name that implies it should not be used, but that reeks of code smell. If you want to be very strict about this, you could declare your class to have internal protection (i.e. not public) and compile it as a separate assembly. Probably not necessary.
There are many other options that don’t work well within Unity regarding the inspector.
Another alternative would be to publicize the names of editable fields from within the class itself, still using nameof
. Ugly, but possible, and retains encapsulation at the cost of simplicity, requiring essentially declaring every field twice, but hey, you can rename it just once!
public class MyMonoBehaviour : MonoBehaviour, IMyMonoBehaviour {
[SerializeField]
private float speed; //can rename to your heart’s content, as long as you use a FormerlySerializedAs attribute.
public float Speed => speed;
#if UNITY_EDITOR
public static class FieldNames {
//actual name of this property is irrelevant. Refactoring it does not affect the field name inside the MonoBehaviour, and vice versa.
public static string Speed => nameof(speed);
}
#endif
}
And to use it:
serializedObject.FindProperty(MyMonoBehaviour.FieldNames.Speed);
Another alternative is to have the editor be a nested class:
#if UNITY_EDITOR
using UnityEditor;
#endif
public class MyMonoBehaviour : MonoBehaviour, IMyMonoBehaviour {
[SerializeField]
private float speed; //don’t forget the FormerlySerializedAs attribute if you rename this.
public float Speed => speed;
#if UNITY_EDITOR
[CustomEditor(typeof(MyMonoBehaviour))]
public class MyMonoBehaviourEditor : Editor{
public override void OnInspectorGUI() {
serializedObject.FindProperty(speed); //or MyMonoBehaviour.speed if you want to be more explicit
}
}
#endif
}
If you are worried about having the editor in the same file as the implementation, they could be separated into distinct files using partial classes. Note that both files would still need to be in the same assembly (meaning neither file can be in an editor folder):
MyMonoBehaviour.cs:
public partial class MyMonoBehaviour : MonoBehaviour, IMyMonoBehaviour {
[SerializeField]
private float speed;
public float Speed => speed;
}
MyMonoBehaviourEditor.cs:
#if UNITY_EDITOR
using UnityEditor;
public partial class MyMonoBehaviour : MonoBehaviour, IMyMonoBehaviour {
[CustomEditor(typeof(MyMonoBehaviour))]
public class MyMonoBehaviourEditor : Editor{
public override void OnInspectorGUI() {
serializedObject.FindProperty(speed);
}
}
}
#endif
Note: I have not tested the above myself. It may not be possible to have nested classes be editors, or it may be possible to have the MyMonoBehaviourEditor class be private and still function correctly. If you try this out, feel free leave a comment or update the answer.