Communicating with the engine

Prysm interprets and executes JavaScript code that can be used for scripting the UI. This mechanism allows you to call operations from the embedded JavaScript to an HTML page, which in turn can call C# functionalities, such as loading a new scene in your Unity3D project, changing a setting in project settings, retrieving data information from the server associated with your project, and so on. This feature enables seamless communication between the game engine and the frontend, ensuring your engine and UI stay in sync. You can easily communicate from the game engine to the frontend and from the frontend to the game engine, making it simple to maintain synchronized operations and data flow throughout your project.

How to use

All communication between JavaScript and the game goes through the engine module in the cohtml.js JS script. This file will be automatically added when you add at least one CohtmlView to your Unity3D project for the first time. You can find it at Assets/StreamingAssets/Cohtml/UIResources/Hello/js/cohtml.js. To prepare CohtmlView to work with event bindings, follow these steps:

  1. Create an empty HTML page in your Unity3D project at Assets/StreamingAssets/Cohtml/UIResources/.
  2. Add a reference to cohtml.js at the end of the body tag of the HTML page.
  3. Create a new <script> tag or reference a new script that will contain your binding logic.
  4. In the script, you need to add the function engine.whenReady . This function will be triggered when the View is ready to receive binding events.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <script class="scripts" src="[relative_path_to_the_file]/cohtml.js"></script>
  <script>
    engine.whenReady.then(() => { 
        // The View's JS is ready for binding
     });
  </script>
</body>
</html>
  1. In the Unity3D editor, add a CohtmlView component to any scene.
  2. Set the newly created page in the URL field of the CohtmlView.
  3. Create a C# MonoBehaviour script and attach it as a component to the game object where the CohtmlView is located. Add a reference to the component:
  4. Subscribe to CohtmlView.Listener.ReadyForBindings delegate. This method will be called when the Cohtml View is ready to accept JavaScript bindings.
public class EventBinding : MonoBehaviour
{
	public CohtmlView cohtmlView;
    private void Start()
    {
        cohtmlView = GetComponent<CohtmlView>();
        cohtmlView.Listener.ReadyForBindings += () =>
        {
            // The View C# is ready for binding. You should register for your binding events here
        };
    }
}

Binding

There are two ways to invoke a game code from JavaScript and vice versa:

  • Events: Used to subscribe to the same event from one or more places. It does not return a result.
  • Calls: Used to call only one registered handler and can return a result from it. Can be called with zero, one, or more arguments. For this purpose, in JS, you must use a promise to handle the result from the C# handler.

Events

Event can be fired with or without parameters, can have multiple handlers, and does not return a result.

Example usage

JS triggers the C#

Let’s create a handler in C# and trigger it from JS. The following diagram visually how an event would be created and sent from the UI to the game.

This example will create an event that notifies C# that JavaScript is ready to listen for triggered events. To achieve that please follow these steps:

  1. In the CohtmlView.Listener.ReadyForBindings delegate callback register the event handler using the CohtmlView.NativeView.RegisterForEvent method:
    • The first parameter is the name of the event, which you will trigger from the JS code. For the example will name it TriggerC#Event_OnViewJSIsReady
    • The second parameter is the handler delegate, which will be the method executed after the event is triggered. The type of the method callback will be an Action without parameters.

The event handler will look like this:

cohtmlView.NativeView.RegisterForEvent("TriggerC#Event_OnViewJSIsReady", (Action)OnViewJSIsReady);

The method callback will look like this:

private void OnViewJSIsReady()
{
      // The View's JS is now ready for subsequent triggers from both directions.
}
  1. In the JS script, call the engine.trigger function with the name of the event created in C# e.g. TriggerC#Event_OnViewJSIsReady:
engine.trigger('TriggerC#Event_OnViewJSIsReady');

C# triggers the JS

Let’s trigger an event in C# and handle it in the JS. The following diagram illustrates how the events sent from the game to the UI work.

The following steps are needed to achieve this:

  1. In the engine.whenReady promise function, add an event handler with engine.on, specifying:
    • The first argument is the event name. For example, name it TriggerJS_OnTriggerParameterlessHandler
    • The second argument is the JS function to be invoked when the handler is called.

The code will look like this

function callback() {
  // Execute the event handler callback
}

engine.on('TriggerJS_OnTriggerParameterlessHandler', callback);
  1. In your C# code, call the event using the cohtmlView.NativeView.TriggerEvent method:
cohtmlView.NativeView.TriggerEvent("TriggerJS_OnTriggerParameterlessHandler");

Calls

Use Calls when you want to return a value from the event handler. You can use JavaScript Promise to handle the result. The result can be only one value. Promises are only available when calling C# events from your JavaScript code.

Let’s look at an example where the JS requests a player name and the C# returns it to the JavaScript via a promise. Follow these steps:

  1. In the C# script, add the delegate to ReadyForBindings using the CohtmlView.NativeView.BindCall method.
    • The first parameter is the name under which you will call from JS.
    • The second is the delegate you will use to return the value. The delegate type must be Func with a return value string.

The BindCall method signature will look like this:

cohtmlView.NativeView.BindCall("CallC#_OnReturnPlayerName", (Func<string>)CallCSharp_OnReturnPlayerName);

The callback method should look like this:

private string CallCSharp_OnReturnPlayerName()
{
    return "Name returned from C#";
}
  1. In the JS, add a function to be used as a callback via promise, which will accept the returned result.
  2. Call the C# event by attaching the method call as a promise with the then() function.

Your code should look like this:

function callback(name) {
    // Handle the name
}

engine.call('CallC#_OnReturnPlayerName')
    .then(callback);

Parameters

To add parameters to the triggers and the event handlers, specify them after the callback method or function. The parameters can range from one to eight per event. Supported data types can be found in the Parameter Types section.

In the JS to C# direction will look like this:

cohtmlView.NativeView.RegisterForEvent("EventName", (Action<type_arg_1, type_arg_2>)EventHandleMethod);

private void EventHandleMethod(arg_1, arg_2)
{
    // Handle the arguments
}
engine.trigger('EventName', arg_1, arg_2);

In the C# to JS direction will look like this:

function eventHandleFunction(argument_1, argument_2) {
    // Handle the  arguments
}

engine.on('TriggerJS_EventName', eventHandleFunction);
cohtmlView.NativeView.TriggerEvent("TriggerJS_EventName", argument_1, argument_2);

Supported Parameter Types

Prysm supports most data types you might need in your project, whether in C# or JS direction. A detailed list is below:

  • C# side:

    • Value types: Boolean, int, uint, long, ulong, sbyte, byte, float, double, DateTime.
    • Reference types: string, Custom types.
    • Collections: Array, List, Dictionary, IReadOnlyList (Export to JS only).
  • JS side:

    • Primitive types: number, string, boolean.
    • Collections: array, associative array.

Custom Types

You can exchange custom types written by you. To do this, you need to describe the object that exists in C# as a JS object and add two Cohtml attributes to the type and its members:

  • To expose your custom C# type, add the cohtml.Net.CoherentType attribute above the class.
  • To expose a field, property, or method, use the cohtml.Net.CoherentProperty attribute above it. You can change the name of the member in JS by adding a parameter to the attribute declaration and writing the desired name as a string. If you do not specify a name, the name given in C# will be used by default, Case Sensitive.

As an example, create a class named Item with members Name (field, string), Damage (field, int), and Durability (property, float). Follow these steps:

  1. In C#, create a new class named Item and add the necessary fields. Add the cohtml.Net.CoherentType attribute above the class.
  2. Add the cohtml.Net.CoherentProperty attribute above each field you want to expose in JS.
    • For Damage field, change the name to damage.
    • For Durability property, change the name to durability.
[cohtml.Net.CoherentType]
public class Item
{
    public int Id;

    [cohtml.Net.CoherentProperty]
    public string Name;

    [cohtml.Net.CoherentProperty("damage")]
    public int Damage;

    [cohtml.Net.CoherentProperty("durability")]
    public float Durability { get; set; }
}
  1. To create an instance of the Item class in JavaScript, you need to create a JS object. It is mandatory to have a property __Type with the value being the full path to the class name in C#. In this case is Item.
  2. Add to the JS object properties for each member you need for your JS logic. In this case, these are Name, Damage, and Durability. Add data corresponding to the type of the respective member.

The JS object declaration should look like this:

var item = {
    __Type: 'Item', // Match the Full Name of the C# type.
    Name: 'Book Of Wisdom',
    damage: 280, // the member name is lowercase because of CoherentProperty("damage")
    durability: 10.15 // the member name is lowercase because of CoherentProperty("durability")
};

JS triggers C#

  1. Add a new event handler in the ReadyForBindings callback, using RegisterForEvent with a delegate using Action<Item>, which will process the received object.

The C# event handler should look like this:

cohtmlView.NativeView.RegisterForEvent("TriggerC#Event_OnSendCustomObject", (Action<Item>)CallCSharp_OnSendCustomObject);

The C# callback method should be like this:

private void CallCSharp_OnSendCustomObject(Item item)
{
    // Receive a JS custom object mapped to the "Item" type. Name: {"Book Of Wisdom"}; Damage: {280}; Durability: {10.15};;
}

The JS trigger should look like this:

engine.trigger('TriggerC#Event_OnSendCustomObject', item);

C# triggers JS

  1. In C#, within the ReadyForBindings callback, trigger the event with the specified event handler name e.g. TriggerJS_OnLoadCustomObject, and the Item instance as a parameter:
Item item = new Item { Id = 0, Name = "Axe of Might", Damage = 123, Durability = 4.75f };
cohtmlView.NativeView.TriggerEvent("TriggerJS_OnLoadCustomObject", item);
  1. Add an event handler using engine.on with a callback function to receive the Item object.
function callback(item) {
    // Received a custom object mapped to the "Item" type. Name: {"Axe of Might"}; Damage: {123}; Durability: {4.75};";
}

engine.on('TriggerJS_OnLoadCustomObject', callback);

Unregister from events

To remove an already registered event handler from JavaScript, you need to call the engine.off function with:

  • The first argument is the name of the event handler.
  • The second optional argument is to provide a specific function to be removed from this event. If you do not provide one, call engine.off will remove all registered functions for this event.

For example, if you have registered an event named TriggerJS_OnLoadCustomObject with the callback function, to unregister it you need to call the following line:

engine.off('TriggerJS_OnLoadCustomObject', callback);

To remove a C# Event or call, you need to keep a reference to the returned BoundEventHandle. When you want to remove an Event, you should pass it as an argument to the CohtmlView.NativeView.UnregisterFromEvent method. For example, you have the following event:

BoundEventHandle handle = cohtmlView.NativeView.RegisterForEvent("EventName", (Action)EventHandleMethod);

The removal should be done like this:

cohtmlView.NativeView.UnregisterFromEvent(handle);

To unregister a Call, you need to invoke the CohtmlView.NativeView.UnbindCall method. For example, if you have a Call:

BoundEventHandle handle = cohtmlView.NativeView.BindCall("CallName", (Func<string>)CallHandleMethod);

The removal should be done like this:

cohtmlView.NativeView.UnbindCall(handle);

Binding Release event

The CohtmView.Listener.BindingsReleased method in C# is invoked when all registered event handlers, call handlers, and models associated with the JavaScript bindings have been automatically unregistered. This provides an opportunity to release any resources that were used by the JavaScript bindings for the Cohtml View, such as destroying registered models and performing other cleanup tasks.

cohtmlView.Listener.BindingsReleased += () =>
{
    // unload your resources
};