The Unity Principle of Design is commonly known as one of the more complex principles, and incorporates other, more basic design principles - Visit to learn more about the Unity Principle of Design. Principles of design are widely-accepted notions that designs must consider an optimal user experience. Unity uses the list of Scenes to determine the order that it loads the Scenes in. To adjust the order of the Scenes, drag them up or down the list. The Platform pane lists all platforms available in your Unity Editor. The list displays the Unity icon next to the name of.
Over the years and over the course of many projects a certain way to structure a game project in Unity emerged that proved particularly scalable (maintainable).
For a long time I wanted to write this up to bring it into a sharable format.
This article is an update to a talk I gave at GDC in 2017 (“Data Binding Architectures for Rapid UI Creation in Unity”).
Disclaimer: Of course these are only best practices that worked for me and reflect my experience and opinion, these are no silver bullets for everything and definitely not the right approach for every project or team.
Disclaimer2: After writing this people made me aware that we are in good company with this approach since Kolibri Games applies something similar aswell: Blog Post
The main goals of this architecture are:
- maintainability
- extensibility
- testability
These three are not easy to achieve in an engine that is primarily inviting to rapid prototyping. The common conception among game developers is that these principles are more relevant to business solutions than in games and I strongly disagree. Games became more and more software as a service businesses. Allowing us to look at solutions in such areas we find that there are actually tools that we can apply to gaming as well.
- inversion of control
- message passing
- model / view / controller (MVC)
- unittests
Inversion of Control
The following diagram illustrates how tightly coupled components usually work:
ClassA
directly depends on ServiceA
/ServiceB
. This makes it harder to independently test ClassA
without having to worry about the implementation details of the services.
Dependency Injection (DI) is an approach to apply inversion of control. The following graphic illustrates the previous example using DI:
DI is used as a Builder to generate our ClassA
, inspecting the necessary dependencies and resolving these automatically. ClassA
does not care what concrete class is used that implements the required interface
as long as there is one.
We settled on using Zenject/Extenject to apply this pattern. It is reflection based. Using the reflection-baking feature we can get rid of the reflection related performance impact.
Model-View-Controller
The heart of this architecture is breaking up the code into seperate layers. The Model-View-Controller (MVC) design applied to Unity looks as follows:
Unity Monobehaviours live in the View-Layer and are supposed to shield the rest of the architecture from the hard to unittest elements of Unity. This layer only has access to the Control-Layer. The View creates instances of prefabs, uses [SerializeField]
to use Unity typical drag/drop components. No logic is encoded in here, pure visualization of data only.
The Controller-Layer contains the business logic and does the heavy lifting. This code is supposed to be testable, it does not depend on Unity View specifics. This layer still does not define the way data is stored in the Model-Layer, it is only controlling changes on it.
The Model contains the actual Data, this might be ephemeral, on disk or in some backend. Usually these Models are Plain-Old-Data types. Peachtree 2011 sage activation code crack.
Since the View should not poll for changes on the data we use Message Passing to notify it. This way we can keep the Layers decoupled and still contain performance.
The decision whether a view reads the data right out of the model or through a controller is made non-dogmatic. The only rule is: Changes only happen via the control-layer. Reading values can happen straight out of the Model.
Message Passing
The above design relies on appropriate notification messages so that the View-Layer can subscribe and react to changes/events:
We are using Zenject Signals.
The following code example shows usage of it:
It is important to note that Signals are supposed to be lightweight and do not contain Data - we use the rest of the MVC-Layers for this. Signals are a tool for pure notification, event propagation and decoupling.
Alternatives to this are using tools like UniRx to observe model-data-changes but I prefer to have more fine grained control over when we want the change to be notified instead of allowing the view to see every individual value change. This sort of decision over when to notify belongs into the controller-layer and therefore the signaling approach fits nicely.
Unittesting
Based on all the efforts above we can now write unittests for almost all of our game (business) logic.
Mortal kombat 9 dlc characters free ps3. On the technical side of writing those tests we use the Unity standard NUnit framework and NSubstitute as a mocking solution.
Let’s have a look at one of our tests:
The above test is making sure the controller behaves correctly when fed with default data. You see how we are using NSubstitute to mock dependencies and even assert that specific methods were called on these.
Let us look at a more intersting example of actually building something on slot 0
:
Now we check that our GetCurrentBuildCount
returns the correct new building count after succesfully building on slot 0
. We also expect the right signal to be sent to the bus - this way the relevant view can refresh.
“wait a minute, the one thing coming from Zenject can’t be mocked?”(very well pointed out by my good friend Peter 😊)
Yes unfortunately SignalBus
does not come with an interface that we can feed to NSubstitute - therefore we have to actually subscribe and check if the right signal was fired.
These sort of tests are inexpensive to run and keep the integrity of our game logic because we check that those are green even before building a new test version.
This was just a 20.000ft view on this topic. Let’s recap:
We want to be able to write testable code, therefore we decouple unity as much as possible from our business logic, we communicate to unity via messages, we have a clear interface from Unity how to access data. With this we have a small surface area of what is unity specific and cannot be tested (lets ignore playmode tests for now).
In future articles we will write a concrete example game to apply the above system and furthermore look how to combine this architecture with:
- practical example applying the approach
- mocking scenes for ui testing
- fake backends and third party SDKs
- promises for maintainable async code