When you are coding your game, the size and structure of your code can rapidly become overwhelming over time, unless you have, from the beginning, set a defined strategy that you will follow to ensure that your code will grow in a way that is manageable. To achieve this goal, there are several ways that you can follow including a component-based structure.
In Unity, most of the games that you will create, by default, will follow a component-based approach, which is essentially how scenes and objects are created in Unity. Unity is using a component-based architecture where each GameObject can include one or more components, which can, in turn be made of or manage other components.
A component often has a specific functionality and purpose. For example, a component can be used for collision detection (for example, BoxCollider), to render a GameObject (for example, Mesh Renderer) or to add physics properties (for example, RigidBody) to a GameObject. Each component will work regardless of the GameObject it is attached to. In addition, components can also communicate between them to exchange information.
Each of the components’ properties can be seen in the Inspector, which makes it easier to add or modify a component and to then see how it impacts on the game. Since adding or amending a component can be done very quickly in Unity (for example, using drag and drop), the component structure makes sense in Unity as it speeds up the way you can test your ideas.
This being said, it can be argued that, on the long run, with rather complex and large projects, this approach may not be suitable, and you may instead change your approach to a stricter Object-Oriented structure. There is no right or wrong approach but instead an approach that matches your progression or stage.
For this section, we will essentially focus on a component-based structure to match Unity’s way of doing things. However, you can if you wish look for additional resources on OOP structures, for your information.
So, to create your games in Unity, and if you are coming from an Object-Oriented background, you may need to think in terms of components, in the sense that you may need to start creating small scripts that implement a specific feature but that are not tied to a particular object or class. Ideally a component should know nothing about the GameObject it is attached to. This makes your component more modular and it also makes it possible to build a game that is truly component-based and hence, modular.
In a component-based architecture, all GameObjects will share some features (that is, components) such as a Renderer or a Collider, for example. However, the number and types of components for a particular object makes this GameObject unique.
Using a component-based approach has, of course its challenges. One of these challenges is linked to communication and synchronization between these components, especially when one component is waiting for another component to perform a particular task. In this case, thankfully, Unity provides the class SendMessage. Using this class, a GameObject can send messages to all of its components and exclusively target the components that are interested in that message. This can be achieved using the option SendMessageOptions.RequireReceiver.
For example, if you plan to create NPCs with different behaviours, you could do the following:
- Start to evaluate the features or “abilities” that the NPC will require. For example, the NPC may need to navigate, sense the presence of the player, include a finite state machine, have a weapon inventory system, have an appearance, have intelligence, be able to attack, be able to chase the player, or be able to look for ammunitions when needed.
- You can then identify existing components provided by Unity that may be able to fulfill some of these features (for example, Renderer, or Navmesh Agent).
- Implement the components that don’t exist yet (for example, vision, hearing, or inventory) using your own scripts.
- You can then create different types of NPCs that consist of a combination of these components.
Let’s look at the example of an NPC:
When creating NPCs in our game, we could, for example, define the following components:
- Health: to manage health levels and to be able to increase or decrease health accordingly.
- Navigation: this can be managed with a NavMesh Agent.
- Intelligence: this can be managed by the Animator Controller (that is, a finite state machine).
- Smell: this will give our NPC a sense of smell based on distance. This can be implemented based on distance in a script that we could create.
- Hearing: this will give our NPC the ability to hear. This can be implemented based on distance in a script that we could create.
- WeaponManagement: this will make it possible for the NPC to manage weapons and can be implemented in a script that we could create.
- Weapons: these will be managed by the WeaponManagement component defined previously.
- Lead: this could be added later-on so that an NPC has the ability to lead a particular group of NPCs.
All components can of course include their own variables and methods and communicate between each other using the syntax SendMessage, including to notify relevant components of the occurrence of specific events.