Hello, I just started to learn Unity, and I’ve already kicked my head against the wall when it comes to change my mindset in the way I should code-think in Unity.
My question is what is the usual way to structurize code in Unity? This is really hardest thing I’m facing about Unity. I’m coming from a C/C++/Java, coding enterprise software in Windows, to give you a rough idea.
First, I finally understood that there is nothing such as a Main Loop. Took me a while to really digest this concept, but I can understand it as the Unity paradigm is component based.
But then, how should I code in Unity? I’m really mixed up because component based is not how I coded during the last 20 years.
Should I create small scripts and place them as components in the Inspector by attaching the scripts to GameObjects?
What about inheritance? I see everything inherits from MonoBehaviour.
So let’s say I want to create a “Player” class-script for a “Hero” GameObject, but this “Player” class has common properties from other characters.
I would first create an “Entity” base class which inherits from MonoBehaviour and make it have these common properties, then make the “Player” script class to inherit from the “Entity”, and finally drop the “Player” script into the “Hero” GameObject in the Unity UI. Is that the correct procedure? is this the way of Unity thinking?
Everything inherits from MonoBehaviour, but you can inherit from other scripts. “Entity” is probably too generic a name but your structure there is sound, and one acceptable way to do these things in Unity.
There is one good reason not to do that…sometimes… and that is versatility. If you have an Entity class that has the basic elements of a character, and then an Enemy class and a Player class, which both inherit from it, it’ll work fine. The Player class adds user controls and the weapons that the player can use. But then you want to add an Ally class that uses the same weapons as the player, and fights the Enemy’s. Then you add a SuperEnemy class that uses the same weapons the player can use, but fights the Player and the Ally. Sure, all this can be done, but it will require a fair amount of code refactoring along the way, and only gets worse as the project gets bigger.
Contrast this to a component-based approach: Create an Entity class, then a PlayerController class and an EnemyController class, all independent classes that inherit from Monobehaviour, and reference their “neighbor” components (PlayerController calls GetComponent().Move() in response to user input, etc). Now you add a PlayerClassWeapons component. So now, when you go to create that super-enemy, you don’t have to do any refactoring at all, just smaller changes in EnemyController so that it uses PlayerClassWeapons; other than that, just drag the PlayerClassWeapons onto your enemy object.
Neither of these is the best approach, which is “use inheritance when you should and components when you should”, and takes a bit of practice to get a handle on. In this case, inheriting Player and Enemy from abstract Entity probably IS a good idea, and inheriting PlayerClassWeapons and MookClassWeapons from abstract Weapons class is good; however, they are separate components from the Entity based ones. abstract Weapons class has a virtual Fire(), and Player and Enemy scripts both use that. (Note that GetComponent() will return not only Weapons, but also PlayerClassWeapons or MookClassWeapons attached to the object, which makes this approach REALLY handy.) When making new stuff in this scenario, giving an enemy player-class weapons requires no refactoring or rewriting code at all, just dropping the new weapon on the Enemy object!
It’ll take some time to get used to when each approach is the way to go, but hopefully this will help you get a start.
So going more the component way of Unity, I assume then that the order in which the scripts are attached to a game object, is really important for the correct workflow execution. Even if components are modular, I still think that the execution order is something to be taken very into account, such as first execute the input controller component, then the AI component, etc
Not necessarily. In your example, why should the AI component be doing anything dependent on that frame’s user input? Instant reaction time?
If you do have several components that depend on each other, some of them will not have an Update function at all. I often find it useful to have a MB-derived class that only has functions that are called from the outside, or classes which only store information, and occasionally classes that have no content at all (to be used as “tags”, since Unity’s tag system is too limited to be of practical use in many cases.)
If we use our players/weapons system from my last post, then weapons would be a good example of a script that probably doesn’t do anything on its own. The Entity script would hold a reference myWeapon to the abstract Weapons object, which it grabs using GetComponent early on. Player (which derives from it) can then use that reference, calling myWeapon.Fire() when the player presses a button. AIController (which also derives from Entity) calls myWeapon.Fire() when it has the player in its crosshairs. Weapon and its derived classes do nothing without being specifically called on by one of the Entity-derived classes. So in many cases like this, you control execution order that way.
Here’s the thing, there’s nothing in the Unity docs that say components have any kind of order. This means that yes, race conditions were very common for me until I understood how to set up a game in Unity.
We have a few prefabs that are fully encapsulated. They can get instantiated, Awake(), Start(), figure out what they need to do and do it. Learning how to encapsulate MonoBehaviours so that it doesn’t rely on what other objects are doing is important.
However, not everything works like that. All our scenes have a SceneManager that initializes everything, instantiates prefabs, starts services, etc. While I know about changing “script execution order” is an option, by having a SceneManager we can control in what order we initialize our scripts. If needed, we can also have our scripts not use the MonoBehaviour Update() function but instead have our SceneManager run a DoUpdate() function in the order we require. We cannot encapsulate everything away, so we have this manager, and sub-managers, to handle the interactions between parts of our game.
When I first started scripting in Unity, I tried having a single MonoBehaviour called MainLoop and that would run everything else in the game. I found out this is unnecessary and bug-prone because I ended up fighting the component-based way of doing things at every step. My code ended up being more complicated than needed.
tl;dr: Encapsulate whatever you can. You can implement a “Main Loop” kind of structure along side if you need.
EDIT: Most of our scripts don’t actually derive from MonoBehaviour. They are “pure” C# classes that get used by our MonoBehaviours.
I’ve come to appreciate that Unity doesn’t guarantee execution order within a phase (e.g., Update()). It forces you to keep components nicely decoupled.
Really embrace component-based design. Think of each script as a single property – that is, a single attribute or facet. For example, rather than having a hitPoints variable inside a larger Entity component, create a HitPoints component that does nothing but manage hit points. Then you can add this not only to entities but also to destructible walls, force fields, and other things that you haven’t thought of yet.
Your class inheritance tree will be very shallow, and your scripts will be smaller, less error-prone, easier to maintain, and more re-usable.
Lots of small scripts don’t necessarily have any more significant overhead than a small number of monolithic scripts, and the benefits of reliability, maintainability, and reusability far outweigh that concern anyway. Just avoid empty or unneeded hooks for the phase methods such as Update(), FixedUpdate(), LateUpdate(), and OnGUI(). No need for Unity to waste time (insignificant as it may be) needlessly calling Update() on a script. If you really do need to do something over time, a shorter-lived coroutine is usually better than checking every frame in Update().