- Home /
How do c# scripts get accessed by the Unity Engine
So when I create a script and extend from monobehavior, and for example, put some code in the start() method, how does unity itself, access this custom script i've just made and call that method. There can be hundreds of custom scripts so how does unity execute all these and manage them?
Answer by JVene · Jan 02, 2019 at 06:59 PM
First, I'll assume the notion of native (C++) code calling C# code is not the focus of your question, and that boils down to a fairly straightforward means of storing and calling methods in C# class instances.
The Unity framework has a few key loops where such method calls are cycled, found in this documentation on the order of execution, particularly the script lifecycle flowchart. Within each cycle the engine will use a container (could be a list, a sorted or unsorted array, etc) to refer to each object instantiated (which may include non-game objects like the event system).
In the simplest overview, Unity is going to loop through that container of objects one at a time, possibly organized for pertinence (a list of only those instances with an Update method for calls to Update, for example). If the C# reflection system shows the object has a method for a particular framework target (like Update or Start), the framework will call that method on each instance.
This isn't much more than what you'd do to call a method on every C# object held in a List or array. There are obvious performance concerns, and it becomes important for the response functions to be efficient. The management code itself might be fast, capable of looping through hundreds of thousands of method calls per second on typical hardware, if the methods did only trivial work. The overhead of invoking a C# method call from C++ code can be somewhat mitigated by the way such code is implemented (like IL2CPP), where some of the hard (and slow) work of method calls between languages is reduced.
However, as your question implies, there is a practical limit on the number of instances this design can effectively serve. Experience suggests that a hundred or so is quite practical, depending on what the response methods do, while several thousand becomes a performance burden. As such, where your question implies concern for volume, the answer comes from two general solution ideas.
First, it is reasonable to consider that having a response method on each GameObject, where they may number beyond a 100 or more instances (the value dependent on the target hardware), replaced with a design that takes a method from a "supervisor" object, which distributes work over the population in C#. For example, say you have 1,000 pieces scatter about, and you require each run code during Update. This volume is high, and the overhead to call a method on 1,000 instances is a bit inefficient. You might do better (perhaps much better) to have an invisible (empty) GameObject act as a supervisor, which itself has a container of the 1,000 objects. In this way, Unity only calls update on the supervisor, which itself loops through the 1,000 objects to apply logic to them (perhaps inline). I should note this notion applies reasonably to Update and FixedUpdate (could be used to speed up initialization for Start or Awake), but is not really applicable to collision and trigger response methods (they are called on the opportunity of collision from the physics engine and thus don't apply to a supervisory approach).
That said, your inquiry mentions the Start method, but since that is only called once it is usually not a performance concern. Update and FixedUpdate are typically a performance problem in large volumes.
HAHAHA! Actually, I type over 100 wpm, so that doesn't take all that much time. I've been at this over 30 years, so it just spills out, and before I know it there's a chapter.
Answer by Vicarian · Jan 02, 2019 at 05:29 PM
The short, less technical answer basically boils down to "magic." The longer, technical answer involves going into the engine system mechanics, touching on InteropServices, code conversion from C# to native, etc. The Editor creates a solution file in the background that organizes your code and prepares it for packaging into a dynamic link library (Assembly-CSharp). This process allows the superclass (MonoBehaviour) to execute all derivative code where a reference exists.