Search Unity

Serialize readonly field

Discussion in 'Scripting' started by GeorgeRoger, Aug 18, 2016.

  1. GeorgeRoger

    GeorgeRoger

    Joined:
    Aug 18, 2016
    Posts:
    1
    I'm using scriptable objects saved as assets to store data for use across scenes. At first I was trying to use this approach.

    Code (CSharp):
    1.     [CreateAssetMenu(fileName ="PlayerData", menuName ="Player Data")]
    2.     public class PlayerData : ScriptableObject
    3.     {
    4.         [SerializeField]
    5.         public int health;
    6.     }
    7.    
    This works, but if someone changes the value of health in script, it changes on the asset, and can mess up the data. So I stopped making the fields public and added getters.

    Code (CSharp):
    1.     [CreateAssetMenu(fileName ="PlayerData", menuName ="Player Data")]
    2.     public class PlayerData : ScriptableObject
    3.     {
    4.         [SerializeField]
    5.         int health;
    6.  
    7.         public int Health
    8.         {
    9.             get { return health; }
    10.         }
    11.     }
    12.    
    This provides exactly the behaviour I'm after, but when using more fields, creates a huge amount of boiler plate code. What I really want to be able to do is...

    Code (CSharp):
    1.     [CreateAssetMenu(fileName ="PlayerData", menuName ="Player Data")]
    2.     public class PlayerData : ScriptableObject
    3.     {
    4.         [SerializeField]
    5.         public readonly int health;
    6.     }
    7.    
    But unity doesn't serialize readonly fields. The same problem arises with trying to use const fields or automatic properties.

    Does anyone know a simple fix for this?
     
  2. Tudor

    Tudor

    Joined:
    Sep 27, 2012
    Posts:
    150
    +1 on this. Helps keep people from shooting themselves in the foot later with messy data write access, and makes the readonly keyword more useful in general.
     
    mawa_tari, SolidAlloy and X3doll like this.
  3. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    Unity can't serialize readonly fields. Readonly fields can only be set in either the object initializer or the constructor of the class. Unity's serialization sets the data for serialized fields after initialization and construction, but before the Awake() call. Unfortunately, that's too late for readonly fields. So it is unlikely that readonly fields will ever be serializable by Unity.

    As of today, without using any third party code, there is no better way to get public read private write properties in Unity, other than to use a property with a serialized backing field. You'll just have to live with the boilerplate.
     
  4. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
    If youre using Unity 2017 and .NET 4.6 you could compact it a little using expression bodied members, but yeah its still not exactly a one liner:
    Code (CSharp):
    1. [SerializeField]
    2. private int health;
    3. public int Health => health;
     
    Marks4, Bunny83, Ceperadlo and 5 others like this.
  5. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Yeah, I can't see this ever happening. Readonly properties are your best bet, and IMHO they're good enough. The "huge amount of boiler plate code" is an extra 1 line per field, and you should arguably only be exposing properties (not fields) publicly to begin with, so it's not even that- it's not adding a property, it's removing the setter from it. Less code.

    *shrugs*
     
    Last edited: Jan 29, 2018
    Bunny83 likes this.
  6. Aminushki

    Aminushki

    Joined:
    Jun 10, 2016
    Posts:
    11
  7. Aminushki

    Aminushki

    Joined:
    Jun 10, 2016
    Posts:
    11
    Also you know you can do [whatever] in front of your variable right? so if you only use [ReadOnly] it should be flush
     
  8. ArachnidAnimal

    ArachnidAnimal

    Joined:
    Mar 3, 2015
    Posts:
    1,815
    The ReadOnly attribute is not going to solve the OPs issue.
    The op wants to Unity to serialize readonly fields.
    The ReadOnly attribute only prevents the value from being modified in the inspector. It does not make the variable readonly.
     
    Bunny83 and nzhangaudio like this.
  9. Flubzies

    Flubzies

    Joined:
    Jun 5, 2017
    Posts:
    5
    Not very compact or elegant, but it's the best workaround I could think of within Unity. I use it occasionally, real shame you can't just use readonly. The biggest problem is the amount that has to be typed out, but you only need to do it once for every class unless you want to arrange your variables in a certain order. But the result is the same (as far as I can tell.)

    Code (CSharp):
    1.  
    2. public class Class : MonoBehaviour
    3. {
    4.     [SerializeField] ReadOnly _Get = null;
    5.  
    6.     [System.Serializable]
    7.     class ReadOnly
    8.     {
    9.         [SerializeField] int _myInt = 90;
    10.         public int _MyInt { get { return _myInt; } }
    11.     }
    12.  
    13.     private void Start ()
    14.     {
    15.         Debug.Log (_Get._MyInt);
    16.     }
    17. }
    18.  
    This allows you to make sure the variable is completely readonly. (Can't even be set in its own class.) But can be serialize set.
    You can skip the ReadOnly class stuff if you want it only readonly outside the class it belongs to.
    If you have Odin, you can add [HideValue] to the _Get and make it look a lot nicer in the inspector.
     
  10. TimHW

    TimHW

    Joined:
    Sep 8, 2013
    Posts:
    5
    This can actually be done now with C# 7.3, which Unity now supports:
    Example:
    Code (CSharp):
    1. [System.Serializable]
    2. public class Item
    3. {
    4.     [field: SerializeField] public int Id { get; private set; }
    5. }

    Note that if you are manually filling out variable names in a save file, or want it easy to edit for users, the compiler-generated name for the backing field isn't very nice - <PropertyName>k__BackingField.

    Example:
    For an item with an Id of 16, it would be written in a .json file as follows:
    Code (JSON):
    1. {
    2.     "<Id>k__BackingField": 16
    3. }


    END OF ABOVE APPROACH
    _____________________________________________________________________________________________________________________

    ALTERNATIVE BELOW
    If the above approach isn't appealing, here is an offered improvement on Flubzie's suggestion.

    A generic class will simplify implementing each class/type (I've added an implicit operator to make it nicer to use for assignment as well - it can be omitted from the implementation if not desired):
    Code (CSharp):
    1. [System.Serializable]
    2. public class ReadOnly<T>
    3. {
    4.     [SerializeField] private T value;
    5.     public T Value { get { return value; } }
    6.  
    7.     public static implicit operator T(ReadOnly<T> instance)
    8.     {
    9.         return (instance.value);
    10.     }
    11. }
    Sadly, you cannot serialize a generic class. However, you can use a class that inherits from the generic class, which makes each new type's implementation simple.

    Example usage:
    Code (CSharp):
    1. // Implement desired types as follows:
    2. [System.Serializable] public class ReadOnlyString : ReadOnly<string> { }
    3. [System.Serializable] public class ReadOnlyInt : ReadOnly<int> { }
    4.  
    5. // Example data class:
    6. [System.Serializable]
    7. public class UserInfo
    8. {
    9.     public ReadOnlyString name;
    10.     public ReadOnlyInt age;
    11.     public ReadOnlyString address;
    12. }
    13.  
    14. // Example usage:
    15. public class User : MonoBehaviour
    16. {
    17.     private UserInfo info;
    18.  
    19.     private void Start()
    20.     {
    21.         // Output the loaded object's results:
    22.         Debug.Log(info.name.Value);
    23.         Debug.Log(info.age.Value);
    24.         Debug.Log(info.address.Value);
    25.  
    26.         // The implicit operator allows us to perform simple, clean assignment:
    27.         string name = info.name;
    28.         int modifiedAge = info.age + 7;
    29.         Debug.Log(name);
    30.         Debug.Log(modifiedAge);
    31.         Debug.Log(info.address + ", NY 10036, United States");
    32.  
    33.         // Thanks to the implicit operator, the following works as well:
    34.         Debug.Log((string)info.name);
    35.         Debug.Log((int)info.age);
    36.         Debug.Log((string)info.address);
    37.     }
    38. }
     
  11. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    515
    One-liner :eek:
    Code (CSharp):
    1. public Animator Animator => _animator; [SerializeField] Animator _animator;  
     
    eduardoromeueberts and jhocking like this.
  12. Bip901

    Bip901

    Joined:
    May 18, 2017
    Posts:
    71
    Serializing private fields is a very common and recommended practice in Unity. Unity doesn't allow you to construct your own MonoBehaviours, so you rely on setting values from the inspector as a constructor.
     
  13. SolidAlloy

    SolidAlloy

    Joined:
    Oct 21, 2019
    Posts:
    58
    I believe the OP wanted not to serialize the field (why would he? The field's value is known beforehand anyway since it's read-only), but just to show the read-only field value in the inspector. SerializeField is just the only attribute we have to show fields in the inspector because Unity doesn't have something like the ShowInInspector attribute by Odin (which would be very helpful if such an attribute was out of the box).

    One additional disadvantage to the most popular solution (field + get-only property) is that the field can be changed inside the class anyway. It is not very hard to keep in mind that the field must not be changed, but if we have a nice C# keyword that prevents it on the compiler level, why not take an advantage of it? It's just a matter of convenience.

    @TimHW, thanks for the heads up about the [field: ] feature, it is the best solution so far! Also, since generic classes are supported by Unity 2020 now, there is no need in implementing additional classes for your second solution.
     
    Last edited: Oct 29, 2020
  14. SolidAlloy

    SolidAlloy

    Joined:
    Oct 21, 2019
    Posts:
    58
    In my case, I have a parent class with a bunch of get-only abstract properties, and several inheritors that change the value returned by those properties. I wanted to display the values of the overridden properties in the inspector, and here is what I came up with:

    Code (CSharp):
    1. public abstract class Class : MonoBehaviour
    2. {
    3.     [SerializeField, ReadOnly] private bool _hasMeleeAttack;
    4.  
    5.     public abstract bool HasMeleeAttack { get; }
    6.  
    7.     private void Reset()
    8.     {
    9.         _hasMeleeAttack = HasMeleeAttack;
    10.     }
    11. }
    12.  
    13. public class Archer : Class
    14. {
    15.     public override bool HasMeleeAttack => false;
    16. }
    As you can see, all the boiler-plate code is located in the parent class. Child classes don't have access to the serialized field and can only override the get-only property.
    The result looks good in the inspector:
    archer.png knight.png

    ReadOnly attribute drawer I'm using is pretty straightforward:

    Code (CSharp):
    1. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    2. {
    3.     bool prevValue = GUI.enabled;
    4.     GUI.enabled = false;
    5.  
    6.     EditorGUI.PropertyField(position, property, label, true);
    7.  
    8.     GUI.enabled = prevValue;
    9. }
     
    Voxel-Busters and Wappenull like this.
  15. DrivZone

    DrivZone

    Joined:
    Aug 15, 2017
    Posts:
    4
    For anyone that will eventually come here, this is simply how to do it:

    First we make a script in the scripts folder(Not inside the Editor folder), lets name it "CustomAttributes" and lets delete everything and add this line of code

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ReadOnlyAttribute : PropertyAttribute { }
    Now we go make An "Editor" folder inside the "Scripts" folder. You have to name it "Editor".
    And lets make a new empty script in the Editor folder and maybe call it "AttributeManager", and add this code in it

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
    5. public class ReadOnlyDrawer : PropertyDrawer
    6. {
    7.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    8.     {
    9.         var previousGUIState = GUI.enabled;
    10.  
    11.         GUI.enabled = false;
    12.  
    13.         EditorGUI.PropertyField(position, property, label);
    14.  
    15.         GUI.enabled = previousGUIState;
    16.     }
    17. }
    You may have realized that you should put "Attribute" at the end of your attribute name (basically ReadOnlyAttribute = ReadOnly when used).

    Now, to use it you only add [ReadOnly] before a public or serialized variable.
     
  16. Eloren

    Eloren

    Joined:
    Jul 17, 2019
    Posts:
    24
  17. huulong

    huulong

    Joined:
    Jul 1, 2013
    Posts:
    224
    Actually the Unity Answers link mentioned above (the reply by FuzzyLogic) uses a scope to preserve the previous GUI state. I recommend using (new EditorGUI.DisabledScope(true)) instead of DisabledGroupScope for better memory usage though.
     
  18. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    I use the following VSCode Snippet to help make writing this a bit less of a pain

    Code (CSharp):
    1. "Serialized Protected Property": {
    2.         "prefix": ["propfullunityser", "propu"],
    3.         "body": [
    4.             "[SerializeField] private ${2:type} _${1:name} = ${3:default};",
    5.             "public ${2:type} ${1:name}",
    6.             "{",
    7.             "\tget {",
    8.             "\t\treturn _${1:name};",
    9.             "\t}",
    10.             "\tprotected set {",
    11.             "\t\t_${1:name} = value;",
    12.             "\t}",
    13.             "}"
    14.         ],
    15.         "description": "Create a protected set serialized field with public get and private backing variable"
    16.     }
     
  19. samlletas

    samlletas

    Joined:
    Jan 2, 2016
    Posts:
    30
    DrivZone likes this.
  20. marcospgp

    marcospgp

    Joined:
    Jun 11, 2018
    Posts:
    194
    Anything is a one liner if you don't break your lines :)
     
    Riiich, Bunny83, jhocking and 3 others like this.
  21. jhocking

    jhocking

    Joined:
    Nov 21, 2009
    Posts:
    814
    Actually that is what he wanted, and this sentence in the first post explains why:
    "This works, but if someone changes the value of health in script, it changes on the asset, and can mess up the data. So I stopped making the fields public and added getters."

    That is, he wants to be able to change the values on scriptable objects in the editor but not in script. What you talk about is basically the reverse (also useful, but very different): values that can't be changed in the editor, only in code.

    Although, oddly the rest of your post does address the correct issue, so maybe you realized this halfway through writing and just didn't change the first sentence?

    oo I didn't know about this! I do the {get; private set;} thing routinely, but didn't know you can serialize that.
     
    Last edited: Jun 17, 2022
    Bunny83 likes this.