Search Unity

FindGameobject or keep References in memory? Best Practice?

Discussion in 'Scripting' started by Unlimited_Energy, Aug 11, 2019.

  1. Unlimited_Energy

    Unlimited_Energy

    Joined:
    Jul 10, 2014
    Posts:
    469
    I'm conflicted on when to use find game object vs keeping things in memory as references when needed. I feel like its not as cut and dry as it seems to be and I need help from you guys.

    example, I have 20 status bars representing achievement progress under the achievement menu. do I keep all 20 references in memory or should I do a method that takes the object name as a parameter and finds the game object and accesses the progress bar and updates it when the user does something to increase the progress bar value for that achievement.

    this really goes for anything tho when it comes to when or should I ever use find game object vs keeping things in memory. I cant seem to find a discussion that compares things like, is it better for a method with a string parameter to look for an object when needed as opposed to keeping tens of object references in memory?

    Obviously finding a game object reference would be a bad idea if we are using it in update or lets say, something that happens a lot, for example killing many enemy's and trying to find game object of hit enemy and accessing its health wouldn't make sense, But is it logical to use methods to look up single gameobject references for things that are called periodically rather than keep X amount of gameobject references in memory?

    I hear the argument that if its called more than one time then we should cache the reference, but is that really logical when you could cut down on MANY references in memory by doing a single object find for a reference? Im assuming looking for many references to use in a function would possibly be a bad idea, but I cannot find info on single references look up vs keeping in memory. What would you guys do?

    Thanks for any help.
     
  2. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    I've personally never used GameObject.Find in any situation outside of extremely quick-and-dirty testing. I hardly ever use GetComponent/GetComponentInParent/Children either, because absolutely all of my objects are constructed from prefabs, and know the information they're supposed to know right off, or are fed that information in initialization functions when they're created. If you're having to iterate through all scene objects to find a specific one that matches some specific criteria, I'd argue that you're doing it wrong, and your communications protocol / way of managing cross-cutting concerns in the application needs some work.

    In my case, I treat all GameObjects as consumers of data, not managers of it. They're just visual representations, which come and go. Once you get into object pooling, you start to treat each individual object as completely interchangeable with a dozen others at any time. There's not a single piece of non-trivial non-transient information contained in a single GameObject or Component in the scene- all systems are managed outside of the scene context, by non-Unity objects (as services, at the topmost level), and all data that needs to be kept and serialized, saved and loaded, or which affects the game in any significant way is actually stored within a service and not managed by any scene objects.

    Take an enemy moving around- its current stats don't belong to the visual representation of that enemy, because that could be enabled, disabled, created and destroyed at any time, for any reason (for instance, loading a quick cutscene), but needs to be able to be recreated in a moment's notice in order to keep scene consistency from pre- and post-cutscene. The GameObject isn't the owner of the data, just the consumer of it- a service for enemies manages it instead. This means that saving and loading that data is trivial, because the scene representations are not important. All of the data is in one place already.

    For things like UI displays, again, the element on the screen is only a consumer of data- it's created and immediately tied into an event that exists on a service which handles achievements and achievement progress. The system it's tied into tracks conditions being fulfilled and achievement/quest statuses being updated, the event for "status of this achievement has changed" is fired when needed, and so the visual representation updates how it looks. Complete separation of concerns from the visual and the logical.

    That's just me though, and I'm sure others have different takes on it. =)
     
  3. Unlimited_Energy

    Unlimited_Energy

    Joined:
    Jul 10, 2014
    Posts:
    469
    I see what your saying but question. if I had a prefab that when instantiated had 40 references (20 image.fill references and 20 text object references) would it not be more performant to use findobject for a method needs that reference inside a class on that prefab and save 40 references from being initialized every time that prefab is created than using find object on 2 objects each time the method is called?
     
  4. Unlimited_Energy

    Unlimited_Energy

    Joined:
    Jul 10, 2014
    Posts:
    469
    I think I see what your saying now. Instead of attaching all the scripts to my "visual Gameobjects" in my scene, I should create prefabs of empty game objects that contain methods and fields and references that manipulate or visualy change the "physical scene gameobjects" so that these prefab "manipulators/managers" can be destroyed and created on the fly when needed without them being attached to the game objects..?

    I have always seen in unity tutorials where they put the script they write on the game object that is being affected by the script, but your saying there is a different way where you remove those 2 from each other and create manager/manipulator prefabs that can be created or destroyed. Makes a lot of sense if thats what you are talking about.
     
  5. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Not at all- references are extremely cheap, and you're still having to get those references anyways, but instead of only doing it once by having it injected into the properties when the MonoBehaviours are initialized (the way dragging and dropping in the inspector works), you're iterating through every object in the scene to find them every time you need them. It's not really saving any memory, and you're consuming quite a bit of processing power. It's like saying that in order to save space in your fridge, you drive to the grocery store and buy 1 meal worth of food at a time. What's the fridge even for then, and how much gas are you wasting?

    No, I'm saying I don't use GameObjects for much of anything at all. 99% of my game systems would be usable even if I made a game entirely text-based and didn't use scenes, GameObjects, or MonoBehaviours at all. The system (read: service) that handles dialogue is not made up of GameObjects or Component/MonoBehaviours, it's a completely ordinary C# class. The system that handles achievements, the one that handles quests, the game's primary state machine, character stats, AI controllers, etc... None of them are GameObjects/MonoBehaviours or exist within the scope of a scene.

    Now, that isn't to say they don't have visual representations in the scene though. In reality, they load up configuration files right at the start of the game, the configuration files have all of the various prefabs they can spawn in the scene as needed (like the achievement popups, quest menus, etc), but let's say for a second for simplicity that I actually started with those things created and active in the scene like you probably do. In that case, the achievement menu would, in Start probably, reach out and get a reference to the achievement service (which to reiterate, it not a MonoBehaviour at all), and register to be alerted when an achievement was successfully completed. The below is a quick and dirty example.
    Code (csharp):
    1. [SerializeField] private PopupGraphic _graphic;
    2.  
    3. private void Start()
    4. {
    5.     // the achievement service is what actually tracks achievements
    6.     var achievementService = ServiceLocator<IAchievementService>();
    7.     achievementService.OnTriggerEvent += AchievementTriggered;
    8. }
    9.  
    10. // this now gets called every time the achievement service
    11. // says an achievement was earned by the player
    12. private void AchievementTriggered(AchievementDetails details)
    13. {
    14.     _graphic.text = details.achievementName;
    15.     _graphic.description = details.achievementShortDescription;
    16.  
    17.     _graphic.Trigger();
    18. }
    This would in theory pop up with an Xbox-Achievement style graphic in the upper right hand corner that shows the name and short description of the achievement. This would remain for a few seconds, and then fade out (the actual Text Component references and fade in/out logic would be in the PopupGraphic child object here, but you could put it all in one MonoBehaviour as well) The main point is though, the MonoBehaviour above doesn't contain any of the data for the achievements, or control how those achievements are obtained. It's a consumer of the data, not the owner of it- just a tool. This is the same for every single MonoBehaviour in a scene- they're only in charge of the aspects that are actually being visually displayed in some way.

    Critically, you could replace them in 5 seconds with a completely different implementation and it would break nothing at all.

    So you might be able to see how there's never a need to use GameObject.Find. The only things a GameObject/Monobehaviour is going to care about are A: the things already contained in the same entity/prefab, which means you can have the references injected via inspector drag-and-dropping, B: things that are controlled through other services, and so which they shouldn't be communicating with directly in the scene anyways (they should go through the service methods), and C: things colliding through the physics system and triggering events, and you get those references as an argument in the Trigger/Collider methods anyways.

    Of course, that's not the only way to build a game (pretty much anything is viable if you're willing to put enough work into it), but it's an approach I'm quite fond of, and I think it makes things much simpler in the long run.
     
    Last edited: Aug 14, 2019
  6. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    For quick reference - keeping a reference in a field increses the size of the object with that field by the size of a pointer on your system - most often 8 bytes these days.

    So if you have 20 references, that means that if you're working with the objects that has those references, and don't use those references, that's 20*8 = 160 bytes of wasted memory.

    Finding a single object using GameObject.Find travels through up to the entire hirearchy of your scene. I don't know how many bytes you have to load, but probably many megabytes - ie. many millions.

    So disregarding all the ways in which Find is brittle and bad that @Lysander is pointing out, you're trying to save crossing a stream by swimming accross the Atlantic.