Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

C#: How to bring back the value of the variable after changing it in 'if'?

Discussion in 'Scripting' started by promant12, Aug 3, 2018.

  1. promant12

    promant12

    Joined:
    Aug 8, 2017
    Posts:
    2
    So, I created an object that's changing size over time with an certain speed. At start, that object's scale = 1f (maxScale) and scalingSpeed = 1f. When the object is scaled to 0.5f, the scalingSpeed also decreases to 0.66f, which is halfScaledSpeed. Then, the object is scaled to 0.1f and after that, it's scaled back to 0.5f. At this point, I want also to bring back the old scalingSpeed.
    The problem is, that I can't simply write "scalingSpeed = 1f", because I wrote variables' values in the inspector, not in script (I want to re-use this script on other objects with changed numbers).

    Code (CSharp):
    1. if (number != 1 && transform.localScale.x < maxScale / 2) {  
    2.             scalingSpeed = halfScaledSpeed;
    3.         }
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,140
    You could have another variable with the other speed if you want and expose that in the inspector if you need to have it different values and then when your scale size is larger, just set the scalingSpeed = to that value.
     
  3. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Standard thing for most Unity projects needing the original number. Just create a private variable and store it in Awake().

    Code (CSharp):
    1.     public class SomeTest : MonoBehaviour
    2.     {
    3.         public float someFloatThatllChangeAndIsSetInInspector; // Play with this to your hearts content.
    4.         private float yourFallbackOriginalFloat; // Don't change this apart from at the beginning.
    5.  
    6.         private void Awake()
    7.         {
    8.             yourFallbackOriginalFloat = someFloatThatllChangeAndIsSetInInspector;
    9.         }
    10.  
    11.         private void GoBackToOriginalNumber()
    12.         {
    13.             someFloatThatllChangeAndIsSetInInspector = yourFallbackOriginalFloat;
    14.         }
    15.  
    16.         public void ChangeThatNumberSometimes()
    17.         {
    18.             someFloatThatllChangeAndIsSetInInspector += 2350985440697f;
    19.         }
    20.     }
    Hopefully eventually Unity will allow readonly variables that can be set at the beginning of play, but for now this works.
     
    Kiwasi likes this.
  4. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I wonder how that is supposed to look like? What's the purpose?
     
    Nigey likes this.
  5. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Just my OCD wanting proper SOLID really.

    Code (CSharp):
    1.     public class AnotherExample : MonoBehaviour
    2.     {
    3.         [SerializeField] private readonly string someTagName; // A tag name that you don't want to be a string somewhere in your code, but won't change during play.
    4.         [SerializeField] private readonly float maxHitPoints; // Something you want to expose in the inspector for designers, but don't want it changeable during play outside of the inspector interface.
    5.         [SerializeField] public string ThisPlayersName { get; private set; } // Unity needs to check there are no extra actions happening in this property's set value.
    6.  
    7.         public void ChangeName(string name)
    8.         {
    9.             // Blah blah you get it.
    10.         }
    11.     }
    I mean they're things you can get around. You can just hit F12 in VS and check that your vars aren't being modified, but it's a shame you can't make readonly's. That piece of mind I like.
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    You can always use properties, and simply do not declare any set for it. Not as pretty, and more code, but there you have it, non-modifiable variable.

    Awake() is nowhere near the actual object constructor in terms of object initialization timeline. So I don't think that will happen ever.

    Bonus points if you actually grab the values from the SerializedField property and push them to the readonly variable inside the constructor. That might work as well.
     
    Nigey likes this.
  7. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Well, that's clearly against the language rules. You can also get around it using reflection, but it's a really dirty hack, and I'd never ever rely on it.

    Next thing is, if such a value is meant to be unchanged, then it's the programmers responsibility to not touch it. Why would you touch it in the first place, if it's supposed to stay as it is? It's one of a programmer's discipline to ensure that this doesn't happen.
    Also, if it is likely to happen that you forget about it or someone else is about to change it, you might think about a better naming which expresses the intent of such a field.

    Anyway, programming wouldn't be so fascinating if there weren't various ways to solve a problem.

    You can also achieve what you want using a ReadOnly<TValue> wrapper that is serializable, exposes it's private field to the inspector and provides a TValue getter.
    Seems inconvenient, but this can be handy and solves your problem with a small overhead.

    Another Unity-specific way is - especially for tags, data sets that serve as base or reference values and such - are ScriptableObjects. The idea is the same. Allow designers to define the values they want, and provide a readonly-interface.
    This also decouples the data from the component that drives the logic. The designers can easily create multiple assets of pluggable data sets for monster (as an example), without the need to create tons of redundant prefabs.
     
    Kiwasi and Nigey like this.
  8. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    ScriptableObjects are a great Unity way that can resolve this, I agree. In terms of the programmers responsibility not to touch it. That part is true, but for me it's important to set up each script as an API both to designers and other programmers, without necessarily needing to explicitly state all the vars requirements in it's name. I mean I can use summaries/comments for programmers to give them some more detail annd decorate with tooltips for designers, but to me part of an API for programmers is sticking to a healthy open/close relationship in terms of how I scope vars. The reason for C# having readonly and const is to have immutable variables. So generally I personally treat these as a guide, or API for the programmer. They can't physically mess up my code without stopping the code being a const or a readonly. It helps give them a bit more definition to the overall structure/use of the class as well. I mean you're right, there's lots of ingenious workarounds. The ScriptableObject in particular is one I need to use more often., and you can just make sure all your programmers know the ins and outs of each other's scripts and not to mess with the scripts. I didn't know about the ReadOnly<TValue> wrapper, do you have an example to hand?
     
    Last edited: Aug 6, 2018
  9. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    It would have been cool if Unity supported usign the constructor for serialization. That way you can choose if you want a field readonly or not
     
  10. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    This can still be done in many different ways.


    Well, that's clearly what they're made for. But I'd definitely not introuce yet another weird Unity-specific behaviour, such as messing with values of reaonly after field initialization / constructors.


    Well, I'm sure you can guess how the SO approach would look like.

    The wrapper can be as simple as
    Code (CSharp):
    1. public abstract class ReadOnlyValue<TValue>
    2. {
    3.     [UnityEngine.SerializeField]
    4.     private TValue _value;
    5.  
    6.     public TValue Value
    7.     {
    8.         get { return _value; }
    9.     }
    10. }
    You can then just add
    Code (CSharp):
    1. [System.Serializable]
    2. public sealed class ReadonlyString : ReadOnlyValue<string> { }
    and noone will be able to mess with the value.

    It does not prevent anyone from creating a new instance of the wrapper though, so it's no absolutely readonly replacement, but the name type suggests you're not supposed to mess with it. If you turn the constructor into private, noone's gonna be able to create a useful instance anyway, unless with the use of reflection (that's what Unity does).

    It is not much additional work, due to non-virtual calls and the additional sealed keyword, it is also likely that the getter might be optimized away.

    The same applies to types that wrap around whole sets of data. I use these alot, and I also add interfaces for many of the important data types, so that I do not need to worry about the specific implementation.
     
    Last edited: Aug 6, 2018
    Nigey and Kiwasi like this.
  11. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    I see your point about trying not to add too many Unity specifics. I'll give this a whirl actually, thanks :).
     
  12. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Anyone curious about how what @Suddoha 's suggestion looks like, it's like this now:

    Code (CSharp):
    1.     using System;
    2.     using UnityEngine;
    3.  
    4.     [Serializable] public sealed class ReadOnlyString : ReadOnlyValue<string> { }
    5.     [Serializable] public sealed class ReadOnlyFloat : ReadOnlyValue<float> { }
    6.     [Serializable] public sealed class ReadOnlyInt : ReadOnlyValue<int> { }
    7.     [Serializable] public sealed class ReadOnlyBool : ReadOnlyValue<bool> { }
    8.  
    9.     [Serializable]
    10.     public abstract class ReadOnlyValue<TValue>
    11.     {
    12.         [SerializeField]
    13.         private TValue value;
    14.  
    15.         public TValue Value
    16.         {
    17.             get
    18.             {
    19.                 return value;
    20.             }
    21.         }
    22.     }
    Code (CSharp):
    1.     using UnityEditor;
    2.     using UnityEngine;
    3.  
    4.     [CustomPropertyDrawer(typeof(ReadOnlyValue<bool>), true)]
    5.     [CustomPropertyDrawer(typeof(ReadOnlyValue<float>), true)]
    6.     [CustomPropertyDrawer(typeof(ReadOnlyValue<int>), true)]
    7.     [CustomPropertyDrawer(typeof(ReadOnlyValue<string>), true)]
    8.     public class ReadOnlyValueDrawer : PropertyDrawer
    9.     {
    10.         public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    11.         {
    12.             return EditorGUI.GetPropertyHeight(property.FindPropertyRelative("value"), label, true);
    13.         }
    14.  
    15.         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    16.         {
    17.             EditorGUI.PropertyField(position, property.FindPropertyRelative("value"), label, true);
    18.         }
    19.     }
    A tiny tiny annoyance, but I also added this for integration:

    Code (CSharp):
    1. public class ReadOnlyRangeAttribute : PropertyAttribute
    2.     {
    3.         public float min, max;
    4.  
    5.         public ReadOnlyRangeAttribute(float min, float max)
    6.         {
    7.             this.min = min;
    8.             this.max = max;
    9.         }
    10.     }
    Code (CSharp):
    1. using System;
    2.     using UnityEditor;
    3.     using UnityEngine;
    4.  
    5.     [CustomPropertyDrawer(typeof(ReadOnlyRangeAttribute))]
    6.     public class ReadOnlyRangeDrawer : PropertyDrawer
    7.     {
    8.         // Draw the property inside the given rect
    9.         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    10.         {
    11.             // First get the attribute since it contains the range for the slider
    12.             ReadOnlyRangeAttribute range = attribute as ReadOnlyRangeAttribute;
    13.  
    14.             // Now draw the property as a Slider or an IntSlider based on whether it's a float or integer.
    15.             if (property.FindPropertyRelative("value").propertyType == SerializedPropertyType.Float)
    16.             {
    17.                 EditorGUI.Slider(position, property.FindPropertyRelative("value"), range.min, range.max, label);
    18.             }
    19.             else if (property.FindPropertyRelative("value").propertyType == SerializedPropertyType.Integer)
    20.             {
    21.                 EditorGUI.IntSlider(position, property.FindPropertyRelative("value"), Convert.ToInt32(range.min), Convert.ToInt32(range.max), label);
    22.             }
    23.             else
    24.             {
    25.                 EditorGUI.LabelField(position, label.text, "Use Range with float or int.");
    26.             }
    27.         }
    28.     }
     
    Last edited: Aug 7, 2018