Search Unity

Making sense of everything "under the hood"

Discussion in 'Scripting' started by SkillBased, May 28, 2015.

  1. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    I love the Unity inspector and how easy it makes things, but I'm struggling to make complete sense of what is really happening deep in the guts of the code that makes up the entire engine. It sometimes leaves me feeling like I wish I was just dealing with pure code, so that it was obvious how everything relates 'under the hood', rather than this Visual UI / Code API duality.

    In order to test my ideas and come up with consistent results, I've had this working mental model in place for trying to understand how everything relates within the code underneath:

    1. GameObjects in the inspector are classes that get instantiated at runtime.

    2. In turn the GameObjects contain properties made up of instances of attached components, also at runtime.

    3. Then some outside class (probably MonoBehaviour) takes all GameObject instances, and calls the Update method (and others inherited methods) on all instances of MonoBehaviour within them, updating the values of the properties, etc, thereby producing the changes in the GameOjects we see on the screen.

    It's occurred to me that even though this model has suited me well in making things work based on the assumptions that go along with it, it's quite possible (probable) I'm missing a big piece of the underlying structure, and if so would eventually reveal my knowledge gap with some code that totally doesn't work. I've had some hints to this already, such as using code like this:

    Code (CSharp):
    1. Instantiate(this); //<-- this should only create a new script component instance, yet it actually instantiates a new GameObject!
    Anyways, I'm just curious how others have been bridging the gap between the UI and the codebase in their minds and, please, by all means tell me if I'm totally wrong in my assumptions!
     
  2. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Sounds like you've got the right idea. I come from an old fashioned, procedural programming (non-OOP) background where we wrote all our own engines for our games (I wrote a 100,000 line physics/vehicle dynamics engine for a couple of racing simulators), so in addition to using someone else's engine, I had to learn OOP in general, C#, and Unity. It's taken some time to wrap my head around it enough.

    By the way, Instantiate() is a method of the Object class:

    http://docs.unity3d.com/ScriptReference/Object.Instantiate.html

    So yes, it creates an object (an instance of an object class). You can add script component instances to game objects with this:

    http://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html

    When I want to add a script component to a game object in code, I prefer the second form, like this:

    Code (csharp):
    1.  
    2. MyClassName myClassName = gameObject.AddComponent<MyClassName>();
    3.  
    OOP has taken a little getting used to for me, reading a C# book helped tremendously. Once things started to click I was off and running though. I try to imagine that Unity is just doing all the same things we used to do in our engines under the hood, then giving me places like Update() and so forth to execute my own code. I too was bothered for awhile by being insultated like that, but have gotten used to it and now enjoy not having to deal with every bit and byte at the lowest level. It's quite a change, I'm much more productive now than I used to be.
     
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I'm reasonably sure that:
    • Game objects are C# objects that happen to be instances of the GameObject class.
    • One of the members of GameObject is a collection of components that are currently attached to that object.
    • The Unity engine uses reflection to find all methods named Awake, Start, Update, etc. on any components attached to game objects, and calls them at the appropriate times.
    • Instantiate means clone (i.e. duplicate an object and all its current data).
     
  4. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    Understanding everything under the hood isn't really all that necessary. Antistone has a better description. GameObject is basically a container for Components and doesn't really do much. MonoBehaviour is one type of Component (you can see this in the Scripting documentation).

    You're confusing yourself with Instantiate because Unity does clever things under the hood to ensure you don't just duplicate a Component. Components always have to live on either a GameObject or asset, so that would be invalid and Unity does the more reasonable thing.

    Unity uses reflection to figure out if any of your scripts have Awake/OnEnable/Start/Update/etc. and calls them at the appropriate time during their internal engine loop. This is why those functions aren't virtual (though they should have been, and their lead engineer has said it was a mistake to not do so).

    If you want to see under the hood a bit more, force your editor settings to serialize in 'text mode' and read your .unity scene file with a text editor.
     
  5. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    Thanks guys, that's all very helpful stuff.

    Still not sure why Instantiate(this) within a script actually creates a GameObject clone. I suppose a script can't be instantiated unless its attached to a dummy GameObject so somewhere there must exist that rule.

    Todd: I've been considering dynamically creating GameObjects purely through code (rather than taking prefabs and using those as starting points). So, yeah something like:

    Code (CSharp):
    1. GameObject someObject = new GameObject();
    2. someObject.transform = new Vector3(1,1,1);
    3. someObject.AddComponent<someScript>();
    4. //etc
    5.  
    The main reason for doing this would be so that I don't have to create all of these prefabs with relatively small differences, and instead rely on some class inheritance and interfaces to keep things simple.

    But then this solution makes me want to make class structures like

    Code (CSharp):
    1.  
    2. public class Enemy : IDamageable //< interface
    3. {
    4.    int health;
    5.    GameObject someObject;
    6. }
    7.  
    8. public class Wizard : Enemy
    9. {
    10.    int someWizardlyThing;
    11. }
    12.  
    13. public class Knight: Enemy
    14. {
    15.    int someKnightlyThing;
    16. }
    17.  
    18.  
    and then dynamically in some other script go:

    Code (CSharp):
    1.  
    2. public class EnemyManager : MonoBehaviour
    3. {
    4.    Enemy enemy = new Wizard();
    5.    
    6.    Start() {
    7.    
    8.    }
    9.    
    10.    Update() {
    11.      
    12.    }
    13. }
    14.  
    15.  
    and determine at that point the specifics (what attached components it has, ie: renderer, scripts, etc). But doing this feels like i'm working against the way Unity seems to make me want to use it so I haven't really thought too hard about implementing this common OOP structure for fear that Unity will slap my hand with some tricking situation it lands me in later. What do you think?
     
    Last edited: May 29, 2015
  6. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    I suppose that's the whole purpose of the Instantiate() function in the first place, which is part of the GameObject class, so it creates GameObjects. :)

    Regarding your code example, you can do things that way where you create the game objects in code, I suppose. Another option is to Instantiate() the game objects from prefabs that don't have any of your Wizard/Enemy/Etc.. scripts on them except for one script, then in that script have it AddComponent() in its Start() to add whatever scripts you want as components to itself to make the small differences. You could probably do that through a constructor that takes in some parameters to tell it which scripts to attach.

    I don't know what's smart to do here, I'm making a boat simulator which is probably very different from what you've got in mind. Someone else can probably give you better guidance on how to skin your particular cat.
     
  7. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Wouldn't Instantiate(this) require the class instance to already exist so that line of code could be executed in the first place? I'm not sure it makes sense for something to instantiate itself.
     
  8. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    I figure that the Script component gets instantiated at runtime as a memory object and the this in Instantiate(this) refers to that instance, and clones it. That's the only way it makes sense. Instantiate actually returns an Object, not a GameObject, so that's why it seemed odd to me that Instantiate(this) on a script Object would return a GameObject.

    But since components only have meaning at runtime as attachments to GameObjects, there must be some check to see if its parent == null and if so create a dummy GameObject. That's my best guess as to what's really happening.
     
  9. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Just as another note, you can have non MonoBehaviour classes that are static and not actually have it as a gameobjects component.

    I'm thinking you probably already knew that, and then again who knows - maybe unity actually does instantiate an invisible empty gameobject which it attaches non MonoBehavior scripts to at runtime! :S hrmmm, probably not haha.

    But yeah my point is you don't always gotta be coupled to a game object - and when you aren't - you don't even use instantiate or any other unity engine goodies really.

    Somebody correct me if I'm wrong!
     
  10. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    If a class is static, doesn't inherit MonoBehaviour there will be a single memory instance generated at runtime which can be tapped into by other classes that reference it. This memory allocation wouldn't be the same as when Unity instantiates a GameObject via the engine. This would just be an ordinary memory object that has nothing to do with GameObjects.

    Now, in that static class you can do something like:

    Code (CSharp):
    1. public static class NonMonobehaviour
    2. {
    3.     static GameObject g;
    4.  
    5.     public static void DoIt()
    6.     {
    7.         g = new GameObject();
    8.     }
    9. }
    And if you access it from within a MonoBehaviour like:

    Code (CSharp):
    1. public class Test : MonoBehaviour
    2. {
    3.     void Update()
    4.     {
    5.           NonMonoBehaviour.DoIt();
    6.     }
    7. }
    It would then indeed create a GameObject.
     
  11. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Ahh ok didn't know that!
     
  12. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
    Component based programming is a curious beast. A lot of the traditional use cases for inheritance are minimized (or potentially just shifted toward interfaces). From your example with separate knight and wizard classes, it would be typical in Unity to just have two separate prefabs where the only differences, minus art assets, are the values on the components, while they use the exact same components. It can make for a very different case of reusability.

    There are a lot of times when if feels like they didn't choose the typical solution for programmers, and that is kind of the reason they chose it.
     
  13. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    What I meant was that if you have some code sitting inside a class, that code doesn't just automatically execute until an object is created out of that class (i.e., nothing happens until it's instantiated, at which point it can then call Instantiate()). At that point what's it supposed to do? Instantiate itself? Maybe I misunderstood what you were saying?
     
  14. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Here's what I mean (statics aside). I could write some class like this that is just sitting in my source code files somewhere:

    Code (csharp):
    1.  
    2. public class ColorPicker : MonoBehavior
    3. {
    4.     Start()
    5.     {
    6.         //Do something
    7.     }
    8. }
    9.  
    This is just a definition for all practical purposes, isn't it? There is no ColorPicker on which the Start() function could even be called until I instantiate it with AddComponent() or Instantiate() or a line in another previously instantiated class that does a "new ColorPicker" or something. So this class could have an AddComponent in it or an Instantiate() or anything else in Start() and nothing would happen at all because the code is never run.

    The Start() function never gets called because there simply isn't a ColorPicker object on which it can be called until I create an instance of it somewhere else, whether that be by attaching it as a script to a game object in the inspector, calling "new ColorPicker," or what have you.

    So to say Instantiate(this) would seem rather impossible. You don't want to Instantiate(this), you want to Instantiate(somethingElseFromWithinThisPrevoiuslyInstantiatedObjectOfThisClass).
     
    Last edited: May 29, 2015
    MD_Reptile likes this.
  15. SkillBased

    SkillBased

    Joined:
    Aug 11, 2014
    Posts:
    141
    Instantiate(this) should take the created instance (which happens later) and create another instance of that object. In this case, the this would be the script instance (the component if you will), not the GameObject. However, the consensus seems to be that Unity creates a GameObject for the script instance (component) and that's why Instantiate(this) creates a GameObject.
     
  16. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Oh, ok. So you meant that it should be creating a copy of itself. Makes sense.