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;
});
BindCall
with engine.call
when you have a single handler and RegisterForEvent
with the engine.trigger
when you have more than one handler.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 Player
s 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.
__Type: Player
will look for this type in the global namespace. If your type resides in a namespace or inside a class you should prefix the type: namespace.class.type
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.