I already wrote some posts about data binding and our Unity asset Data Bind for Unity which gives you a clean way to separate your logic from the presentation of your data. The last parts were mostly about the logic side though, so let’s have a look how to combine those two.
Installing Data Bind
As soon as you’ve got the asset from the asset store, you can just unzip it and will have a new folder “Slash.Unity.DataBind” in your Assets folder. Everything DataBind related is in there.
The repository contains a stripped version of the asset, so you can still check how it is used. If you like it and want to use it in your own projects, consider to support us and buy it in the store. You’ll get many more tools at your hand with the full asset 🙂
Creating a HUD
When connecting logic and presentation of your game, you can approach this connection from both sides. Either you create the context first and setup all the data that should be available for the presentation. Or you create the presentation first and connect it with the correct data afterwards.
Most of the time I take the latter approach. It is easy to think of what the player should see in the end, especially UI-wise. So let’s start using the new Unity UI to create a nice HUD for the player where he can see how many bullets he has left, how many he can have at maximum and an indicator that shows up when the player should reload (i.e. has only one bullet left).

As you can see I’m not a UI designer 😉 Luckily there are some free resources available on the internet, so I didn’t have to draw myself. But you should get the idea. The player will see opaque bullets for the ones that are still in his clip and grayed out bullets for the ones he already shot. The alarm sign will be visible when he has only one bullet left or when his clip is empty.
Creating the data context
Designing the UI often makes clear already which data you will need to fill it. In our small HUD we need only two data values:
- Current number of bullets
- Maximum number of bullets
So our data context will be quite small, too:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public class WeaponContext : Context { #region Fields private readonly Property<int> currentBulletCountProperty = new Property<int>(); private readonly Property<int> maxBulletCountProperty = new Property<int>(); #endregion #region Properties public int CurrentBulletCount { get { return this.currentBulletCountProperty.Value; } set { this.currentBulletCountProperty.Value = value; } } public int MaxBulletCount { get { return this.maxBulletCountProperty.Value; } set { this.maxBulletCountProperty.Value = value; } } #endregion } |
If you are using DataBind more regularly, you should definitively add the code snippets we provide, so you can create new data properties even faster.
Back to our HUD UI scene. Now that we have our data available, we can use it to steer our UI. Therefore just put a ContextHolder on the root object. This is where the data context will be put.
To make editing the bindings easier, you can already tell the ContextHolder which type of context will be placed here. This way DataBind can already find out which data is available and give you some drop down fields to define the data paths. Otherwise you would have to type in the path manually which is a bit error-prone and less comfortable.

Adding bindings
Reload indicator visibility
Let’s setup the reload indicator first. As we said it should only be shown if the weapon has one or none bullets left. DataBind already comes with the most common scripts on board, so we don’t have to write a single line of code to achieve this behavior.
The first script we use is the ActiveSetter. It enables/disables a specified game object depending on a boolean data value. There are three ways where this value can come from:
- Data Context
- Constant
- Data Provider
In our case we are not using a value from the data context directly but need an additional script, the ComparisonCheck. This script acts as a data provider by comparing two data values which again can come from different data sources. Here we use the data value “CurrentBulletCount” from the data context and the constant value 2 and compare both with the lesser than comparer.

Note: A common mistake (which I made myself a lot of times) is to put the ActiveSetter on the same GameObject which should be activated/deactivated. This will deactivate it correctly but never activate it again, as the script itself is deactivated together with the GameObject.
Bullet count display
We like to show bullets for each one left in the weapon and grayed out ones for already used ones. We can use the same way for both, using the built-in LayoutGridItemsSetter script. This scripts creates a child GameObject from a specified prefab for each item in a collection or (in our case) as many GameObjects as the data value specifies.

The available bullets use a normal bullet for display and the CurrentBulletCount instead of the MaxBulletCount data value from the context.
Testing the HUD
When you design your interfaces the data to fill it might not be available yet or it is too tedious to always start your game and go to the screen you are designing to check for your changes.
As the data context completely hides the underlying logic from the presentation you can easily create dummy contexts to test your UI without even starting the game. Just derive it from your real context class and put in some test values.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class DummyWeaponContext : WeaponContext { #region Constructors and Destructors public DummyWeaponContext() { this.CurrentBulletCount = 3; this.MaxBulletCount = 10; } #endregion } |
There is a script called ContextHolderInitializer which will initialize a specific ContextHolder with an instance of a context on startup. It was created for exactly this use case to test a UI with a dummy context.

Now if you hit the play button, the context holder gets initialized with the DummyWeaponContext and the UI displays your testing values.

Using the data context
Updating the data when things change
Right now we have our logic working and our data context works together with the UI. The missing part is to connect our logic to the data context itself, so the data in it is updated from the logic.
For this case we created a new base class in our previous projects which connects a data context to the game logic. With the Slash framework this works via events and the EntityManager. If you are using a different framework to implement your logic, you may have to adjust this base class accordingly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
/// <summary> /// Context to connect a data context to the game logic via events. /// </summary> public abstract class GameContext : Context { #region Fields /// <summary> /// Events to register for at event manager instances. /// </summary> private readonly Dictionary<object, EventManager.EventDelegate> events = new Dictionary<object, EventManager.EventDelegate>(); #endregion #region Properties /// <summary> /// Entity manager to access data from client logic. /// </summary> protected EntityManager EntityManager { get; set; } /// <summary> /// Client event manager provided to all view models. /// </summary> protected EventManager EventManager { get; set; } #endregion #region Public Methods and Operators public virtual void Deinit() { // Clear event handlers. if (this.EventManager != null) { foreach (var gameEvent in this.events) { this.EventManager.RemoveListener(gameEvent.Key, gameEvent.Value); } } } /// <summary> /// Sets up the connection to the client logic. /// </summary> /// <param name="eventManager">Event manager to communicate with the client logic.</param> /// <param name="entityManager">Entity manager to access data from client logic.</param> public virtual void Init(EventManager eventManager, EntityManager entityManager) { // Setup event callbacks. this.EventManager = eventManager; this.EntityManager = entityManager; if (this.EventManager != null) { this.SetEventListeners(); } if (this.EventManager != null) { foreach (var gameEvent in this.events) { this.EventManager.RegisterListener(gameEvent.Key, gameEvent.Value); } } } /// <summary> /// Registers callbacks for interesting game events. /// </summary> public virtual void SetEventListeners() { } #endregion #region Methods /// <summary> /// Registers the passed callback for events with the specified key. /// </summary> /// <param name="eventKey">Key of the events to register to callback for.</param> /// <param name="callback">Callback to register.</param> protected void SetEventListener(object eventKey, EventManager.EventDelegate callback) { this.events.Add(eventKey, callback); } #endregion } |
Our weapon context now derives from this GameContext instead of the Context class and listens to events to update its data when things change:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public override void SetEventListeners() { this.SetEventListener(WeaponEvent.Fired, this.OnWeaponFired); this.SetEventListener(WeaponEvent.Reloaded, this.OnWeaponReloaded); } private void OnWeaponFired(GameEvent e) { var data = (WeaponFiredData)e.EventData; // Get weapon component if not already set. if (this.weaponComponent == null) { this.weaponComponent = this.EntityManager.GetComponent<WeaponComponent>(data.WeaponEntityId); } // Update data. this.UpdateBulletCounts(); } private void OnWeaponReloaded(GameEvent e) { var weaponEntityId = (int)e.EventData; // Get weapon component if not already set. if (this.weaponComponent == null) { this.weaponComponent = this.EntityManager.GetComponent<WeaponComponent>(weaponEntityId); } // Update data. this.UpdateBulletCounts(); } private void UpdateBulletCounts() { this.CurrentBulletCount = this.weaponComponent != null ? this.weaponComponent.BulletCount : 0; this.MaxBulletCount = this.weaponComponent != null ? this.weaponComponent.MaxBulletCount : 0; } |
Creating the data context
The data contexts should be created right after the game was created, so they don’t miss any important events, e.g. for initialization. Most of the time you will have a hierarchical context structure, starting at one root context which creates all the underlying contexts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public class GameRootContext : MonoBehaviour { #region Fields public GameBehaviour GameBehaviour; #endregion #region Properties public WeaponContext Weapon { get; set; } #endregion #region Methods protected void OnDisable() { this.GameBehaviour.GameChanged -= this.OnGameChanged; } protected void OnEnable() { this.GameBehaviour.GameChanged += this.OnGameChanged; var game = this.GameBehaviour.Game; if (game != null) { this.CreateContexts(game); } } private void CreateContexts(Game game) { this.Weapon = new WeaponContext(); this.Weapon.Init(game.EventManager, game.EntityManager); } private void OnGameChanged(Game newGame, Game oldGame) { this.CreateContexts(newGame); } #endregion } |
The root context also takes care about the recreation of the data contexts when a new game was started. We put it on a game object in the Root scene.

When the HUD scene is loaded, its ContextHolder has to be initialized with the correct context. In bigger projects you will have a window management or something similar which takes care about this. Right now we will just attach a small script which searches for the root context and sets its weapon context to the ContextHolder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class WeaponContextInitializer : MonoBehaviour { #region Fields public ContextHolder ContextHolder; public GameRootContext RootContext; #endregion #region Methods protected void Start() { if (this.RootContext == null) { this.RootContext = FindObjectOfType<GameRootContext>(); } if (this.ContextHolder == null) { this.ContextHolder = this.gameObject.GetComponent<ContextHolder>(); } if (this.RootContext != null) { this.ContextHolder.Context = this.RootContext.Weapon; } } #endregion } |
Loading the HUD
The last tiny bit is to load the HUD scene. The Slash framework contains a small script called LoadSceneBehaviour which can load a scene additionally. We can just use this to load the HUD scene into the Root scene on startup.

Now if you start the game the contexts are created by the GameRootContext script, the LoadSceneBehaviour loads the HUD scene and the WeaponContextInitializer sets the weapon context on the ContextHolder of the HUD UI. This triggers the update of the controls in the UI with the provided data.
If you fire your weapon you should see the UI react accordingly. The reload indicator should be visible when you have less than 2 bullets left.

Conclusion
Visualizing the data of your game with Data Bind for Unity is a task which is very separated from creating your logic. This is quite important when working on a bigger project as you don’t want the presentation to interfere with your logic.
After you have created reasonable data contexts that define which data the presentation side of your game has access to, you don’t have to write a lot of code anymore. For the main use cases there are already scripts in the Data Bind package, e.g. to hide GameObjects or to instantiate UI elements based on the values in your context. When the data values change the UI elements are updated accordingly.
From our personal experience this clean separation has already saved us a lot of time, especially when the UI changes during a project. In that case you just have to adjust the UI design and don’t have to touch your code. So there is much less potential to introduce new bugs.
There will be another update to 1.0.4 soon which adds more helpful scripts that we created along with our projects and I will raise the price to 20$ with that update because of the bigger content. So if you want to try out the whole power of Data Bind for Unity, consider purchasing it now from the Unity Asset Store to save some money. This will support us in further developing our assets and doing the extra work to put them together in a package which easy for others to use.
If you have any questions or comments on the topic, just leave a comment, write me mail or leave a post in the official Unity forum thread.