Search Unity

Better way of storing changing values?

Discussion in 'Scripting' started by kalisclark, Mar 25, 2015.

  1. kalisclark

    kalisclark

    Joined:
    Sep 29, 2014
    Posts:
    33
    Hi folks,

    I am optimising my project and it seems that I am using the Update() to update references to other scripts.

    Is there a better way of doing this?

    Please note that the objects are being instantiated so I need to check to see if the object is instantiated and if it is, then I store the value and keep checking back to make sure that my stored value is the same as the script value....if that makes any sense?

    I currently use the following:

    Code (CSharp):
    1. void Update()
    2. {
    3. playerHealth= GameObject.Find("MyPlayer").GetComponent<Health>();
    4. }
    I hope someone can pass some wisdom for a better way of achieving this.

    Many thanks
     
  2. proandrius

    proandrius

    Unity Technologies

    Joined:
    Dec 4, 2012
    Posts:
    544
    Hi, don't use GameObject.Find in Update and try not using GetComponent as well but especially GameObject.Find. The correct way to do is like this:
    Code (csharp):
    1. public Health myHealth;
    2.  
    3. void Start(){
    4.     // Get component only once at start
    5.     myHealth = GameObject.Find("MyPlayer").GetComponent<Health>();
    6. }
    7.  
    8. void Update(){
    9.     playerHealth = myHealth.SomeVariableThatHasHealth;
    10. }
    11.  
     
  3. Defero

    Defero

    Joined:
    Jul 9, 2012
    Posts:
    200
    Hi,

    If your script (the one you writing here) is created before the "MyPlayer" gameObject, than you can reverse the process.

    Write the GameObject.Find (you are looking for your script now) code on the MyPlayer gameobject. And save MyPlayer into your current script.
     
  4. proandrius

    proandrius

    Unity Technologies

    Joined:
    Dec 4, 2012
    Posts:
    544
    Well if you are not sure that "Player exists" yet, you can do this way:

    Code (csharp):
    1.  
    2. public Health myHealth;
    3.  
    4. public void Update(){
    5.     if(myHealth){
    6.         playerHealth = myHealth.SomeVariableThatHasHealth;
    7.     } else {
    8.         myHealth = GameObject.Find("MyPlayer").GetComponent<Health>();
    9.     }
    10. }
    So in this case it will always try to get health but if player or health does not exist yet it will try to find it.
     
    Timelog likes this.
  5. kalisclark

    kalisclark

    Joined:
    Sep 29, 2014
    Posts:
    33
    Hi Proandrius,

    From your last post you said do NOT use GameObject.find and here you have the same thing that I currently have.(GameObject.find in the Update).

    Deferno
    This looks like a good way, could you explain a bit? Are you saying, run the (GameObject.Find) in the Start function of the health script on MyPlayer to find the Object that isnt instantiated?

    Just trying to wrap my brain around this guys, Thank you for your help
     
  6. proandrius

    proandrius

    Unity Technologies

    Joined:
    Dec 4, 2012
    Posts:
    544
    There is difference when using GameObject.Find every update and using only once. In my script it will only use GameObject.Find while player does not exist and when it exist it will get the reference and only use reference.
     
  7. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    One of my favorite parts of the C# language is Accessors. The reference for their use and construction is here: https://msdn.microsoft.com/en-us/library/aa287786(v=vs.71).aspx

    A typical pattern I would use in your case, like said above, if you are not sure you can set it in the Start() method, is to define myHealth like this:
    Code (CSharp):
    1. public Health myHealth
    2. {
    3.     get
    4.     {
    5.         if (myHealth == null)
    6.         {
    7.             myHealth = lookupthehealthstuff;
    8.         }
    9.         return myHealth;
    10.     }
    11.     set
    12.     {
    13.         myHealth = value;
    14.     }
    15. }
    Shorthand you can also replace the set{} section with simply "set;" since you're not doing anything other than setting the variable.

    That way it'll find it when it needs to, but only once. This is lazy instantiation, which can mean you have excess loading while you're playing the game instead of waiting to load and run smoothly. So just be smart about how and where you use it. For small variables like this though, I doubt it'd really matter. Read up on accessors - they're wonderfully useful.
     
  8. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    Last edited: Mar 25, 2015
  9. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    Not quite, but really close. You don't want to use the property for storing the actual object, and it will likely result in bugs/exceptions. This is pretty much correct though. You just need to create a separate variable that the property works with. For exmaple.

    Code (CSharp):
    1. class Foo{
    2.     Attributes Attrs;
    3.  
    4.     public int Health {
    5.         get {
    6.             if (Attrs == null) {
    7.                 Attrs = new Attributes ();
    8.                 // In this example this is zero, but you ge the idea...
    9.                 return Attrs.Health;
    10.             } else {
    11.                 return Attrs.Health;
    12.             }
    13.         }
    14.         set {
    15.             Attrs.Health = value;
    16.         }
    17.     }
    18. }
    19.  
    20. class Attributes{
    21.     public int Health;
    22. }
    EDIT: If you're using a class or struct JUST to manage health, I'd think you might be over complicating it. I don't know your game though so I could be wrong. Just using a property for health might be better. In my example though I have an attributes class, and then a separate property to access individual members of said class, which might be something you're interested in. Especially if the Attributes class is serializeable. That would make for saving and loading your game a lot easier.
     
  10. kalisclark

    kalisclark

    Joined:
    Sep 29, 2014
    Posts:
    33
    Ok Proandrius I see it now, like this?

    Code (CSharp):
    1. void Start()
    2. {
    3. myHealth = GameObject.Find("MyPlayer").GetComponent<Health>();
    4. }
    5.  
    6. void Update()
    7. {
    8. if(MyPlayer !=Null)
    9. {
    10. playerHealth = MyHealth.currentHealth;
    11. }
    12. else
    13. {
    14. myHealth = GameObject.Find("MyPlayer").GetComponent<Health>();
    15.  
    16. }
    17. }
    Would this not throw an exception in the Start function?
     
  11. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    Ah, I'm new to Unity myself. Unity use previous to C# 3.0? If so then yeah.
     
  12. proandrius

    proandrius

    Unity Technologies

    Joined:
    Dec 4, 2012
    Posts:
    544
    If you are checking for player and not component you will need to do this:
    Code (csharp):
    1. public GameObject myPlayer;
    2. public Health myHealth;
    3.  
    4. void Start(){
    5.     myPlayer = GameObject.Find("MyPlayer");
    6.    
    7.     if(myPlayer){
    8.         myHealth = myPlayer.GetComponent<Health>();
    9.     }
    10. }
    11.  
    12. void Update(){
    13.     if(myPlayer&&myHealth){
    14.         playerHealth = myHealth.currentHealth;
    15.     } else {
    16.         myPlayer = GameObject.Find("MyPlayer");
    17.        
    18.         if(myPlayer){
    19.             myHealth = myPlayer.GetComponent<Health>();
    20.         }
    21.     }
    22. }
    With this solution this is the safest you can get, since you check for player GameObject and the Component as well. Basically it will try to find player and assign component while it does no exist when it finds it it will assign once and then will not use GameObject.Find and GetComponent anymore.
     
  13. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    I'm not actually familiar with the versions. So if this is a legacy problem then I could be completely wrong. Thanks for mentioning something. I'll look into that.
     
  14. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    C'mon son! :p Let's at least follow some good practices in our examples! (I tease, but seriously though)

    Code (csharp):
    1. public GameObject myPlayer;
    2. public Health myHealth;
    3.  
    4. void Start(){
    5.     AssignHealth()
    6. }
    7.  
    8. void Update(){
    9.     if(myPlayer&&myHealth){
    10.         playerHealth = myHealth.currentHealth;
    11.     } else {
    12.         AssignHealth();
    13.     }
    14.   }
    15.  
    16. void AssignHealth(){
    17.     myPlayer = GameObject.Find("MyPlayer");
    18.  
    19.     if(myPlayer){
    20.         myHealth = myPlayer.GetComponent<Health>();
    21.     }
    22.   }
    23. }
    I have always believed it's a good idea to show good practices, even when just showing an example. New programmers eat examples up and often replicate it exactly as written. That in itself isn't horrible, but the habits that go along with it are bad. In this case, code duplication is an issue. I'm not not trying to call you out either. Just making sure all the bases are covered. :D

    PS. If it's too much work to write a thorough example, at least add comments saying, "Don't do it exactly like this... this is just to make a point". Or something like that.

    Ok I'm done now... sorry. :p *slips quietly away*
     
  15. proandrius

    proandrius

    Unity Technologies

    Joined:
    Dec 4, 2012
    Posts:
    544
    Haha I agree, I just wanted to give an idea tho. Since I'm on iPad, I'm trying to write less complicated code right in the forum so I can compile it in my head. :)
     
  16. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    Lol. The ol' classic "I'm on a touchscreen device" excuse. Fair enough. ;)
     
  17. kalisclark

    kalisclark

    Joined:
    Sep 29, 2014
    Posts:
    33
    This is great you guys thank you,

    Very informative stuff:

    Jeddak: I will look into those links, the accessors could prove useful for other parts of my code, thank you.
    Proandrius: Thank you for the explanation it has really helped.
    jtsmith1287: Thank you for clarification and even more in depth explanation.

    I like how this is now calling a function but only checking to see if the object is available in the update and then calling the function outwith the update!

    Really great help, Thanks you guys!
     
    Last edited: Mar 25, 2015
  18. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    I did a bit of research myself - I don't see much info as to what Unity is using under the hood. I found one post that said that with C# 3.0 and after, C# actually creates the underlying variable yourself and you don't need to worry about it. I also found a post last year where someone mentioned that Unity 4 used Mono 2.6, which adds some features from C# 3.0...so idk, lol. I use C# 40hr/wk at work and I've never created an underlying private variable, but I started using C# around 3yrs ago and that could be after that update. I'm new to Unity.

    kalisclark - I'd say go ahead and use jtsmith1287's correction to my accessor example with the private underlying variable unless we can figure out for sure that it's not a problem. That way, at the very least, your stuff will work :). And it's pretty easy to go through and remove those if it ends up not being needed anymore.

    Update: I see Unity doing it both ways here: https://unity3d.com/learn/tutorials/modules/intermediate/scripting/properties.
    Code (CSharp):
    1. //This is an example of an auto-implemented property
    2. public int Health{ get; set;}}
    So looks like it's safe to do any of the above. I'd suggest using my original version because it's concise, and you don't have to keep track of two variables for each desired variable:
    Code (CSharp):
    1. public Health myHealth
    2. {
    3.     get
    4.     {
    5.         if (myHealth == null)
    6.         {
    7.             myHealth = lookupthehealthstuff;
    8.         }
    9.         return myHealth;
    10.     }
    11.     set;
    12. }
    Unless you have a very specific reason to define a seperate variable underneath.
     
    Last edited: Mar 25, 2015
    jtsmith1287 likes this.
  19. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    I'm actually excited to have learned this. I have always added the private member. Woot!
     
  20. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    Haha, enjoy :D
     
  21. blizzy

    blizzy

    Joined:
    Apr 27, 2014
    Posts:
    775
    This results in a stack overflow? At least it does for me on Linux:

    Code (CSharp):
    1. $ mono Test.exe
    2. Stack overflow: IP: 0x4105ddab, fault addr: (nil)
    3. Stacktrace:
    4.   at Test2.get_foo () <0x0000f>
    5.   <...>
    6.   at Test..ctor () <0x00023>
    7.   at Test.Main (string[]) <0x0001f>
    8.   at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <0xffffffff>
    9.  
    10. $ mono --version
    11. Mono JIT compiler version 3.0.6 (tarball Sat Sep 28 04:42:52 UTC 2013)
    12. Copyright (C) 2002-2012 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
    13.         TLS:           __thread
    14.         SIGSEGV:       altstack
    15.         Notifications: epoll
    16.         Architecture:  amd64
    17.         Disabled:      none
    18.         Misc:          softdebug
    19.         LLVM:          supported, not enabled.
    20.         GC:            Included Boehm (with typed GC and Parallel Mark)
    21.  
     
  22. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    Huh...shouldn't. I'll give it a shot myself when I get a chance tonight.
     
  23. blizzy

    blizzy

    Joined:
    Apr 27, 2014
    Posts:
    775
    Please report back :p
     
  24. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    It's the mono framework. Linux doesn't have the most recent .net or whatever. That's actually what I tested this on as well to make sure I knew what I was saying and I got the stack overflow. Basically it's a recursive reference. This apparently isn't an issue in the most recent build of Unity.

    That being said, it's not horrible to maintain separate private members. Some may find it more readable. It's not really using any extra memory.
     
  25. Kogar

    Kogar

    Joined:
    Jun 27, 2013
    Posts:
    80
    I would also recommend properties which Jeddak proposed already. Then you don't need to refresh the variable in update()

    Just a question. Why do you need to have two health variables? One in MyPlayer and one copy in your current script with playerHealth? Is the script with playerHealth your GUI?

    If you instantiate MyPlayer and that should refresh your script you can also do it the other way around. Maybe even working with delegates. Than you can register your player on instantiation to playerHealth.
    Simpler version would be to just switch your variable setting. So in your myPlayer you write something like that.
    Code (csharp):
    1. void OnEnable()
    2. {
    3.   GameObject.Find("GUI?").playerHealth = GetComponent<Health>();
    4. }
    If you want to learn about delegates i can recommend this site http://unitydojo.blogspot.com/2015/03/how-to-use-delegates-in-unity-like-boss.html#more. The site shows how to use events and delegates which only update stuff when something changes.
     
  26. Palimon

    Palimon

    Joined:
    Apr 18, 2013
    Posts:
    225
    Looks like jtsmith1287 got to it before I did. Thanks!
     
  27. kalisclark

    kalisclark

    Joined:
    Sep 29, 2014
    Posts:
    33
    Jeddak thank you for looking into this further. I have implemented it and it works like a dream and it has set that scripts Garbage Collection to zero when before it was up at 2 mb.

    Kogar, I like Delegates! that will be useful for other items I am using, thank you. Player health twice? no I was trying to get it from an instantiated object.

    Anyway,

    Thanks you guys, an overwhelming response and some fantastic solutions here for anyone looking to optimise.