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. Dismiss Notice

Is it better to be public or protected?

Discussion in 'Scripting' started by Marscaleb, Mar 10, 2016.

  1. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    971
    When I started writing scripts in Unity, I kept many of my variables public. For example, a character's health. This made it really easy for other scripts to read those values, or even set them if need be.

    But later, some things that some of my professional programmer friends had said led me to think it was better to minimize my public variables, and so I began to instead have variables be private (or usually, protected, since I extend a lot of my scripts,) and then have publicly-accessible functions that can be used to read the values, or set them if needed.

    Today I wonder if there is any difference, noticeable or otherwise.

    First of all, something about leaving them public made them feel less secure, as if that made it easier for some random glitch to mess with those properties, or as if people trying to hack the game will be able to easily cheat and break stuff. But these feelings are whimsical and not based off of anything I actually understand.
    As far as what I do understand, having a variable be public doesn't present any dangers as long as I'm not writing scripts that try to directly manipulate that variable.

    Second of all I wonder about code efficiency. Oh sure it only might shave a few nanoseconds off the code for most of this, or it might extend the memory requirements by only a couple bytes, but I still want to run my code as efficiently as I can.

    And finally I wonder if there is some other aspect to this that is just coming out of left field that I simply do not know of. Maybe there's a good reason for reasons I didn't think of. Maybe it just makes a good habit to follow because if I was writing a standalone windows program I would have issues with this issue. I can't say I know that stuff.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    What your programmer friends are on about is called 'encapsulation'. The idea is that a properly designed class in an object orient design has all its bits encapsulated into private fields (the variables). And access to them is controlled through public methods.

    This is what is called the 'interface' of your class (not to be confused with the interface type).

    The idea being, you don't allow direct access to anything inside the object, but rather through controlled mechanisms. This way other code can't manipulate the state of your object without going through some method that restricts how the state can be manipulated (a method might validate, or clamp ranges, or other things like that).

    OO purists will beat into each other hands to ALWAYS encapsulate everything. Leaving a field public is considered taboo.

    But really... at the end of the day, it doesn't matter.

    What I mean is that the code isn't really protected... it's not like these 'public/private/protected' keywords actually block access really. Your IDE and compiler just respect those keywords. The chunk of memory for any given field is still technically available to manipulate. Even .net lets you reflect out these fields.

    It's a design choice, just that. And when you're writing a public library to distribute. Or you're working on a team. Or you're just in a very large project that you lose track of what's what. It's great for conveying what should or shouldn't be manipulated.

    Thing is, it takes more effort. You have to mark it private, you have to add the SerializeField attribute, you have write a public getter if you want it readale, and setter if you want it writable. It's all extra work.

    AND yes, it's technically slower to access fields through properties becuase it's a function call, so it's got that overhead.
     
  3. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Everything should be private unless you have a reason for it to be public.

    You should assume any variable or method that is public can be randomly called or changed at any point. That means that only things that are designed to be messed with from the outside should be public.

    In general this is called encapsulation. It's a good thing. Having a clearly defined points where the outside world interacts with your class is a great idea.

    Every time you change with a public member you have the potential to break some other class that is dependent on that member. The less public stuff you have, the less things you can break.

    Encapsulation also can hide the messy underbelly of your class. Think of it like your house. You have a front door people can come in. At the front door you can decide to accept or turn away visitors. You can also control what they access, let the into the living room, but prevent them from pawing through your underwear drawer.
     
  4. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    971
    Are there any special caveats to a variable being public in Unity? I mean, at the very least, this displays the variable in the editor. I suppose it is reasonable that the overhead this would add does not appear in the regular game, just in the editor, but I still wonder.

    Also, is it possible to make a private variable be editable in the editor? Usually when I want a variable to be public it is just so that I can edit the variable for specific instances.
    It seems reasonable to me that I don't want someone setting the "health" variable without going through a hurt or restore call, lest I miss calling the character to die, but I still need to set the character's health in the inspector, because some instances might not start out with full health.
     
  5. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Yes, there's an attribute for that.

    Code (CSharp):
    1. [SerializeField]
    2. private int myVar;
    It will be shown in the inspector and it will also get properly serialized, as the same suggests.
     
    Kiwasi likes this.
  6. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    I agree with all comments, but I just wanted to add that one of the reasons why programmers coming from any other platform or programming experience hesitate to use Unity is because of using public variables for inspector exposure. At the very least, It should be an attribute assigned, but even better would have been a mechanism to add your own methods for inspector exposure.

    LIke @Marscaleb, the only reason I would expose public variables would be for debugging purposes.

    EDIT: Er, I guess I didn't read all the comments well. Am I to understand that using [SerializeField] on a private/protected property will make it act like an inspector variable? If so, I actually didn't know that =/. I just didn't want to mark anything as [SerializeField] unless I absolutely needed it, as in the case of actually serializing objects.
     
  7. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    The inspector is closely tied to the serialisation system. So anything serialised is shown in the inspector. And anything not shown in the inspector is not serialised.
     
  8. Marscaleb

    Marscaleb

    Joined:
    Jan 7, 2014
    Posts:
    971
    I'm trying to encapsulate this code better, and I feel as if I am just being paranoid now.

    I have health and ammo variables, and I made them protected, and other classes can only manipulate them with "HurtPawn" "HealPawn" and "AddAmmo" public functions. But I also need the game manager to be able to set these values outright; if nothing else, when I walk into a new scene, the GameManager needs to set these variables for the player, so their values are the same from the last scene.
    And I think... having a function that just outright sets these values is really no different from having them be public. And then I think that I want to have a method to ensure that these values are only being set when the player spawns.
    But I'm just being paranoid, aren't I? I mean, who is going to set these values? I'm writing all the code here, and if for some reason I had a reason to set the value outright, I'd probably have a reason to set the value outright.

    ...Also, what exactly does that "serializeField" do other than have it show up in the inspector? The only time I ever used serializing was when I made a save file, and then I was just following instructions. I'm reading it in the documentation, and it assumes that you already know what serializing is, and it sounds like it does more than just display stuff in the inspector.
     
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Encapsulation is mainly about protecting things from your future code. It does absolutely nothing against malicious users. It simply instructs the compiler how to deal with your code.

    The key advantages is code that is easier to maintain and refactor. You have to balance this against the increased structural complexity.

    On most projects of any size encapsulation makes sense. But not always.
     
    Suddoha likes this.
  10. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    There is truth here, but it isn't a whole picture. There is a subtle, but important difference between a public field and a method (either a setter or a SetX() method) which sets the value of the field. The public field alone tells me nothing about the intent of the class. If I am in some class and I start typing "player." and intellisense shows me that the Player class has a public health field, but it doesn't help me determine whether or not its safe to just set the value of that class at this point in the program. A setter can contain logic that suggests an order of operations which must be followed. Even better is when those public setting methods are named such that I can follow the logic in a quick and intuitive manner.

    Even though you are the only one writing code, you're actually not the only one writing code. There is going to be a future you, 6 or 9 or 12 or 24 months from now who is going to stumble across that public health field. Will the future you remember that you have to call player.CheckForDeath() every time after you set the value of player.health? If not, that's a bug. But providing only accessor methods, which automate the procedure and (more important) properly convey the intent of the player class, the future you will be less likely to make those mistakes.
     
    Marscaleb, Kiwasi and aer0ace like this.
  11. frekiidoochmanz

    frekiidoochmanz

    Joined:
    Feb 7, 2016
    Posts:
    158
    Not sure if this is wise or intelligent or straight bad coding, but what I do is set the last real value of something I want to stay in a class to private,a nd make a public copy i work with having two sets of everything I need to or choose to do math with such as

    Code (csharp):
    1.  
    2.  
    3. public int MyNumber;
    4.  
    5. private int MyNumberReal;
    6.  
    7.  
    8.  
    9.  
    10.  
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    So this is my opinion... and it comes from the fact my background is more business code related than game code.

    Encapsulation is my go to design choice.

    And a protected field, that's code smell to me. Why is this field protected? Why should this field need to be accessed by a child class?

    IMO a child class is almost like a completely different class. I don't just encapsulate from outside code, I encapsulate from inheriting code as well.

    The point of encapsulation is that the logic and structure of a class internally (the fields and actual code) can be changed at any time without effecting any other code in the project. As long as the publicly facing interface remains exactly the same... I can modify the internals of it freely with little concern that the project will break. As long as the new code works logically, it should work in general.

    Of course, unit test that change.

    Which speaking of... unit testing pretty much means nothing unless you're following a strict design model like encapsulation.

    One of the main points of encapsulation is that you convey intent through the interface of the class. A class isn't supposed to be a set of data (though it has data). It's an object that does things.

    I always liked the VCR -> DVD example.

    I don't care what goes on inside a VCR. I care that there is an input (where I stick my VHS tape), and output (the video cables out the back), and general purpose methods (play, pause, ffw, rwd, etc). Now... if Panasonic makes the internals of their VCR different from Sony, that doesn't matter. I'm not opening it up and mucking about inside.

    I can if I want to, I just need a screwdriver, but to use it how it's designed I just access the publicly facing interface. And it just works. No matter how the guts might change.

    So much so, that when the guts completely changed when DVD came about. The interface remained pretty much identical. The only thing changing was that inside of inserting VHS tapes, I insert DVD disks. But there's still just a media input, a video output, play, pause... hell they even kept ffw and rwd despite them being superfluous with the whole chapters feature added on.

    This is encapsulation.

    You don't just encapsulate willy nilly... you do so with INTENT.

    The methods and properties that are publicly facing aren't exactly direct pass throughs of internals.

    'Play' is not a direct pass through to some specific thingy inside. It's a whole set of commands that varies from VCR to VCR.

    The time into the film isn't a direct reflection of some value inside the VCR neither. Rather it might be interpreted and shown publicly in various ways. One VCR might estimate the time by measuring the inches of real that have spun since the tape was inserted. Another might read a timestamp on the VHS tape itself. Another might have an actual quartz timer recording time. Who knows...

    Same goes in a code class. You might have a property 'Count' of type int. And there's no actual "count" field in the class. Maybe it's just the sum of some other values together that would be the count.

    First, see my previous few paragraphs.

    And who should you be paranoid of?

    Yourself.

    Other people you might have look at, read, or even maintain the code.

    I mean if you plan to just be a hobby coder who never works with anyone else, and never expects anyone to look at your code. None of this really matters at all. Do it however you want... you're doing this as an amateur hobby. I don't golf like the pros, I'm just on the course to play a few holes, drink a beer, and shoot the S*** with some buddies.

    But if you expect to go anywhere in programming. Use it for anything beyond just personal tinkering. Well yeah... learn the standards. Make your code readable! Practice at this stuff.

    Being able to convey the intent of your code through the interface is critical in allowing other developers to be able to make sense of your code.

    Of course... your lay programmers might get confused by it. But if you're shooting for well written code, it's not supposed to be interpreted by people who can't read good code. 5 year olds can't read Hemingway... does that say more about the 5 year old or Hemingway?


    A well designed interface might not exactly be intuitive first go. When devices like VCR's came out in the 70's, people still had to read manuals to learn how to use it.

    But once you had it figured out, it's not that complicated, and you can easily teach a 5 year old how to do it.

    And an especially well designed interface like that of the VCR can grow with time and be reused. I mean hell that interface didn't even start with the VCR, it was used in other devices like cassettes, reel to reel, etc. But that's my point... this was a common interface that once you saw it once... you can use a large selection of products that have similar interfaces.

    That's what readable code is.

    It's following common standards so that other developers when they look at it. They're like "oh, I've seen something like this before... I get how they expect me to use this".



    It marks the field to be serialized by unity.

    This means if you save a prefab with that script on it, or a scene with a GameOBject with that script on it, that value is saved. Allowing the field to be configurable at edit time.

    Unity by default only shows serializable fields in the inspector.

    Of course, writing custom inspectors, or even custom property drawers, can allow displaying non-serialized fields as well.
     
    Last edited: Mar 11, 2016
    eisenpony and Kiwasi like this.
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    Here, I'm going to give you what I consider a good real-world example of encapsulation, it's my own code and I don't mean to sound like I'm gloating or something, it's not that at all. Honestly, the design technically isn't mine... it's a fairly standard design.

    It also uses interfaces, the type, which conveys that 'encapsulation' aspect a bit more. The entire point of it is to define interface. You may have heard of the statement "program to the interface" before... they're not talking about 'interface' the type, the mantra existed long before those sorts of interfaces were ever introduced.

    Anyways...

    The point of this structure is to give 'time' an object identity in Unity. I don't like the way unity makes its time these global properties... ugh.

    So, here I defined a type called 'TimeSupplier':

    Code (csharp):
    1.  
    2. using System.Collections.Generic;
    3.  
    4. namespace com.spacepuppy
    5. {
    6.  
    7.     /// <summary>
    8.     /// Represents an object that supplies the current game time. See com.spacepuppy.SPTime.
    9.     /// </summary>
    10.     public interface ITimeSupplier
    11.     {
    12.  
    13.         float Total { get; }
    14.         float Delta { get; }
    15.         float Scale { get; }
    16.  
    17.         double TotalPrecise { get; }
    18.  
    19.     }
    20.  
    21.     /// <summary>
    22.     /// A ITimeSupplier that has a scale property (this gives things like Time.timeScale an object identity).  See com.spacepuppy.SPTime.
    23.     /// </summary>
    24.     public interface IScalableTimeSupplier : ITimeSupplier
    25.     {
    26.  
    27.         event System.EventHandler TimeScaleChanged;
    28.  
    29.         bool Paused { get; set; }
    30.  
    31.  
    32.         IEnumerable<string> ScaleIds { get; }
    33.  
    34.         void SetScale(string id, float scale);
    35.         float GetScale(string id);
    36.         bool RemoveScale(string id);
    37.         bool HasScale(string id);
    38.  
    39.     }
    40.  
    41. }
    42.  
    43.  
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/ITimeSupplier.cs

    Now there's all sorts of times out there. Unscaled time, regular game time, etc. So I wrote versions of them:

    Code (csharp):
    1.  
    2.         private class NormalTimeSupplier : IScalableTimeSupplier
    3.         {
    4.  
    5.             private bool _paused;
    6.             private Dictionary<string, float> _scales = new Dictionary<string, float>();
    7.  
    8.             public event System.EventHandler TimeScaleChanged;
    9.  
    10.             public float Total
    11.             {
    12.                 get { return UnityEngine.Time.time; }
    13.             }
    14.  
    15.             public double TotalPrecise
    16.             {
    17.                 get { return (double)UnityEngine.Time.time; }
    18.             }
    19.  
    20.             public float Delta
    21.             {
    22.                 get { return UnityEngine.Time.deltaTime; }
    23.             }
    24.  
    25.             public bool Paused
    26.             {
    27.                 get { return _paused; }
    28.                 set
    29.                 {
    30.                     if (_paused == value) return;
    31.                     _paused = value;
    32.                     if (_paused)
    33.                     {
    34.                         UnityEngine.Time.timeScale = 0f;
    35.                     }
    36.                     else
    37.                     {
    38.                         this.SyncTimeScale();
    39.                     }
    40.                 }
    41.             }
    42.  
    43.             public float Scale
    44.             {
    45.                 get
    46.                 {
    47.                     return (_paused) ? this.GetTimeScale() : UnityEngine.Time.timeScale;
    48.                 }
    49.             }
    50.  
    51.             public IEnumerable<string> ScaleIds
    52.             {
    53.                 get { return _scales.Keys; }
    54.             }
    55.  
    56.             public void SetScale(string id, float scale)
    57.             {
    58.                 _scales[id] = scale;
    59.                 if(!_paused)
    60.                 {
    61.                     this.SyncTimeScale();
    62.                 }
    63.             }
    64.  
    65.             public float GetScale(string id)
    66.             {
    67.                 float result;
    68.                 if(_scales.TryGetValue(id, out result))
    69.                 {
    70.                     return result;
    71.                 }
    72.                 else
    73.                 {
    74.                     return float.NaN;
    75.                 }
    76.             }
    77.  
    78.             public bool RemoveScale(string id)
    79.             {
    80.                 if (_scales.Remove(id))
    81.                 {
    82.                     if (!_paused)
    83.                     {
    84.                         this.SyncTimeScale();
    85.                     }
    86.                     return true;
    87.                 }
    88.                 else
    89.                 {
    90.                     return false;
    91.                 }
    92.             }
    93.  
    94.             public bool HasScale(string id)
    95.             {
    96.                 return _scales.ContainsKey(id);
    97.             }
    98.  
    99.             private void SyncTimeScale()
    100.             {
    101.                 float result = this.GetTimeScale();
    102.              
    103.                 if (MathUtil.FuzzyEqual(result, UnityEngine.Time.timeScale))
    104.                 {
    105.                     UnityEngine.Time.timeScale = result;
    106.                 }
    107.                 else
    108.                 {
    109.                     UnityEngine.Time.timeScale = result;
    110.                     if (this.TimeScaleChanged != null) this.TimeScaleChanged(this, System.EventArgs.Empty);
    111.                 }
    112.  
    113.                 if(Mathf.Approximately(result, UnityEngine.Time.timeScale))
    114.                 {
    115.                     UnityEngine.Time.timeScale = result;
    116.                 }
    117.                 else
    118.                 {
    119.                     UnityEngine.Time.timeScale = result;
    120.                     if (this.TimeScaleChanged != null) this.TimeScaleChanged(this, System.EventArgs.Empty);
    121.                 }
    122.             }
    123.  
    124.             private float GetTimeScale()
    125.             {
    126.                 float result = 1f;
    127.                 if (_scales.Count > 0)
    128.                 {
    129.                     var e = _scales.GetEnumerator();
    130.                     while (e.MoveNext())
    131.                     {
    132.                         result *= e.Current.Value;
    133.                     }
    134.                 }
    135.                 return result;
    136.             }
    137.  
    138.         }
    139.  
    Code (csharp):
    1.  
    2.         private class RealTimeSupplier : ITimeSupplier
    3.         {
    4.  
    5.             public float Total
    6.             {
    7.                 get { return UnityEngine.Time.unscaledTime; }
    8.             }
    9.  
    10.             public double TotalPrecise
    11.             {
    12.                 get { return (double)UnityEngine.Time.unscaledTime; }
    13.             }
    14.  
    15.             public float Delta
    16.             {
    17.                 get { return UnityEngine.Time.unscaledDeltaTime; }
    18.             }
    19.  
    20.             public float Scale
    21.             {
    22.                 get
    23.                 {
    24.                     return 1f;
    25.                 }
    26.             }
    27.  
    28.         }
    29.  
    Code (csharp):
    1.  
    2.         private class SmoothTimeSupplier : IScalableTimeSupplier
    3.         {
    4.  
    5.             public float Total
    6.             {
    7.                 get { return UnityEngine.Time.time; }
    8.             }
    9.  
    10.             public double TotalPrecise
    11.             {
    12.                 get { return (double)UnityEngine.Time.time; }
    13.             }
    14.  
    15.             public float Delta
    16.             {
    17.                 get { return UnityEngine.Time.smoothDeltaTime; }
    18.             }
    19.  
    20.             public float Scale
    21.             {
    22.                 get
    23.                 {
    24.                     return SPTime.Normal.Scale;
    25.                 }
    26.             }
    27.  
    28.             bool IScalableTimeSupplier.Paused
    29.             {
    30.                 get
    31.                 {
    32.                     return SPTime.Normal.Paused;
    33.                 }
    34.                 set
    35.                 {
    36.                     throw new System.NotSupportedException();
    37.                 }
    38.             }
    39.  
    40.             IEnumerable<string> IScalableTimeSupplier.ScaleIds
    41.             {
    42.                 get
    43.                 {
    44.                     return SPTime.Normal.ScaleIds;
    45.                 }
    46.             }
    47.  
    48.             event EventHandler IScalableTimeSupplier.TimeScaleChanged
    49.             {
    50.                 add
    51.                 {
    52.                     SPTime.Normal.TimeScaleChanged += value;
    53.                 }
    54.  
    55.                 remove
    56.                 {
    57.                     SPTime.Normal.TimeScaleChanged -= value;
    58.                 }
    59.             }
    60.  
    61.             void IScalableTimeSupplier.SetScale(string id, float scale)
    62.             {
    63.                 throw new System.NotSupportedException();
    64.             }
    65.  
    66.             float IScalableTimeSupplier.GetScale(string id)
    67.             {
    68.                 return SPTime.Normal.GetScale(id);
    69.             }
    70.  
    71.             bool IScalableTimeSupplier.RemoveScale(string id)
    72.             {
    73.                 throw new System.NotSupportedException();
    74.             }
    75.  
    76.             bool IScalableTimeSupplier.HasScale(string id)
    77.             {
    78.                 return SPTime.Normal.HasScale(id);
    79.             }
    80.         }
    81.  
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/SPTime.cs

    I also set up a 'CustomTimeSupplier' so you can define custom times independent of the standard unity time:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4. using System.Collections.Generic;
    5.  
    6. namespace com.spacepuppy
    7. {
    8.  
    9.     /// <summary>
    10.     /// A TimeSupplier that allows for individual scaling. You may have to scale the world time to 0 for pause
    11.     /// or other various effects, but you still want time to tick normally for other aspects like the menu or
    12.     /// something. BUT, lets say you want to be able to scale that time as well for various effects, independent
    13.     /// of the world time. By using a custom TimeSupplier you can scale that time independent of the world time.
    14.     ///
    15.     /// Furthermore you can stack time scales, just like described in com.spacepuppy.SPTime.
    16.     ///
    17.     /// Allows for approximately 29,247 years of simulation, twice that if you include negative time, while
    18.     /// maintaining at minimum 3 digits of fractional precision for seconds (millisecond precision) when at the
    19.     /// extents of its range.
    20.     /// </summary>
    21.     public class CustomTimeSupplier : IScalableTimeSupplier, System.IDisposable
    22.     {
    23.  
    24.         private const long SECONDS_TO_TICKS = 10000000L;
    25.         private const double TICKS_TO_SECONDS = 1E-07d;
    26.  
    27.         private static long GetTicksSafe(double value)
    28.         {
    29.             if (double.IsNaN(value))
    30.                 return 0;
    31.  
    32.             value *= (double)SECONDS_TO_TICKS;
    33.             if (value <= (double)long.MinValue)
    34.                 return long.MinValue;
    35.             else if (value >= (double)long.MaxValue)
    36.                 return long.MaxValue;
    37.             else
    38.             {
    39.                 try
    40.                 {
    41.                     return (long)value;
    42.                 }
    43.                 catch
    44.                 {
    45.                     return 0;
    46.                 }
    47.             }
    48.         }
    49.  
    50.         #region Fields
    51.  
    52.         private string _id;
    53.         private long _startTime;
    54.         private long _t;
    55.         private long _ft;
    56.         private double _scale = 1.0;
    57.         private bool _paused;
    58.         private Dictionary<string, double> _scales = new Dictionary<string, double>();
    59.  
    60.         #endregion
    61.  
    62.         #region CONSTRUCTOR
    63.  
    64.         internal CustomTimeSupplier(string id)
    65.         {
    66.             _id = id;
    67.         }
    68.  
    69.         #endregion
    70.  
    71.         #region Properties
    72.  
    73.         public string Id { get { return _id; } }
    74.  
    75.         public bool Valid { get { return _id != null; } }
    76.  
    77.         public double StartTime
    78.         {
    79.             get { return _startTime * TICKS_TO_SECONDS; }
    80.             set
    81.             {
    82.                 _startTime = GetTicksSafe(value);
    83.             }
    84.         }
    85.  
    86.         public TimeSpan TotalSpan
    87.         {
    88.             get
    89.             {
    90.                 if (GameLoopEntry.CurrentSequence == UpdateSequence.FixedUpdate)
    91.                 {
    92.                     return new TimeSpan(_ft + _startTime);
    93.                 }
    94.                 else
    95.                 {
    96.                     return new TimeSpan(_t + _startTime);
    97.                 }
    98.             }
    99.         }
    100.  
    101.  
    102.  
    103.  
    104.         /// <summary>
    105.         /// The total time passed since the CustomTime was created. Value is relative to the Update sequence.
    106.         /// </summary>
    107.         public double UpdateTotal { get { return (_t + _startTime) * TICKS_TO_SECONDS; } }
    108.  
    109.         public TimeSpan UpdateTotalSpan { get { return new TimeSpan(_t + _startTime); } }
    110.  
    111.         /// <summary>
    112.         /// The delta time since the call to standard update. This will always return the delta since last update, regardless of if you call it in update/fixedupdate.
    113.         /// </summary>
    114.         public double UpdateDelta { get { return Time.unscaledDeltaTime * _scale; } }
    115.  
    116.  
    117.  
    118.  
    119.         /// <summary>
    120.         /// The total time passed since the CustomTime was created. Value is relative to the FixedUpdate sequence;
    121.         /// </summary>
    122.         public double FixedTotal { get { return (_ft + _startTime) * TICKS_TO_SECONDS; } }
    123.      
    124.         public TimeSpan FixedTotalSpan { get { return new TimeSpan(_ft + _startTime); } }
    125.  
    126.         /// <summary>
    127.         /// The delta time since the call to fixed update. This will always return the delta since last fixedupdate, regardless of if you call it in update/fixedupdate.
    128.         /// </summary>
    129.         public double FixedDelta { get { return Time.fixedDeltaTime * _scale; } }
    130.  
    131.         #endregion
    132.  
    133.         #region Methods
    134.  
    135.         internal void Update(bool isFixed)
    136.         {
    137.             if (_paused) return;
    138.  
    139.             if (isFixed)
    140.             {
    141.                 _ft += (long)(Time.fixedDeltaTime * _scale * SECONDS_TO_TICKS);
    142.             }
    143.             else
    144.             {
    145.                 _t += (long)(Time.unscaledDeltaTime * _scale * SECONDS_TO_TICKS);
    146.             }
    147.         }
    148.  
    149.         public bool Destroy()
    150.         {
    151.             if( SPTime.RemoveCustomTime(this))
    152.             {
    153.                 _id = null;
    154.                 return true;
    155.             }
    156.             else
    157.             {
    158.                 return false;
    159.             }
    160.         }
    161.  
    162.         private void SyncTimeScale()
    163.         {
    164.             double result = 1d;
    165.             if (_scales.Count > 0)
    166.             {
    167.                 var e = _scales.GetEnumerator();
    168.                 while (e.MoveNext())
    169.                 {
    170.                     result *= e.Current.Value;
    171.                 }
    172.             }
    173.             if(System.Math.Abs(result - _scale) > 0.0000001d)
    174.             {
    175.                 _scale = result;
    176.                 if (this.TimeScaleChanged != null) this.TimeScaleChanged(this, System.EventArgs.Empty);
    177.             }
    178.             else
    179.             {
    180.                 _scale = result;
    181.             }
    182.         }
    183.  
    184.         public void Reset()
    185.         {
    186.             _startTime = 0;
    187.             _t = 0;
    188.             _ft = 0;
    189.         }
    190.  
    191.         public void Reset(double startTime)
    192.         {
    193.             _startTime = GetTicksSafe(startTime);
    194.             _t = 0;
    195.             _ft = 0;
    196.         }
    197.  
    198.  
    199.         /// <summary>
    200.         /// Adjust the current 'TotalTime' by some amount.
    201.         /// WARNING - delta is not effected
    202.         /// WARNING - time based event systems might be adversely impacted
    203.         /// Especially if the value is negative.
    204.         /// USE AT OWN RISK!
    205.         /// </summary>
    206.         /// <param name="value"></param>
    207.         public void AdjustTime(double value)
    208.         {
    209.             _startTime += GetTicksSafe(value);
    210.         }
    211.  
    212.         #endregion
    213.  
    214.         #region ITimeSupplier Interface
    215.  
    216.         public event System.EventHandler TimeScaleChanged;
    217.  
    218.         /// <summary>
    219.         /// The total time passed since thie CustomTime was created. Value is dependent on the UpdateSequence being accessed from.
    220.         /// </summary>
    221.         public float Total
    222.         {
    223.             get
    224.             {
    225.                 if(GameLoopEntry.CurrentSequence == UpdateSequence.FixedUpdate)
    226.                 {
    227.                     return (float)((_ft + _startTime) * TICKS_TO_SECONDS);
    228.                 }
    229.                 else
    230.                 {
    231.                     return (float)((_t + _startTime) * TICKS_TO_SECONDS);
    232.                 }
    233.             }
    234.         }
    235.  
    236.         public double TotalPrecise
    237.         {
    238.             get
    239.             {
    240.                 if (GameLoopEntry.CurrentSequence == UpdateSequence.FixedUpdate)
    241.                 {
    242.                     return (_ft + _startTime) * TICKS_TO_SECONDS;
    243.                 }
    244.                 else
    245.                 {
    246.                     return (_t + _startTime) * TICKS_TO_SECONDS;
    247.                 }
    248.             }
    249.         }
    250.  
    251.         /// <summary>
    252.         /// The delta time since the last call to update/fixedupdate, relative to in which update/fixedupdate you call.
    253.         /// </summary>
    254.         public float Delta
    255.         {
    256.             get
    257.             {
    258.                 if (_paused)
    259.                     return 0f;
    260.                 else
    261.                     return (GameLoopEntry.CurrentSequence == UpdateSequence.FixedUpdate) ? (float)(Time.fixedDeltaTime * _scale) : (float)(Time.unscaledDeltaTime * _scale);
    262.             }
    263.         }
    264.  
    265.         public bool Paused
    266.         {
    267.             get { return _paused; }
    268.             set
    269.             {
    270.                 if (_paused == value) return;
    271.                 _paused = value;
    272.                 if (this.TimeScaleChanged != null) this.TimeScaleChanged(this, System.EventArgs.Empty);
    273.             }
    274.         }
    275.  
    276.         public float Scale
    277.         {
    278.             get { return (float)_scale; }
    279.         }
    280.  
    281.         public IEnumerable<string> ScaleIds
    282.         {
    283.             get { return _scales.Keys; }
    284.         }
    285.  
    286.         public void SetScale(string id, float scale)
    287.         {
    288.             _scales[id] = (double)scale;
    289.             this.SyncTimeScale();
    290.         }
    291.  
    292.         public float GetScale(string id)
    293.         {
    294.             double result;
    295.             if (_scales.TryGetValue(id, out result))
    296.             {
    297.                 return (float)result;
    298.             }
    299.             else
    300.             {
    301.                 return float.NaN;
    302.             }
    303.         }
    304.  
    305.         public bool RemoveScale(string id)
    306.         {
    307.             if (_scales.Remove(id))
    308.             {
    309.                 this.SyncTimeScale();
    310.                 return true;
    311.             }
    312.             else
    313.             {
    314.                 return false;
    315.             }
    316.         }
    317.  
    318.         public bool HasScale(string id)
    319.         {
    320.             return _scales.ContainsKey(id);
    321.         }
    322.  
    323.         #endregion
    324.  
    325.         #region IDisposable Interface
    326.  
    327.         void System.IDisposable.Dispose()
    328.         {
    329.             this.Destroy();
    330.         }
    331.  
    332.         #endregion
    333.  
    334.     }
    335.  
    336. }
    337.  
    https://github.com/lordofduct/space...b/master/SpacepuppyBase/CustomTimeSupplier.cs

    Note how each one has this public interface that directs to completely different values internally. Not just which global value it passes back.

    But it might not even implement them the same.

    Like the way NormalTimeSupplier implements 'scale' is way different from how CustomTimeSupplier does, just because of the way those scales technically work.

    And if you look at the history of CustomTimeSupplier, you can see that technically I changed the internal implementation recently:
    https://github.com/lordofduct/space...704a3598/SpacepuppyBase/CustomTimeSupplier.cs

    The public interface stays pretty much the same (I added some new props, but overall the existing interface remains the same). But internally I changed from using a double to a long. I found the long to give a much better resolution and precision. Sure it reduced my range, but when double reached outside of the range of long, it's moment to moment precision would suffer.

    So technically double could only gives me a range of 2^53 while maintaining precise moment to moment values. Where as long would give me a range of 2^64 with precise moment to moment.





    Now of course this is very specific design here.

    And you might say "well this doesn't really explain why I should ALWAYS encapsulate."

    And no, it doesn't.

    Becuase honestly... there is no good reason to do it ALWAYS except for maintaining a consistent standard in your code that is common among a large portion of OO programmers.
     
    Kiwasi likes this.
  14. frekiidoochmanz

    frekiidoochmanz

    Joined:
    Feb 7, 2016
    Posts:
    158
    Are you setting up a base um, function? which you derive other functions from and then tell each one what to { get }

    Code (csharp):
    1.  
    2.  private class SmoothTimeSupplier : IScalableTimeSupplier
    3.         {
    4.  
    5.             public float Total
    6.             {
    7.                 get { return UnityEngine.Time.time; }
    8.             }
    9. [...]
    10.  
    is the public interface an actual class inside of another class? not a function?

    this makes more sense.
     
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,376
    I'm not sure what you're asking.
     
    aer0ace likes this.