Unity Software Architecture and Design Patterns

17 min. read

Ever had that feeling of, fckit, let’s just start over! Over the years, I have ran into this problem so many times, on our own project and client projects. Projects get messy and you might as well start over instead of trying to fix it. The Monolithic Project problem quickly reveals it’s self, especially if more people come onto the project, with no coding standards, etc… in place.

In this document I highlight some of the options we have when it comes to planning (Architecting) the Unity project.

We need to think ahead, and plan.

Scalable Game Development
Unity Project
Art Pipeline
Code
Build Tools
Multi-platform
Externalizing Data
Better GUI
Editor Tools

The Let’sJustDoIt “Pattern”

This is the default way people write code, I call it pattern, since 99% of also course do it this way, hence, a pattern. I dont blame them, each course on YouTube, Udemy, Pluralsight, LinkedIn Learning, all start out exactly the same. “Lets start by making a script for XYZ….”

XGD Pattern

Data - ScriptableObjects are nice, but it has limitations:
Unity Dependant
No easy way to convert from CSV/JSON to scriptable objects.
Linking/Reference ScriptableObjects is basically the same as linking MonoBehaviours
ScriptableObject can be loaded by:
Reference
Resources.Load
Singleton with Resources.Load
Asset Bundles

Clean Architecture
https://michael-martinez.fr/clean-unity-architecture/

https://unity3d.com/how-to/architect-code-as-your-project-scales

https://github.com/umm/cafu_core

https://www.slideshare.net/monry84/clean-architecture-for-unity

https://www.youtube.com/watch?v=8Hy4JvtfUb8

https://www.gamasutra.com/blogs/RubenTorresBonet/20180703/316442/A_better_architecture_for_Unity_projects.php

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

https://pusher.com/tutorials/clean-architecture-introduction

https://dev.to/bosepchuk/why-i-cant-recommend-clean-architecture-by-robert-c-martin-ofd

https://www.freecodecamp.org/news/a-quick-introduction-to-clean-architecture-990c014448d2/

https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164

https://gist.github.com/ygrenzinger/14812a56b9221c9feca0b3621518635b

https://michael-martinez.fr/clean-unity-the-controller-manager-issue/

ECS

Unite Europe 2016 - ECS architecture with Unity by example
https://www.youtube.com/watch?v=lNTaC-JWmdI&t=2s

Entity system architecture with Unity - Unite Europe 2015
https://www.youtube.com/watch?v=1wvMXur19M4&t=2s

Reactive
UniRx Reactive Extensions for Unity

https://github.com/neuecc/UniRx

Design Patterns
https://www.oodesign.com/

http://gameprogrammingpatterns.com/

https://jacksondunstan.com/articles/3092

https://unity3d.com/learn/tutorials/modules/intermediate/scripting/coding-practices?playlist=17117

https://learn.unity.com/learn/search?k=%5B%22q%3AScripting%22%5D

http://unite.schellgames.com/Unite_2011_Scalable_Game_Development.pptx

MVC
Model - game logic
View - Graphics, 3D models, etc..
Controller - Interface to the model

From a user’s perspective

View
User Action -> Controller
Read state -> Model.State
Controller
Write State -> Model.State
Call Functions - > Model.function
Model
State
Functions

MVCS
References
http://www.rivellomultimediaconsulting.com/unity3d-mvcs-architectures-strangeioc/

Robot Swarm

Reactive Programming
http://www.rivellomultimediaconsulting.com/unity3d-reactive-extensions-1/

uFrame
https://github.com/InvertGames/uFrame-MVVM
https://www.assetstore.unity3d.com/en/#!/content/14381

StrangeIoC
https://github.com/strangeioc/strangeioc
https://www.assetstore.unity3d.com/en/#!/content/9267

Entitas
https://github.com/sschmid/Entitas-CSharp
https://www.assetstore.unity3d.com/en/#!/content/87638
https://github.com/sschmid/Entitas-CSharp
https://youtu.be/1wvMXur19M4
https://youtu.be/lNTaC-JWmdI

UniRx

Zenject

Entity–component–system
https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system

https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system

Singleton Pattern
Examples: Audio Manager
private static AudioManager instance;
public static AudioManager Instance
{
get { return instance; }
}

void Awake()
{
instance = this;
}

Create a reusable Singleton pattern using T

Beware of Singleton abuse!
Some call it an anti-pattern

Thin Component Pattern
Model should be separate from Unity

Example:
Controller
VechileComponent : MonoBehavior
Handles all unity related functions
Model
Driver
Attributes
Operations
Weapon System
Attributes
Operations

MVVM

Model
View
ViewModel

Or rather
View
UI
UI Logic (Code Behind)
Data Binding <-> ViewModel
Commands <-> ViewModel
ViewModel
Notifications -> View
Presentation Logic
Model
Business Logic (Game Logic)
Entities

References
http://www.what-could-possibly-go-wrong.com/bringing-mvvm-to-unity-part-1-about-mvvm-and-unity-weld/
http://www.what-could-possibly-go-wrong.com/bringing-mvvm-to-unity-part-2-property-and-event-bindings/
https://forum.unity.com/threads/mvvm-databinding-and-lobby-toolkit.232526/

Data-Driven Design (DDD)
Techniques and strategies for data-driven design in game development
http://web.eecs.umich.edu/~soar/Classes/494/talks/Schumaker.pdf

Data-driven and component-based game entities
https://archive.fosdem.org/2012/schedule/event/536/104_Thomas_Kinnen_-_Data-Driven_and_Component-Based_Game-Entities_-_Slides.pdf

Component-Based Design (CBD)
References: https://en.wikipedia.org/wiki/Component-based_software_engineering

https://www.youtube.com/watch?v=1YGVP6wsxj0

ScriptableObjects
Unite Europe 2016 - Overthrowing the MonoBehaviour tyranny in a glorious ScriptableObject revolution
https://www.youtube.com/watch?v=VBA1QCoEAX4

https://www.youtube.com/watch?v=VtuSKmfrFDU&t=10s

https://www.youtube.com/watch?v=raQ3iHhE_Kk&t=46s

Unity Scriptableobject Architecture

References
https://github.com/jheiling/unity-signals

https://forum.unity.com/threads/best-practice-questions-scriptable-objects-and-prefabs-reference.511021/

https://www.schellgames.com/blog/insights/game-architecture-with-scriptable-objects

https://github.com/roboryantron/Unite2017

https://www.slideshare.net/RyanHipple/game-architecture-with-scriptable-objects

https://www.gamasutra.com/blogs/HermanTulleken/20160812/279100/50_Tips_and_Best_Practices_for_Unity_2016_Edition.php

https://www.google.com.au/search?q=unity+scriptableobject+architecture&safe=off&rlz=1C5CHFA_enAU772AU772&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwjj16L99t7bAhXItpQKHQaJBQUQsAQIsQE
1:04:29
Unite Austin 2017 - Game Architecture with Scriptable Objects
15:33
Unity3D - Using ScriptableObject for Data architecture / sharing

https://www.youtube.com/watch?v=8TIkManpEu4

https://www.google.com.au/url?sa=i&rct=j&q=&esrc=s&source=images&cd=&cad=rja&uact=8&ved=2ahUKEwi4kMKu-d7bAhULa7wKHVKKAEQQjRx6BAgBEAU&url=https%3A%2F%2Fdocs.unity3d.com%2FManual%2FExecutionOrder.html&psig=AOvVaw3KzbtMynBI-XP8lkdjoQE3&ust=1529470931346260

Event subscriber/listener pattern
A.K.A Messaging System

EventManager
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;

public class EventManager : MonoBehaviour {

private Dictionary <string, UnityEvent> eventDictionary;

private static EventManager eventManager;

public static EventManager instance
{
    get
    {
        if (!eventManager)
        {
            eventManager = FindObjectOfType (typeof (EventManager)) as EventManager;

            if (!eventManager)
            {
                Debug.LogError ("There needs to be one active EventManger script on a GameObject in your scene.");
            }
            else
            {
                eventManager.Init (); 
            }
        }

        return eventManager;
    }
}

void Init ()
{
    if (eventDictionary == null)
    {
        eventDictionary = new Dictionary<string, UnityEvent>();
    }
}

public static void StartListening (string eventName, UnityAction listener)
{
    UnityEvent thisEvent = null;
    if (instance.eventDictionary.TryGetValue (eventName, out thisEvent))
    {
        thisEvent.AddListener (listener);
    } 
    else
    {
        thisEvent = new UnityEvent ();
        thisEvent.AddListener (listener);
        instance.eventDictionary.Add (eventName, thisEvent);
    }
}

public static void StopListening (string eventName, UnityAction listener)
{
    if (eventManager == null) return;
    UnityEvent thisEvent = null;
    if (instance.eventDictionary.TryGetValue (eventName, out thisEvent))
    {
        thisEvent.RemoveListener (listener);
    }
}

public static void TriggerEvent (string eventName)
{
    UnityEvent thisEvent = null;
    if (instance.eventDictionary.TryGetValue (eventName, out thisEvent))
    {
        thisEvent.Invoke ();
    }
}

}
Event Listener
using UnityEngine;
using UnityEngine.Events;
using System.Collections;

public class EventTest : MonoBehaviour {

private UnityAction someListener;

void Awake ()
{
    someListener = new UnityAction (SomeFunction);
}

void OnEnable ()
{
    EventManager.StartListening ("test", someListener);
    EventManager.StartListening ("Spawn", SomeOtherFunction);
    EventManager.StartListening ("Destroy", SomeThirdFunction);
}

void OnDisable ()
{
    EventManager.StopListening ("test", someListener);
    EventManager.StopListening ("Spawn", SomeOtherFunction);
    EventManager.StopListening ("Destroy", SomeThirdFunction);
}

void SomeFunction ()
{
    Debug.Log ("Some Function was called!");
}

void SomeOtherFunction ()
{
    Debug.Log ("Some Other Function was called!");
}

void SomeThirdFunction ()
{
    Debug.Log ("Some Third Function was called!");
}

}

Event Caster
using UnityEngine;
using System.Collections;

public class EventTriggerTest : MonoBehaviour {

void Update () {
    if (Input.GetKeyDown ("q"))
    {
        EventManager.TriggerEvent ("test");
    }

    if (Input.GetKeyDown ("o"))
    {
        EventManager.TriggerEvent ("Spawn");
    }

    if (Input.GetKeyDown ("p"))
    {
        EventManager.TriggerEvent ("Destroy");
    }

    if (Input.GetKeyDown ("x"))
    {
        EventManager.TriggerEvent ("Junk");
    }
}

}

References:
https://www.youtube.com/watch?v=0AqG1fDhPT8

Finite State Machines (FSM)
Panda BT
https://www.assetstore.unity3d.com/en/#!/content/33057

PlayMaker
https://hutonggames.com/

decorator pattern
https://en.wikipedia.org/wiki/Decorator_pattern

Passive View
https://martinfowler.com/eaaDev/PassiveScreen.html

Testing
Quote on debugging
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”

  • Brian Kernighan

MonoBehaviour
Set Assembly Folders

Good pattern to debug
Debug.Log(nameOfComponent + “ message “, this );
Delete Debug.Log when no you release
Solve warnings and errors

Write own Debug system, can disable it globally

Make error handling LOUD, cause

Unit testing (NUnit)

Easy way to test functions on components
[ContextMenu(“Attack”)]
private void TestAttack(){

}

Right click the component to set the function, this also works in edit mode (without playing)

Integration Testing

Profile and Analyser

Project Management
http://initiative.online/p/demystifying-unity-for-large-projects-6-rules/

Folder Structure
ProjectName
Unity
Library
Project Settings
Assets
Code
Editor
Game
Test
Content
Art
Audio
Effects
Scenes
Test
Documents
RawAssets
Version Control
BitBucket
GitFlow
Use blame

Estimations
Estimation = Original estimation * = PI

uFrame
https://assetstore.unity.com/packages/tools/visual-scripting/uframe-game-framework-14381

Pokemon Go
Was a new genre
Dependency Injection
Normal Polymorphism
IOC (Inversion of Control) Polymorphism

Traditional Dependency Injection
Zenject

Uses a GameState system

Ideas
Create a JSON file structure for GUI

Unity Packages

Marklight
https://assetstore.unity.com/packages/tools/gui/marklight-unity-presentation-framework-37466

Zenject
https://assetstore.unity.com/packages/tools/zenject-dependency-injection-ioc-17758

Introduction
Context is everything! The users of games are not the same as the users of apps

Current

Dotted lines are symbolic

SOLID
Single Responsibility
Open-Close
Liskov Substitution
Interface Segregation
Dependency Injection

Dependency injection

https://unity3d.com/unity/features/job-system-ECS

https://blogs.unity3d.com/2014/05/07/dependency-injection-and-abstractions/

http://wiki.unity3d.com/index.php/Dependency_injection

https://github.com/modesttree/Zenject

http://strangeioc.github.io/strangeioc/

https://www.oodesign.com/dependency-inversion-principle.html

Promises

Links
https://github.com/Real-Serious-Games/c-sharp-promise#understanding-promises
http://www.what-could-possibly-go-wrong.com/promises-for-game-development/

Push or Pull
Low level pulls data from high level, but the issue is, it doesn’t know when.
Subscribe on Awake
Unsubscribe on Destroy
Inheritance vs Interfaces
What it is VS what it can do
Lazy vs Eager
Game uses eager
Abstraction
Virtual or abstract methods

Generic

TrackViewBase: MonoBehavior where T : Component

TrackViewImage : TrackViewBase

Lis
Custom Samplers

Seperate Input Modules

C# Indexers

Partial Class

Unity Architecture

Scriptable Objects
Unity ScriptableObjects

Unite Europe 2016 - Overthrowing the MonoBehaviour tyranny in a glorious ScriptableObject revolution
https://www.youtube.com/watch?v=VBA1QCoEAX4&t=2s

Unite Austin 2017 - Game Architecture with Scriptable Objects
https://www.youtube.com/watch?v=raQ3iHhE_Kk

General
Use
[SerializeField]
private TYPE NAME;

Component Abuse
Either too many or too little components

Make a system has to be a component

Unity Project Static
Project Components
Unity-dependent systems
Unity-independent systems
Unity
.Net

Flux
Its only the views that change….

Tic Tac Toe
Player makes a move — puts a X somewhere on the board
The system needs to check if the move is legal — whether the slot is free. That is done by running a set of rules on the board model.
If it is NOT free — the ADD_ITEM_FAILURE action should be dispatched, thus keeping the currentPlayer property without change.
If it is free — the ADD_ITEM_SUCCESS action should be dispatched
The boardReducer should update the board.
The gameRuntimeReducer should update the current player
If the second player is AI — the AI manager should fire up the CALCULATE_NEXT_MOVE action
After the next move is calculated, the TRY_ADD_ITEM should be fired, resulting in a succesfull board update and switching control to the other player.

State
{
board:{}, //Current state of the board
players:[], //Player objects, each player has a name and colour
currentPlayer:{}, //Who is the current player to make a move?
time:DateTime, //How much time passed since game start?
steps:int //How many steps were taken until now?
}

Actions
actions/moves //TRY_ADD_ITEM, ADD_ITEM_SUCCESS, ADD_ITEM_FAILURE - player makes a move - add item to board
actions/rules //RUN_RULES - to run rules on board to determine if someone won.
actions/board //CLEAR_BOARD
actions/game // GAME_START, GAME_END,…
actions/AI //CALCULATE_NEXT_MOVE

Reducers
boardReducer //updates the board model
gameRuntimeReducer //Updates game runtime - currentPlayer, time, steps, etc.

https://github.com/mattak/Unidux

References
https://medium.com/front-end-hacking/flux-architecture-in-games-porting-the-web-design-pattern-to-game-development-f9d0959346ec

Design Patterns
Singleton Pattern
Examples: Audio Manager
private static AudioManager instance;
public static AudioManager Instance
{
get { return instance; }
}

void Awake()
{
instance = this;
}

Create a reusable Singleton pattern using T

Beware of Singleton abuse!
Some call it an anti-pattern
Flexible Software Architecture
MVC
Model - game logic
View - Graphics, 3D models, etc..
Controller - Interface to the model

From a user’s perspective

View
User Action -> Controller
Read state -> Model.State
Controller
Write State -> Model.State
Call Functions - > Model.function
Model
State
Functions

MVVM

MVP

MVC

Model
The model class only stores the data

View
The view only displays data
View can be MonoBehaviour or UIBehavior
https://docs.unity3d.com/ScriptReference/EventSystems.UIBehaviour.html

Updates when the data changes

Receive events from controllers.

Controller
The controller only manipulates the data

Start with View
Model
Ask the question, what is the object?

Controller
Reads data

Resources
NOTE!: XGameDev template document

Course:
https://www.udemy.com/unity-model-view-controller/learn/v4/t/lecture/6981038?start=0

Thin Component Pattern
Model should be separate from Unity

Example:
Controller
VechileComponent : MonoBehavior
Handles all unity related functions
Model
Driver
Attributes
Operations
Weapon System
Attributes
Operations

MVVM

Model
View
ViewModel

Or rather
View
UI
UI Logic (Code Behind)
Data Binding <-> ViewModel
Commands <-> ViewModel
ViewModel
Notifications -> View
Presentation Logic
Model
Business Logic (Game Logic)
Entities

Testing
Quote on debugging
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”

  • Brian Kernighan

MonoBehaviour
Set Assembly Folders

Good pattern to debug
Debug.Log(nameOfComponent + “ message “, this );
Delete Debug.Log when no you release
Solve warnings and errors

Write own Debug system, can disable it globally

Make error handling LOUD, cause

Unit testing (NUnit)

Easy way to test functions on components
[ContextMenu(“Attack”)]
private void TestAttack(){

}

Right click the component to set the function, this also works in edit mode (without playing)

Integration Testing

Profile and Analyser

Project Management
Folder Structure
ProjectName
Unity
Library
Project Settings
Assets
Code
Editor
Game
Test
Content
Art
Audio
Effects
Scenes
Test
Documents
RawAssets
Version Control
BitBucket
GitFlow
Use blame

Estimations
Estimation = Original estimation * = PI

uFrame
https://assetstore.unity.com/packages/tools/visual-scripting/uframe-game-framework-14381

Pokemon Go
Was a new genre
Dependency Injection
Normal Polymorphism
IOC (Inversion of Control) Polymorphism

Traditional Dependency Injection
Zenject

Uses a GameState system

Ideas
Create a JSON file structure for GUI

Unity Packages

Marklight
https://assetstore.unity.com/packages/tools/gui/marklight-unity-presentation-framework-37466

Zenject
https://assetstore.unity.com/packages/tools/zenject-dependency-injection-ioc-17758

References
https://www.youtube.com/watch?v=q9aeNtKKXeo
https://github.com/jbruening/ugui-mvvm
https://www.codeproject.com/Articles/100175/Model-View-ViewModel-MVVM-Explained
https://wiki.nus.edu.sg/display/TitanomachyRTS/Architecture+Design+for+User+Interface
https://localwire.pl/uframe-mvvm-and-separation-of-concerns/
https://github.com/peterloos/Wpf_TowersOfHanoi/tree/master/TowersOfHanoi

https://www.udemy.com/solid-object-oriented-programming-and-profiling-unity-5/learn/v4/t/lecture/7222964?start=0

https://www.youtube.com/watch?v=q9aeNtKKXeo

https://github.com/jbruening/ugui-mvvm

https://www.codeproject.com/Articles/100175/Model-View-ViewModel-MVVM-Explained

https://wiki.nus.edu.sg/display/TitanomachyRTS/Architecture+Design+for+User+Interface
https://localwire.pl/uframe-mvvm-and-separation-of-concerns/
https://github.com/peterloos/Wpf_TowersOfHanoi/tree/master/TowersOfHanoi

https://www.toptal.com/unity-unity3d/unity-with-mvc-how-to-level-up-your-game-development

https://forum.unity.com/threads/unity-best-architecture-and-design-pattern.424786/

https://www.gamasutra.com/view/feature/132649/the_case_for_game_design_patterns.php?page=2