JavaScript Unity3D

Gameface interprets and executes JavaScript code that can be used for scripting the UI. The execution itself is performed by a JavaScript virtual machine. Gameface uses different VMs on different platforms to provide the best available option. On platforms where code JIT compilation is allowed like Windows, Mac and Android, Gameface uses V8, while on platforms where JIT isn’t allowed it uses the ChakraCore framework.

All currently implemented JavaScript functions and objects can be viewed in the DOM API section of the documentation.

All communication between JavaScript and the game goes through the engine module.

There are two ways for invoking native code from JavaScript and vice-versa.

  • Events
  • Calls

Events

Events allow to call multiple handlers in both directions, but they can not return any value. To register a JavaScript handler for an event use the engine.on method.

C Sharp triggering JavaScript

First, the JavaScript code has to attach a handler for the event using the engine.on:

engine.on('PlayerScoreChanged', function (new_score) {
    var scoreDisplay = document.getElementById('scoreDisplay');
    scoreDisplay.innerHTML = new_score;
});

Then we trigger the event in C#, using the cohtml.View.TriggerEvent method:

// set player score to 10000
view.TriggerEvent('PlayerScoreChanged', 10000);

There are also global functions in the cohtml namespace which can be used such as:

    cohtml.ViewExtensions.TriggerEvent(view, 'ScoreChanged', 10000);

JavaScript triggering C Sharp

Registering C# functions for events triggered by JavaScript should happen in the handler for the cohtml.Net.IViewListener.OnReadyForBindings callback. There is a convenient event that you can subscribe to - cohml.ViewListener.ReadyForBindings;

Event Handlers

Event Handlers are registered with the cohtml.Net.View.RegisterForEvent method. They cannot return any value to JavaScript but may trigger an event for JavaScript. There may be more than one C# handler for a given event. There also may be both C# and JavaScript handlers for the same event.

public class Game : MonoBehaviour
{
    void Start()
    {
        viewComponent.Listener.ReadyForBindings += RegisterEvents;
    }

    void RegisterEvents()
    {
        viewComponent.NativeView.RegisterForEvent("OnQuitClicked", (System.Action)Quit);
    }

    void Quit()
    {
        Application.Quit();
    }
}

Triggering the event from JavaScript looks like this:


engine.on('OnQuitClicked', function () {
    ShowMessage('Bye');
});

function OnClick() {

    // this will execute both Game.Quit in C#
    // and ShowMessage('Bye') in JavaScript

    engine.trigger('OnQuitClicked');
});

Call Handlers

Promises are used to return results from C# to JavaScript. Gameface promises are modeled after the Promises A specification.

Call handlers are registered with the cohtml.Net.View.BindCall method. There may be only one handler for a given call and the handler may return a result.

public class Game : MonoBehaviour
{
    void Start()
    {
        viewComponent.Listener.ReadyForBindings += RegisterEvents;
    }

    void RegisterEvents()
    {
        viewComponent.NativeView.BindCall("getPlayerName",
            (System.Func<string>)GetPlayerName);
    }

    string GetPlayerName()
    {
        return m_PlayerName;
    }
}

To get the player name in the view:


// call 'Game.GetPlayerName' with callback for the result
engine.call('getPlayerName').then(function (name) {
    var playerName = document.getElementById('playerName');
    playerName.innerHTML = name;
});

Exposing C Sharp Types

To be able to use your C# types as arguments or results for the event and call handlers, the C# type must be exposed to Gameface. To expose a C# type to Gameface you should add the cohtml.Net.CoherentType attribute above the type.

namespace MyGame
{
[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; }

    // Health remains invisible to JavaScript
    public double Health;
}
}

C Sharp and JavaScript Communication

After registering the Player type, events can be triggered in both directions with instances of Player as an argument and call handlers can return Players as return types.

public class Game : MonoBehaviour
{
    Player playerOne;

    void Start()
    {
        viewComponent.View.TriggerEvent("StartWithPlayer", playerOne);
    }
}

Then in JavaScript, we receive the object with the specified properties. The value of each property is the same as at the moment of triggering the event. The JavaScript callback may store a reference to the object, but its properties WILL NOT be synchronized with the actual Game.playerOne in the game.

engine.on('StartWithPlayer', function (player) {
    var playerName = document.getElementById('playerName');
    playerName.innerHTML = player.name;

    var scoreDisplay = document.getElementById('scoreDisplay');
    scoreDisplay.innerHTML = player.score;
});

If you want to call the C# handler with an instance of Player created in JavaScript there is one important detail - the object must have a property __Type with the value Player.

function CreatePlayer() {
    var player = {
        __Type: 'MyGame.Player', // set the type name
        name: "MyTestPlayer",
        score: 0
    };
    engine.call('CreatePlayer', player).then(function (success) {
        if (success) {
            ShowMessage('Welcome ' + player.name);
        } else {
            ShowMessage('Sorry, try another name');
        }
    });
});
bool CreatePlayer(Player player)
{
    if (player.Name.Contains(" "))
    {
        // sorry, no spaces in player name
        return false;
    }

    // create the player
    return true;
}

Customizing Promises

Custom promises have been deprecated! Now we are supporting ECMAScript 6 promises

JavaScript extensions

Gameface provides extensions on top of the vanilla DOM-related JavaScript as defined by the standard. The extensions are aimed to provide better performance and a more natural workflow to UI developers. The prime example of such an extension is the “Node.leftXX/Node.topXX” method. In vanilla JS, to update the position of an element you have to call Node.top/Node.left, however, the functions take a string parameter. To update an element’s left property you have to do:

myNode.left = newPos + "px";

The newPos variable is a number in pixels, but the runtime will have to convert it to a string, append the ‘px’ specifier and send it to the native code. The native code at that moment will convert it back to a number to re-calculate the position. This is very inefficient, it creates JavaScript garbage and unnecessary work.

In Gameface you can just say:

myNode.leftPX = newPos;

No garbage is generated and the calculation is much faster both in JS and native code. A complete list of extensions is available in the DOM API section of the documentation.