Utilizing the binding and data binding feature in Unity
Currently, we can establish the data communication in Unity3D between the C# and the JavaScript in Gameface through Events and Calls. To achieve this, you have to prepare the data on the C# side and then send it over to the JS side to bind it to the elements. Once you receive the data on the frontend side, you can map it to your elements by easily modifying their innerHTML or style properties.
In the following example, we will demonstrate how you can share the data between the C# and the JavaScript code in Unity.
First, we will define a custom struct that will hold the necessary data for the UI:
[CoherentType]
struct Item
{
[CoherentProperty] // expose name
public string name;
// Health remains invisible to JavaScript
[CoherentProperty]
public int count;
}
[CoherentType] // by default explicitly expose properties
struct Player
{
[CoherentProperty] // expose name
public string name;
[CoherentProperty("score")] // expose Score as "score"
public double Score { get; set; }
[CoherentProperty]
public double health;
[CoherentProperty]
public List<Item> inventory;
}
The key points here are that you will need to define the special macros above the struct/class you wish to expose to the UI with the “[CoherentType]” keyword. After that, each of the properties of that class/struct that will be exposed to the UI must have the “[CoherentProperty]” keyword with an optional string to define how the C# data will be exposed.
Now, we will populate the structures we have created with data to have something to receive on the front-end side:
private void prepareData()
{
backendPlayerData = new Player();
backendPlayerData.health = 100;
backendPlayerData.name = "Player_1";
backendPlayerData.Score = 0;
backendPlayerData.inventory = new List<Item>();
for (int i = 0; i < 50; i++)
{
Item newItem = new Item();
newItem.name = "Item_" + i;
newItem.count = Random.Range(0, 10);
backendPlayerData.inventory.Add(newItem);
}
}
Once the data is populated, we will send it to the JavaScript code using an event where we will pass a “playerData” object of type Player:
void sendData()
{
myView.View.TriggerEvent("SendPlayerData", backendPlayerData);
}
As soon as the event is invoked from the C# code with TriggerEvent, we must have a handle on the JavaScript side that will intercept it. This is done by defining an “engine.on(<string>)” handler with a string matching the first argument of the TriggerEvent API call on the C# side:
engine.on('SendPlayerData', function (PlayerData) {
var PlayerName = document.getElementById("name");
PlayerName.innerHTML = PlayerData.name;
// Using the data manipulate all of the UI elements accoridngly
});
Although a straightforward approach, it might not be optimal in complex UI's where lots of data is transferred to the frontend and mapped to elements. This can cause a lot of work on the JavaScript side to fetch the elements, assign the data, and create garbage, making the Garbage Collecting trigger more often. This can ultimately impact the performance negatively.
Luckily, there is an alternative approach in Gameface that allows you to achieve the same results using the feature called `data-binding`. This feature is fully available in C++ environments as it allows you to efficiently transfer data between C++ and JS, acting as a binding layer. However, we have not implemented such a binding layer in Unity 3D yet (due to the performance limitations linked with the “C# <-> JavaScript <-> C++” communication). Currently, in Unity, this feature is only available on the JavaScript side and allows you to create objects that hold data (let’s refer to them as Data Models) and connect their properties to the UI elements with the help of data-bind attributes.
An example based on the screenshot above - instead of fetching the “name” element and modifying its `innerHTML`, you can instead assign the `PlayerData.name` value with the `data-bind-value` attribute:
<div data-bind-value="{{PlayerData.name}}"></div>
This way, the value will be passed to our internal library, meaning it will be processed and applied on the C++ side instead of the JavaScript one.
To create the JavaScript object with the Player data from the C# code, you should use the “engine.createJSModel” function which receives a name of the object as a first argument and a JavaScript object with different properties.
engine.createJSModel('PlayerData', {
name: 'Player',
health: 100,
score: 50,
inventory: [{
name: "Stick",
count: 5
}, {
name: "Stone",
count: 8
}]
});
To populate the PlayerData object with the C# data you can hook to the event that will send the data and create the model there instead:
engine.on('SendPlayerData', function (PlayerData) {
engine.createJSModel('PlayerData', {
name: PlayerData.name,
health: PlayerData.health,
score: PlayerData.score,
inventory: PlayerData.inventory
});
Once you have prepared the JavaScript data model, you must utilize the engine.updateWholeModel(PlayerData) API and the engine.synchronizeModels() API to notify our plugin to synchronize the data models and update the HTML elements linked to them.
engine.updateWholeModel(PlayerData);
engine.synchronizeModels();
When modifying the model’s values, you need to utilize both updateWholeModel and synchronizeModels APIs to sync the HTML and JavaScript data. In the example below, we are changing the Player’s score each second and update the model to sync the data in the HTML code:
setInterval( () => {
PlayerData.score++;
engine.updateWholeModel(PlayerData);
engine.synchronizeModels();
}, 1000);
The above is a simple use case where a single property is bound to the HTML element. What about creating HTML elements based on a collection of items that we send from the C# side? You can achieve this with the help of the `data-bind-for` and `data-bind-value` attributes:
<div data-bind-for="item:{{PlayerData.inventory}}">
<div data-bind-value="{{item.name}}"> </div>
</div>
You can find more information on our native documentation regarding the rest of the `data-bind-*` attributes that allow you to change the structure of the DOM, elements’ style, CSS classes and more.
https://coherent-labs.com/Documentation/cpp-gameface/d1/ddb/data_binding.html
Additionally, you could check out the Virtual List and Custom Data binding attributes sections for more examples that can be useful when making complex UI.
Finally, you can also synchronize the JavaScript data with the C# one by sending the whole object with an event or call.
You will need to register a delegate in C# for your call/event. You can customize your delegates based on whether you want parameters or return types using System.Func and System.Action
myView.View.BindCall("SendToBackend_1", (System.Action<Player>)ReceiveDataFromFrontend_1);
myView.View.RegisterForEvent("SendToBackend_2", (System.Action<Player>)ReceiveDataFromFrontend_2);
Where the bodies of the functions are the following ones “manipulating” the JavaScript data:
void ReceiveDataFromFrontend_1(Player p)
{
Debug.Log("Single call no return type" + p.name);
}
private void ReceiveDataFromFrontend_2(Player player)
{
Debug.Log("Trigger - " + player.name);
}
Once you have everything from the C# ready, the last step would be to send the JavaScript data for processing in the C# code:
PlayerData.name = "Change from JS!";
engine.call('SendToBackend_1', PlayerData);
engine.trigger('SendToBackend_2', PlayerData);
Normally, in a C++ environment, you can skip this step since you can create data models from C++. The updateWholeModel and synchronizeModels APIs allow you to keep this data synchronized between both ends(C++ and JS). We have plans in the future to evaluate if this can be implemented for C# as well.
Please sign in to leave a comment.
Comments
0 comments