Decoupling your game code via Command pattern, debugging it flying on the time machine

Poisonous John (Ivan Fateev)
GameDev Architecture
16 min readJun 13, 2018

Hi! I write articles dedicated to software architecture in game development. In this article I want to show the Command pattern. It’s absolutely flexible and may be applied in so many ways. But I will show you my favorite trick — time machine for debugging mutations of the game state.

This approach saves me so much time when I search for a source of bugs and try to reproduce them. It allows me to save “snapshots” of the game state, history of mutations, and apply these changes step by step.

Junior developers will get acquainted with the pattern, seasoned devs, perhaps will find this trick helpful.

Wanna know how do you do it? Welcome!

Command Pattern

What do we mean by the “Command” word? It’s something like an order. One, with the help of command, expresses they the need to perform some action. Action is inseparable from command.

Command pattern — is a way to represent the Action in an object-oriented programming (OOP) world. And only with the help of polymorphism it is possible.

An idea behind the pattern is that all commands are unified in the system. In terms of OOP, all commands have the same interface. System may transparently execute any of them. And this means that command have to absolutely independent, encapsulate all the data required for its execution.

So far description is pretty abstract, right? Let’s look into concrete examples.

Here’s the basic interface for all commands:

public interface ICommand
{
void Execute();
}

Here’s an example of specific implementation of th ecommand:

public class WriteToConsoleCommand : ICommand
{
public string Message { get; private set; }
public void Execute() {
Console.WriteLine(Message);
}
}

It’s something like traditional “Hello world” using command. But how do you execute it? Let’s write a simple system for command execution.

public interface IGameSystem
{
void Execute(ICommand cmd);
}

public class LoggableGameSystem : IGameSystem
{
public LoggableGameSystem(ILogger log)
{
_log = log;
}

public void Execute(ICommand cmd) {
_log.Debug(string.Format("Executing command <{0}>: {1}", cmd.GetType(), cmd);
cmd.Execute();
}

private ILogger _log;
}

Now we can log any execution of the command for debugging purposes. Helpful, right? But command should be adapted for debugging output. Let’s add ToString() method.

public class WriteToConsoleCommand : ICommand
{
public string Message { get; private set; }
public void Execute() {
Console.WriteLine(Message);
}

public override string ToString()
{
return Message;
}
}

Let’s see how does it work:

    class Program
{
static void Main(string[] args)
{
var gameSystem = new LoggableGameSystem();
var cmd = new WriteToConsoleCommand("Hello world");
var cmd2 = new WriteToConsoleCommand("Hello world2");
gameSystem.Execute(cmd);
gameSystem.Execute(cmd2);
}
}

It’s a very basic example. Of course, debug output is a pretty helpful thing, but it’s still unclear what benefits you can get out of this approach.

In my projects I always use this pattern because of the following reasons:

  • Command encapsulates (stores) everything required for its execution. It is in fact immutable object. Therefore it’s easy to pass this objects via network, and execute absolutely identically between client and server. Of course it’s only in case if client and server produce the same result if given the same input parameters.
  • Command represents pretty small piece of business logic. It’s easy to write, understand and debug. Since command is immutable, and doesn’t contain any extra dependencies, writing a unit test for the command is like a piece of cake.
  • Complex business logic may be expressed via set of simple commands. Commands are easily composable and reusable.
  • Command may represent a check-point, or transaction, if you like. If mutation of the state is done only via commands, it simplifies debugging and understanding of program flow. If something gone wrong, you always can track which command introduced the bug. What is helpful — you can see all the parameters command has been executed with.
  • Execution of commands may be deferred/asynchronous. Typical example — you send command to a server. When a user initiated any action in the game, command is created and appended to a queue for execution. In fact, command will be executed only after the server confirmed it and responded with success.
  • The code written using Command approach, slightly differs from traditional “call function” way. When developers create command instance, they express “intent” to do some action, and do not care when and how it will be executed. It allows one to make very interesting things.

A bit more details about last point. For example, you have synchronous function, which should become asynchronous. In order to do that, you have to change its signature, and rewrite logic to handle asynchronous result in form of callback, or coroutine, or async/await (in case you’ve switched to .net 4.6 runtime). And you have to do that for every function you need to change.

Commands approach allows you to abstract from the execution mechanics. Therefore, if command has been synchronous before, it may be easily changed to asynchronous. It can even be done in runtime (which is impossible with traditional approach).

Let’s look into specific example. Some game should support “partial” offline mode. In case if internet connection is unavailable right now, then commands are appended to the queue, and will be delivered to the server for execution when the connection becomes available again. In case if connection is ok commands are sent instantly.

Making a one-way game state modification

What’s that “one-way game state modification” exactly? The idea is borrowed from the Flux approach, described by the Facebook. The same idea is behind modern libraries like Redux.

In traditional MV* approaches, View interacts with a model in a two-way mode.

In Unity situation is even worse. Traditional MVC absolutely doesn’t fit here, and data is being mutated right from the View (I’ll show an example below). In complex apps, numbers of these dependencies is insane, update is missed inside another update, everything is messed, and you get a spaghetti.

(Source: medium.com)

Let’s experiment and come up with a system similar to the Redux, but in Unity. The main idea behind Redux is that application state is represented by the single object. In other words, in a single model.

Some people may be terrified at this point. But serialization of the game state often comes down to serialization of the single object. It’s a natural way for games.

The second idea is that mutation of the game state is done using Actions. In fact Actions are the same as Commands. View can not modify state directly, only using command.

The third idea — natural continuation of the previous one. View only reads the game state and subscribe to its updates.

Here’s what it looks like in Flux:

(Source: medium.com)

In our case:

  • Store = game state
  • Action = Command
  • Dispatcher = something that executes commands

Such approach will give you a bunch of benefits. Since there’s only one a single game state object, and it’s mutated only using commands, then it’s easy and natural to create a single event of game state update.

Then UI may be easily switched to the reactive approach. In other words, automatically update UI components when the game state changes (hi UniRx! we’ll discuss it in other article).

Such approach also allows you initiate game state mutation from the server side, the same way: using commands. Since game state update event is the same, then UI is absolutely indifferent where it came from.

Another great benefit — cool features for debugging. Since View can only send commands, you can track all changes produced by the app like a piece of cake.

Detailed logging, commands history, bugs reproducing etc. — all that is possible due to this approach.

Implementation

First, let’s decide what our game state will look like. May it be the following class:

    [System.Serializable]
public class GameState
{
public int coins;
}

Let’s add game state serialization to the JSON file. I’ll introduce a dedicated manager class for that.

public interface IGameStateManager
{
GameState GameState { get; set; }
void Load();
void Save();
}
public class LocalGameStateManager : IGameStateManager
{
public GameState GameState { get; set; }
public void Load()
{
if (!File.Exists(GAME_STATE_PATH))
{
return;
}
GameState = JsonUtility.FromJson<GameState>(File.ReadAllText(GAME_STATE_PATH));
}
public void Save()
{
File.WriteAllText(GAME_STATE_PATH, JsonUtility.ToJson(GameState));
}
private static readonly string GAME_STATE_PATH = Path.Combine(Application.persistentDataPath, "gameState.json"); }

Let’s use Dependency Injection library Zenject to manage our dependencies wisely. Its installation and setup are pretty trivial and documented well.

I declare binding for the IGameStateManager.

I created my class of MonoInstaller called BindingsInstaller, according to documentation, and added it to the scene.

public class BindingsInstaller : MonoInstaller<BindingsInstaller>
{
public override void InstallBindings()
{
Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle();
Container.Bind<Loader>().FromNewComponentOnNewGameObject().NonLazy();
}

Also I add binding for a Loader component, which will handle loading and exit from the game.

public class Loader : MonoBehaviour {    [Inject]
public void Init(IGameStateManager gameStateManager)
{
_gameStateManager = gameStateManager;
}
private void Awake()
{
Debug.Log("Loading started");
_gameStateManager.Load();
}
private void OnApplicationQuit()
{
Debug.Log("Quitting application");
_gameStateManager.Save();
}
private IGameStateManager _gameStateManager;
}

Script Loader starts the first in the game. I use it as a starting point and as a script which handles loading/saving of the game state.

Now I create simplest View for the UI.

public class CoinsView : MonoBehaviour
{
public Text currencyText;
[Inject]
public void Init(IGameStateManager gameStateManager)
{
_gameStateManager = gameStateManager;
UpdateView();
}
public void AddCoins()
{
_gameStateManager.GameState.coins += Random.Range(1,100);
UpdateView();
}
public void RemoveCoins()
{
_gameStateManager.GameState.coins -= Random.Range(1,100);
UpdateView();
}
public void UpdateView()
{
currencyText.text = "Coins: " + _gameStateManager.GameState.coins;
}
private IGameStateManager _gameStateManager;
}

I added two methods there, to add and remove coins. Standard approach which I often see — place a business logic right in the view.

You shouldn’t do that way. But for now we should ensure that our prototype is working.

Buttons are working, the game state is being saved and loaded upon game start.

We’ve made it working. Let’s make it right.

I create a separate type for commands which mutate the GameState.

public interface ICommand
{
}
public interface IGameStateCommand : ICommand
{
void Execute(GameState gameState);
}

Common interface is empty to express a single shared type for commands. For specific commands which mutate the game state there’s an Execute method which takes GameState as an argument.

Now I create a service which will execute commands of type IGameStateCommand like the one I showed before. Interface will be generic to fit every type of command.

public interface ICommandsExecutor<TCommand>
where TCommand: ICommand
{
void Execute(TCommand command);
}
public class GameStateCommandsExecutor : ICommandsExecutor<IGameStateCommand>
{
public GameStateCommandsExecutor(IGameStateManager gameStateManager)
{
_gameStateManager = gameStateManager;
}
public void Execute(IGameStateCommand command)
{
command.Execute(_gameStateManager.GameState);
}
private readonly IGameStateManager _gameStateManager;
}

Here’s how I register it in DI.

public class BindingsInstaller : MonoInstaller<BindingsInstaller>
{
public override void InstallBindings()
{
Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle();
Container.Bind<Loader>().FromNewComponentOnNewGameObject().AsSingle().NonLazy();
// added this line
Container.Bind<ICommandsExecutor<IGameStateCommand>>().To<GameStateCommandsExecutor>().AsSingle();
}
}

Now I implement concrete command which mutates coins value.

public class AddCoinsCommand : IGameStateCommand
{
public AddCoinsCommand(int amount)
{
_amount = amount;
}
public void Execute(GameState gameState)
{
gameState.coins += _amount;
}
private int _amount;
}

Update CoinsView to use AddCoinsCommand instead of the direct modification.

public class CoinsView : MonoBehaviour
{
public Text currencyText;
[Inject]
public void Init(IGameStateManager gameStateManager, ICommandsExecutor<IGameStateCommand> commandsExecutor)
{
_gameStateManager = gameStateManager;
_commandsExecutor = commandsExecutor;
UpdateView();
}
public void AddCoins()
{
var cmd = new AddCoinsCommand(Random.Range(1, 100));
_commandsExecutor.Execute(cmd);
UpdateView();
}
public void RemoveCoins()
{
var cmd = new AddCoinsCommand(-Random.Range(1, 100));
_commandsExecutor.Execute(cmd);
UpdateView();
}
public void UpdateView()
{
currencyText.text = "Coins: " + _gameStateManager.GameState.coins;
}
private IGameStateManager _gameStateManager;
private ICommandsExecutor<IGameStateCommand> _commandsExecutor;
}

Now CoinsView only reads GameState. All mutations are made using commands.

What looks dirty here is a manual call of the UpdateView. We may forget it. Or the GameState may be changed by command from another View.

Let’s add an event of GameState update in ICommandExecutor. Also let’s create a separate alias-interface IGameStateCommandsExecutor, in order to hide extra types of generic interface.

public interface ICommandsExecutor<TState, TCommand>
{
// added event
event System.Action<TState> stateUpdated;
void Execute(TCommand command);
}
public interface IGameStateCommandsExecutor : ICommandsExecutor<GameState, IGameStateCommand>
{
}

Now I need to update registration in DI.

public class BindingsInstaller : MonoInstaller<BindingsInstaller>
{
public override void InstallBindings()
{
Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle();
Container.Bind<Loader>().FromNewComponentOnNewGameObject().AsSingle().NonLazy();
// updated this line
Container.Bind<IGameStateCommandsExecutor>()
.To<DefaultCommandsExecutor>().AsSingle();
}
}

Let’s add an event to theDefaultCommandsExecutor.

public class DefaultCommandsExecutor : IGameStateCommandsExecutor
{
// this event added
public event Action<GameState> stateUpdated
{
add
{
_stateUpdated += value;
if (value != null)
{
value(_gameStateManager.GameState);
}
}
remove
{
_stateUpdated -= value;
}
}
public DefaultCommandsExecutor(IGameStateManager gameStateManager)
{
_gameStateManager = gameStateManager;
}
public void Execute(IGameStateCommand command)
{
command.Execute(_gameStateManager.GameState);
// these lines added
if (_stateUpdate != null)
{
_stateUpdated(_gameStateManager.GameState);
}
}
private readonly IGameStateManager _gameStateManager;
// this line added
private Action<GameState> _stateUpdated;
}

It’s worth to pay attention to implementation of the event. Since executor is sharing the game state only inside the event, it’s important to invoke it right after the subscription.

Finally, update the View.

public class CoinsView : MonoBehaviour
{
public Text currencyText;
[Inject]
public void Init(IGameStateCommandsExecutor commandsExecutor)
{
_commandsExecutor = commandsExecutor;
_commandsExecutor.stateUpdated += UpdateView;
}
public void AddCoins()
{
var cmd = new AddCoinsCommand(Random.Range(1, 100));
_commandsExecutor.Execute(cmd);
}
public void RemoveCoins()
{
var cmd = new AddCoinsCommand(-Random.Range(1, 100));
_commandsExecutor.Execute(cmd);
}
public void UpdateView(GameState gameState)
{
currencyText.text = "Coins: " + gameState.coins;
}
private void OnDestroy()
{
_commandsExecutor.stateUpdated -= UpdateView;
}
private IGameStateCommandsExecutor _commandsExecutor;
}

View doesn’t needIGameStateManager now, since UpdateView takes GameState as an argument. Excellent, we’ve got rid of extra dependency! UpdateView itself I’ve subscribed to the event in IGameStateCommandsExecutor. It’ll be invoked every time GameState changes. Also it’s important to unsubscribe from the event in OnDestroy.

That’s it. Pretty clean and straightforward approach. Now it’s impossible forget to invoke UpdateView in some place, in some who-the-hell-knows condition, which reproduces only in certain phase of the moon.

Nice, you may take a break, then we’ll move forward. There are few more benefits.

Using commands history as a time machine for debugging a complex business logic.

How do you reproduce bugs? You launch the app and follow repro steps. Often these steps are performed manually. Walking from screen to screen, hitting buttons, etc.

It’s ok if the bug is simple or repro steps are easy. But what if the bug depends on network logic and time. For instance, there’s a game event, which runs for 10 minutes. Bug occurs at the end of the event.

Every iteration of testing will take 10 minutes minimum. Usually you need at least several iterations, and between them you fix something.

I’ll show an interesting approach which utilizes all we’ve done so far, and which will save you from the headache.

There’s an obvious bug in the code from previous example. Number of coins can not be negative. Of course, the case is not so difficult, but I hope you have a good imagination.

Imagine, that business logic is complex, and it’s really hard to reproduce a bug every time. But at some point, you or your QA mate stumbled on the bug. What if you could “save” that bug?

Now the trick: save the initial game state you had on game start, and heep a history of all the commands that have been applied to it during a game session.

That data is enough to reproduce the bug as many times as you may need, for the matter of milliseconds. At the same time there’s no need to launch UI at all, since all modifications of the broken game state are stored in commands history. It’s like a small integration test-case.

Now, let’s move to the implementation. Since this approach required more advanced serialization than Unity’s JsonUtility may provide, I’ll install Json.Net for Unity from the Asset Store.

First, let’s create a debug version of the IGameStateManager, which clones initial game state to the separate file.

public class DebugGameStateManager : LocalGameStateManager
{
public override void Load()
{
base.Load();
File.WriteAllText(BACKUP_GAMESTATE_PATH, JsonUtility.ToJson(GameState));
}
public void SaveBackupAs(string name)
{
File.Copy(
Path.Combine(Application.persistentDataPath, "gameStateBackup.json"),
Path.Combine(Application.persistentDataPath, name + ".json"), true);
}
public void RestoreBackupState(string name)
{
var path = Path.Combine(Application.persistentDataPath, name + ".json");
Debug.Log("Restoring state from " + path);
GameState = JsonUtility.FromJson<GameState>(File.ReadAllText(path));
}
private static readonly string BACKUP_GAMESTATE_PATH
= Path.Combine(Application.persistentDataPath, "gameStateBackup.json");
}

Behind the scenes I’ve left the change of parent’s methods to virtual ones. It will be an exercise for you. Also there’s a SaveBackupAs methods which we’ll be used later, so we can save our “Snapshots” with a specific name.

Now let’s create a debug version of executor, which can store everything we need to “replay” a bug.

public class DebugCommandsExecutor : DefaultCommandsExecutor
{
public IList<IGameStateCommand> commandsHistory { get { return _commands; } }
public DebugCommandsExecutor(DebugGameStateManager gameStateManager)
: base(gameStateManager)
{
_debugGameStateManager = gameStateManager;
}
public void SaveReplay(string name)
{
_debugGameStateManager.SaveBackupAs(name);
File.WriteAllText(GetReplayFile(name),
JsonConvert.SerializeObject(new CommandsHistory { commands = _commands },
_jsonSettings));
}
public void LoadReplay(string name)
{
_debugGameStateManager.RestoreBackupState(name);
_commands = JsonConvert.DeserializeObject<CommandsHistory>(
File.ReadAllText(GetReplayFile(name)),
_jsonSettings
).commands;
_stateUpdated(_gameStateManager.GameState);
}
public void Replay(string name, int toIndex)
{
_debugGameStateManager.RestoreBackupState(name);
LoadReplay(name);
var history = _commands;
_commands = new List<IGameStateCommand>();
for (int i = 0; i < Math.Min(toIndex, history.Count); ++i)
{
Execute(history[i]);
}
_commands = history;
}
private string GetReplayFile(string name)
{
return Path.Combine(Application.persistentDataPath, name + "_commands.json");
}
public override void Execute(IGameStateCommand command)
{
_commands.Add(command);
base.Execute(command);
}
private List<IGameStateCommand> _commands = new List<IGameStateCommand>(); public class CommandsHistory
{
public List<IGameStateCommand> commands;
}
private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
};
private readonly DebugGameStateManager _debugGameStateManager;
}

Here you can see that JsonUtility couldn’t handle it. I had to specify TypeNameHandling for serialization settings, so during load/save of the snapshot, commands are a typed objects, because the business logic is tied to the type.

What are other interesting points about this executor?

  • Svaes every command to the history
  • Can save and restore both commands history and state
  • Key method Replay — replays all the commands, applying them to the initial state until command with target index is reached.

I wouldn’t want to waste the memory on debug stuff in release project, thus I register debug service only if there’s #DEBUG define.

public class BindingsInstaller : MonoInstaller<BindingsInstaller>
{
public override void InstallBindings()
{
Container.Bind<Loader>().FromNewComponentOnNewGameObject().AsSingle().NonLazy();
#if DEBUG
Container.Bind<IGameStateManager>().To<DebugGameStateManager>().AsSingle();
Container.Bind<DebugGameStateManager>().AsSingle();
Container.Bind<IGameStateCommandsExecutor>().To<DebugCommandsExecutor>().AsSingle();
#else
Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle();
Container.Bind<IGameStateCommandsExecutor>().To<DefaultCommandsExecutor>().AsSingle();
#endif
}
}

Ah, we need to prepare a command for serialization:

public class AddCoinsCommand : IGameStateCommand
{
public AddCoinsCommand(int amount)
{
_amount = amount;
}
public void Execute(GameState gameState)
{
gameState.coins += _amount;
}
public override string ToString() {
return GetType().ToString() + " " + _amount;
}
[JsonProperty("amount")]
private int _amount;
}

Here I’ve added JsonProperty, since _amount property is private and will not be serialized by default. Also I’ve overrided ToString(), so the command may be logged nicely.

In order to get debug version working, do not forget ot add “DEBUG” define in Player Settings -> Other Settings -> Scripting define symbols.

Then I want to have a way to save and load the history of commands and state right from Unity’s UI. Let’s create a custom EditorWindow.

public class CommandsHistoryWindow : EditorWindow
{
[MenuItem("Window/CommandsHistoryWindow")]
public static CommandsHistoryWindow GetOrCreateWindow()
{
var window = EditorWindow.GetWindow<CommandsHistoryWindow>();
window.titleContent = new GUIContent("CommandsHistoryWindow");
return window;
}
public void OnGUI()
{
// this part is required to get
// DI context of the scene
var sceneContext = GameObject.FindObjectOfType<SceneContext>();
if (sceneContext == null || sceneContext.Container == null)
{
return;
}
// this guard ensures that OnGUI runs only when IGameStateCommandExecutor exists
// in other words only in runtime
var executor = sceneContext.Container.TryResolve<IGameStateCommandsExecutor>() as DebugCommandsExecutor;
if (executor == null)
{
return;
}
// general buttons to load and save "snapshot"
EditorGUILayout.BeginHorizontal();
_replayName = EditorGUILayout.TextField("Replay name", _replayName);
if (GUILayout.Button("Save"))
{
executor.SaveReplay(_replayName);
}
if (GUILayout.Button("Load"))
{
executor.LoadReplay(_replayName);
}
EditorGUILayout.EndHorizontal();
// and the main block which allows us to walk through commands step by step
EditorGUILayout.LabelField("Commands: " + executor.commandsHistory.Count);
for (int i = 0; i < executor.commandsHistory.Count; ++i)
{
var cmd = executor.commandsHistory[i];
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(cmd.ToString());
if (GUILayout.Button("Step to"))
{
executor.Replay(_replayName, i + 1);
}
EditorGUILayout.EndHorizontal();
}
}
private string _replayName;
}

That’s it. Pretty simple. What does it look like?

I’ve saved empty “initial” sate, so I could get back to it in case of something went wrong. Then I’ve hit the buttons couple of times, coins counter has changed. Also you may see a list of commands applied to the game state.

Then I’ve saved final snapshot with a name “version1”.

Then I use “Step to” buttons to “replay” all mutations until specific command.

Now let’s get back to the bug with negative coin values. May testers accidentally stumbled on the bug. I’ve shared the “Save snapshot” button only in Unity, but it may be implemented right in the game UI. In such case the tester may specify a snapshot name “negativeCoins” and then hit “Save” button.

Then they browse to “saves” folder and found two files: negativeCoins.json and negativeCoins_commands.json.

Then they send it to developers. Developers puts those files to the same folder on his machine. In debug window they put “negativeCoins” name, hit Load button and voila. They have a perfect test-case in their hands.

Moreover, you can create an empty scene, without any UI, in which you may “replay” game state snapshots only. This may save your time dramatically.

You may even build an integration process around this approach. For instance, keep a list of “snapshots” which should be tested on every build.

Ok, let’s stop our fantasy. Let’s fix the bug.

public class AddCoinsCommand : IGameStateCommand
{
public AddCoinsCommand(int amount)
{
_amount = amount;
}
public void Execute(GameState gameState)
{
gameState.coins += _amount;
// this is the fix
if (gameState.coins < 0)
{
gameState.coins = 0;
}
}
public override string ToString() {
return GetType().ToString() + " " + _amount;
}
[JsonProperty("amount")]
private int _amount;
}

Let’s check it out on our snapshot, we’ve saved before.

As you see, can not be negative anymore. Win!

Summary

In this article I’ve shared my vision of Command pattern. I believe there are so much ways to apply it. I’ve showed only few out of the bunch I’m using.

In next articles I plan to share more ways to use commands:

  • communication with game server using commands
  • basic logic of command handling between client and server

Also I’ve slightly touched painful UI topic, Flux approach, and reactive approach.

I’ve showed the interesting way of debugging, which may seem as time machine, when you literally replay game state mutations like in debugger.

Combining these patterns, we’ve got a flexible architecture, which is easy to support, refactor and debug. Of course many things may be improved. But it’s up to you.

Also I want to point out that in this case reactive approach in UI and Command pattern dramatically decoupled the system. When I added debug versions of executor and GameStateManager, I literally changed nothing.

UI is a pretty broad topic to cover, and there will be a separate article for that.

Source code you may find in this repository.

If you like the article, do not forget to show it with claps, or write your thoughts in comments :)

Subscribe and don’t miss new content.

My contacts:

--

--

Poisonous John (Ivan Fateev)
GameDev Architecture

Software Engineer with a decade of experience. Former Technical Evangelist at Microsoft. Blog reflects my personal opinion