Overview on Unity component based development

For every experienced Unity user the development process of writing and designing entities by the use of components should only feel natural. However not so many programmers stop to think about what really is the architectural approach behind Unity (and game development in general) that makes the engine and the design process so good.
This article wants to give a fast insight of what patterns and architecture a game engine like Unity might use in order to better understand what it's going on behind the scene.

Intro

Unity is a component based engine, adopting a design pattern that was originally pioneered in order to avoid annoying class hierarchies. The idea is to package all functionality of Game Objects into separate behaviour-based scripts. A single GameObject is just the sum of his parts, being them legacy or user written components.

Unity comes with a vast set of legacy components used to extend one Game Object's functionality in terms of graphics, physics, user interface, audio, etc. User written scripts are mostly meant to be newborn components for GameObjects, following a well defined structure in concern of the engine execution lifecycle 1.

This reused-based approach to define, implement and compose loosely coupled independent components into systems is widely used in game engines and is one of the fundamental design pattern adopted to give the user the right flexibility to deal with most of the complexity brought by 3D real time applications2.

GameObject components can be assigned inside the Inspector View, or by script, using the AddComponent call of GameObject. Each public property is directly rendered inside the Inspector with a special controller, to aid the developer in both configuration and debugging.

Runtime Engine Overview

Most of the complexity of Unity built games is hidden behind the inner runtime engine. This engine is the core of all Unity applications and even if his main structure is hidden from the public eye, we can still have a simplistic view at its possible implementation.

The Unity runtime is written in C/C++. Wrapped around the Unity core is a layer which allows for .NET access to core functionality. This layer is used by the user for scripting and for most of the editor UI.

This core is what really handles, in what is expected to be the most efficient way possible, the application main loop3, including aspects regarding resource handling, front-end initialization and shoutdown, input decoupling and more.

At their heart, graphical real-time applications, such as videogames, are driven by an endless loop that performs a series of tasks every frame. By doing these tasks, we put together the illusion of an animated, living world. The tasks that happen during the game loop perform all the actions necessary to have a fully interactive game, such as gathering player input, rendering, updating the world, and so forth. It is important to realize that all of these tasks need to run in one frame.

Game loop sketch from game programming patterns.

class GameObject  
{
public:  
  Component *GetComponent( id );
  void AddComponent( Component *comp );
  bool HasComponent( id );

private:  
  std::vector<Component *> m_components;
};

void Engine::Update( float dt )  
{
  for(unsigned i = 0; i < m_systems.size( ); ++i)
    m_systems[i].Update( dt, ObjectFactory->GetObjectList( ) );
}

The most straightforward implementation of a game loop is to simply have a main while block that sequentially calls all subsystem functionality to be executed in a single frame. This would also include the execution of all component-specific update functions for that exact frame rate. It's clear that without a proper structure and optimization the result would be quite unpleasant, even after adopting the fixed time step & variable rendering practice4.

A more optimized solution can be earn by exploiting caching. The approach is to store all game objects inside a sequential array, so that calls to the components update function can be made following the memory linear traversal5. The idea here is that when you retrieve something from the RAM the likelyhood of requiring to fetch something nearby is high, so the data in that area is grabbed all at once. Of course this approach would have some impact on the complexity of game object deletetion, but it's often unnoticeable.

Another solution would be exploiting parallelism, in one multiprocessor game loop architecture. A way to take advantage of parallel hardware architecture is to divide up the work that is done by the game engine into multiple small, relatively independent jobs. A job is best thought of as a pairing between a chunk of data and a bit of code that operates on that data. When a job is ready to be run, it is placed on a queue, to be picked up and worked on by the next available processing unit. This can help maximize processor utilization, while providing the main game loop with improved flexibility.

References
comments powered by Disqus