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 JavaScript 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 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# triggering Javascript overview
First the JavaScript code has to attach a handler for the event using 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 10000view.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# overview
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 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.View.RegisterForEvent("OnQuitClicked", (System.Action)Quit);}void Quit(){Application.Quit();}}
Triggering the event from JavaScript looks like:
engine.on('OnQuitClicked', function () {ShowMessage('Bye');});function OnClick() {// this will execute both Game.Quit in C#// and ShowMessage('Bye') in JavaScriptengine.trigger('OnQuitClicked');});
Promises are used to return results from C# to JavaScript. Gameface promises are modeled after the PromisesA 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.View.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 resultengine.call('getPlayerName').then(function (name) {var playerName = document.getElementById('playerName');playerName.innerHTML = name;});
BindCall
with engine.call
when you have single handler and RegisterForEvent
with engine.trigger
when you have more that one handlers.To be able to use your C# types as arguments or results for 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 propertiesstruct Player{[CoherentProperty] // expose namepublic string name;[CoherentProperty("score")] // expose Score as "score"public double Score { get; set; }// Health remains invisible to JavaScriptpublic double Health;}}
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 in 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 C# handler with an instance of Player
created in JavaScript there is one important detail - the object must have a property __Type
with 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 namename: "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 namereturn false;}// create the playerreturn true;}
Custom promises have been deprecated! Now we are supporting ECMAScript 6 promises
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 are the Node.leftXX/Node.topXX methods. 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 JavaScript DOM API section of the documentation.