With some proper equipment we are ready to go!

UI Window Management in Unity 5.3 with Data Bind (Part 3)

So we got our window management system in a nice, generic state already during part 1 and part 2 of this blog post series. This (final?) part adds our asset Data Bind for Unity to the equation. This makes sure that not only our UI setup is capsuled in its own scene, but the data the window expects is passed to it when it is opened. This way each window becomes very modular: It can be tested independent from the rest of the game and you can put it in and out of your project pretty easy with just a few lines of code. Check out the source code of the fully functional example.

One context per window

During this blog post I will add an inventory window to the example project. If you haven’t yet, you should pull/download the sources from the repository to follow the descriptions more easily.

Similar to what we decided in the first part (one window, one scene) we will introduce one dedicated context for each UI window. Those contexts will be called WindowContexts to separate them from other ones.

Therefore, for our inventory window, let’s create an InventoryWindowContext:

 

As you can see I already defined some commands that can be executed by the user of this window:

  • Close the window
  • Assign an item to an equipment slot

The context itself doesn’t force any specific kind of visualization/input. Your UI designer will be free to add any kind of user interface he can think of, from a touch drag & drop to a command line presentation. The context just provides the commands he can execute and the data he can display. Speaking of data…

Data Context vs. Window Context

We named our context WindowContext because it is a bit different to other contexts, namely the game logic data contexts. It is highly recommended to split those two as they take over different functions.

The game logic data contexts contain the data of the objects in your game. They can be created only once and updated in one place. If you do this, you can share the context everywhere it is visualized.

For example you may use an InventoryContext  to store the inventory data in. It is, of course, used in the inventory window. But you may as well use it when visualizing the player character to increase the size of his backpack when his inventory grows. Furthermore there may be an indicator in the main HUD that warns the player when his inventory gets too full.

The different presentations of your inventory don’t have to manage the inventory context itself, but it is only updated in one place depending on the changes in the game logic and its reference is shared.

On the other side, a window context is (as we defined earlier) dedicated to a specific window/UI. It defines the commands that can be executed in this window and the data that can be visualized. Most of the time this data consists of references to game logic data contexts. In our inventory window we have two main data contexts:

  • Inventory
  • Equipment

Those contexts are pure data holders of the game data. You can imagine them as the view your presentation side has on the game.

 

The concrete example

Let’s get our hands on the inventory example now to see how things work out there. You have already seen the window and data contexts we use in our example. To simulate how things would work in a real project, I added two classes that take over the role of the logic and presentation side.

 

We should equip some stuff!
We should equip some stuff!

 

GameLogic

This class capsules our logic. In a real game you would have a much bigger architecture here, of course. But probably you will have a way to send commands into your logic (simulated by the methods of the class) and have a way to be informed about important events that occur in the game (simulated by the events of the class).

In the example it is possible to equip an item from the inventory. The equip action considers the case that there is already an item equipped and puts the old item back into the inventory.

In a real game there would be a lot more logic probably, e.g. checking if the item can be put in the specified equipment slot and so on.

InventoryView

This class represents the view of our game. It is responsible for keeping the data contexts up-to-date and opening/closing the inventory window if requested. It also calls the commands of the game logic if issued by the user through the inventory window.

When the InventoryView  starts, it registers itself for the events of the GameLogic  and initializes the InventoryContext  with the data from the GameLogic . In our example the game data is pretty similar to the data we store in the context. In a real game though the data might be stored in a very different way. So you shouldn’t work on the context directly, instead they should only be used as a bridge between your game data and the data that is presented to the user.

The code in the callbacks of the events is pretty simple and just adjusts the values of the data contexts depending on the type of event.

Executing game logic

On the other hand the commands from the UI have to be forwarded to the game logic to execute an action. In our case we have only a single action which is equipping an item in an equipment slot.

Therefore we listen to the drop event of the EquipmentSlotContext s. Whenever an item is dropped on an equipment slot we call the EquipItem  method in the game logic. That’s all the work the presentation side has to do because data changes that might occur by the action will be already communicated via the events the view registered for on initialization.

With some proper equipment we are ready to go!
With some proper equipment we are ready to go!

Testing game logic

The big advantage of a clean separation is testing. Our game logic can run completely on its own without any dependency on a specific kind of presentation. This means it is very easily testable with unit tests to guarantee its functionality.

Testing UI

The game logic isn’t the only thing that can be tested separately. We can also test the UI windows on their own by simply providing some test contexts. If you open the Inventory scene for example, you will see a Debug node. Underneath there is an EventSystem to run the window scene on its own and a second node called “Dummy Data”.

This node creates a context ( DummyInventoryWindowContext ) on startup and puts it in the context holder of the inventory window. This is the same thing that happens in the real game, so you can test specific scenarios of data in a window easily. It makes designing the UI a lot easier as you don’t have to start up your game each time, but just run your scene. But still you can use similar data as the one the UI gets in the game.

Conclusion

Using Data Bind for Unity to provide the data for your UI windows isolates the windows even more and makes them completely testable on their own before they are even added to your game. This is especially useful for your UI designers as they don’t have to start the game every time they changed the UI, but they just start the scene of the UI they are designing.

The example project contains a Dll of Data Bind, if you like to supports the development and get the full version with its source code check out the Unity Asset Store 🙂

As always: If you have any questions or feedback, don’t hesitate to write a comment or get in touch!