Structural Data Binding
In a previous two-part blog post, we talked about Data Binding Common Game UI. We gave you a preview of Coherent’s declarative data binding and several examples of its use. Now, we will discuss in detail what data-binding actually is and how it works in Coherent GT and Hummingbird.
To start with, take a look at our changelog. Can you see the pattern there? All versions since 1.9 have improved upon the data-binding system. It is definitely the feature we iterate the most on, the reason being that we hear incredible feedback on how much better it is compared to our vanilla system for transferring data between the game and the UI.
In turn, we decided to spend a few minutes and introduce the data-binding system to anyone who has nоt seen it. Besides, we know all of you are eagerly waiting for GT 2.0 so this will be a good distraction.
What is data binding?
A technique in programming that has become so popular with the rise of the MV* patterns in recent years that it now has a wikipedia page. In short, it is a system which allows its users to synchronize some data (commonly known as the data model) with its consumer. When it comes to the UI that would mean synchronizing the game’s state with the game’s UI:
- You have got a health bar which changes text and width to represent the player’s health and you want to update both values whenever the conditions change.
- You have got an in-game shop. The player has a fixed amount of currency and can buy some of the items if they cost less than what he has. Whenever a purchase is made, you want your UI to reflect the action. It is the same whenever an item is added/removed and it has to be visually displayed as such:
Lastly, you want the system to synchronize the game’s state and UI without any unnecessary interaction. The system should be able to generalize well and let you bind data to various visual attributes such as text, dimension, position, color, 3D transform, etc. You should not need to write code – the system is equipped to take care of it. What is needed is some declarative way to specify the data dependencies.
Data binding in GT & Hummingbird
Both GT and Hummingbird support data binding (albeit GT has a few more features). Both use custom HTML attributes to declare the data dependencies as shown
1 |
<div data-bind-value="{{player.health}}"></div> |
which will bind the players’ health with the div element’s text so whenever the status changes it will update the div. Aside from data-bind-value, we also have several other visual attributes that affect standard CSS properties – width, height, background-image, etc. Unsurprisingly and unoriginally, we call those styling data bind attributes.
Styling attributes are really simple to use and you can find more useful information on the matter in our docs along with some information about the C++ code. After all is clear, let us focus on our so-called structural data binding.
Structural data binding
This category encompasses attributes which are related to modifying the DOM tree / UI structure, not just its visuals. Examples include conditionally showing / hiding elements or as mentioned earlier – populating lists of items in your inventory/shop/action bar.
GT has 3 attributes which fall in the structural data binding category, HB has 2.
The most simple one is data-bind-if. This one allows you to conditionally display an element. Consider an FPS game in which the player’s screen flashes in red when he is low on health. How would you implement that with GT/HB? Well, add a div, style it accordingly and tell the system to only display it when they are low on health.
1 2 3 4 5 6 7 8 9 10 11 12 |
<style> @keyframes blinker { 50% { opacity: 0; } } #red-flashing-screen-overlay { color: red; width: 100%; height: 100%: animation: blinker 1s linear infinite; } </style> <div id=”red-flashing-screen-overlay” data-bind-if=”{{player.health}} < 0.2”></div> |
As straightforward as possible!
If there is data-bind-if, there is got to be a data-bind-for! Consider a scoreboard in any type of game – it should display data about different player stats (kills, deaths, k/d ratio, objectives captured, etc.). Here is an example of a 5-line implementation:
1 2 3 4 5 6 7 8 9 |
<!-- Go through all players; The syntax is equivalent to for (auto& player : game.players) in C++ or for (let player of game.players) in JavaScript --> <div id=”scoreboard” data-bind-for=”player: {{game.players}}”> <span data-bind-value=”{{player}}.name”></span> <span data-bind-value=”{{player}}.kills”></span> <span data-bind-value=”{{player}}.deaths”></span> </div> |
Let’s do one more! Imagine if you have a list of items with a different rarity – as in card games for instance. Some cards are common, others are rare and we will call the very best cards – legendary. You want to not only display the card’s name, power, and effects but also an animated glow around its borders:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<style> .common { border: none; } .rare { border: 2px solid orange; } .legendary { /* Spritesheet animation for the animated glow*/ } </style> <div id=”cardlist” data-bind-for=”card: {{player.deck}}”> <!-- data-bind-class is a styling attribute which applies the CSS class with the same name as the value --> <div data-bind-class=”{{card}}.rarity”></div> <span data-bind-value=”{{card}}.name”></span> <span data-bind-value=”{{card}}.effects”></span> </div> |
Neat, isn’t it? All of the above use cases are shown in our data binding and components samples.
To see them in action, download our latest version from the dev portal if you are a GT licensee or request a demo if you are not.
We appreciate your feedback, so keep it coming! Let us know your thoughts on this blog post by tweeting @CoherentLabs or commenting below!