It seems to me that there is something missing in the documentation on how to fully handle two-way communication between Unity and content on a webpage.
Consider the following:
Use Case 1: Send text from Unity to a text box on a webpage
In this case, some kind of text string is known by the unity content, and it needs to be sent to the web page. The approach here is to create a jslib that exists within the Unity project for C# code to talk to. The jslib code then communicates with the web page via known, existing function that has been added to the global document.
However, what if I want to register a callback from the webpage to the jslib, because I am not sure what the webpage developer is going to do? If I want my jslib to just be an API for communicating with the Unity content, is this possible?
Use Case 2: Send event data from webpage to Unity
In this use case, the webpage will notify Unity when interaction with a button (or other ui element) occurs. For the example, when the button is pressed, the name of the button is sent to a handler in Unity. We have SendMessage for this, but I it has restrictions on what you can send, it can’t return data, and it requires your scene to be configured in a specific way. I would really like to just register a callback from the webpage to the jslib I put in the plugins folder, which in turn triggers a registered callback on the c# side. No restrictions other than what is defined in the API.
What I have tried:
jslib
var UnityAngularUIBridge =
{
// Object to hold various callbacks
$UnityUIBridge:
{
dialogTextCallback : null,
buttonPressedCallback : null
},
// Called from angular/js side to register a callback for when new dialog text
// should be displayed
SetOnDialogTextCallback: function(obj)
{
console.log("[UnityAngularUIBridge] SetOnDialogTextCallback");
console.log(obj);
UnityUIBridge.dialogTextCallback = obj;
},
// Called from Unity to indicate that new dialog text should be displayed
SetDialogText: function (dialogJsonPtr)
{
// string paramters from unity get delivered to javascript as pointers.
// So we get the actual string from the pointer.
var dialogJson = Pointer_stringify(dialogJsonPtr);
// Now convert the string to a javascript object.
var dialog = JSON.parse(dialogJson);
// Invoke the provided dialog text callback with the new dialog json
UnityUIBridge.dialogTextCallback(dialog);
},
// Called from Unity to set a C# callback for when a button is pressed
SetOnButtonPressedCallback: function(obj)
{
console.log("[UnityAngularUIBridge] SetOnButtonPressedCallback");
console.log(obj);
UnityUIBridge.buttonPressedCallback = obj;
},
// Called from angular/js side when a button is pressed that Unity should
// know about
OnButtonPressed: function(buttonName)
{
console.log("[UnityAngularUIBridge] OnButtonPressed" + buttonName);
// Invoke emscripten call with the button callback and required parameter
Runtime.dynCall('vi', UnityUIBridge.buttonPressedCallback, buttonName);
},
};
autoAddDeps(UnityAngularUIBridge , '$UnityUIBridge');
mergeInto(LibraryManager.library, UnityAngularUIBridge);
C#
using System.Runtime.InteropServices;
using AOT;
using UnityEngine;
public class UnityAngularUIBridge : MonoBehaviour
{
/// <summary>
/// Unity facing event that should handle button presses from the angular UI
/// </summary>
public delegate void ButtonCallback(string name);
public static event ButtonCallback ButtonPressed;
/// <summary>
/// Callback function definition to be invoked from angular UI
/// </summary>
public delegate void JSButtonCallback(System.IntPtr ptr);
[DllImport("__Internal")]
public static extern void SetOnButtonPressedCallback(JSButtonCallback buttonCallback);
/// <summary>
/// The function invoked from the javascript bridge
/// </summary>
/// <param name="buttonNamePtr">Pointer to data holding the button name</param>
[MonoPInvokeCallback(typeof(JSButtonCallback))]
public static void OnButtonClickedCallback(System.IntPtr buttonNamePtr)
{
// Convert from byte data into managed string
string buttonName = Marshal.PtrToStringAuto(buttonNamePtr);
Debug.Log("Managed string: " + buttonName);
// Invoke the Unity side event if set
ButtonPressed?.Invoke(buttonName);
}
/*
* For each type of UI control that we want to hook up, the above set of functions
* will be needed. Add more as new control types are required
*/
[DllImport("__Internal")]
public static extern void SetDialogText(string jsonData);
[DllImport("__Internal")]
public static extern void OnButtonPressed(string buttonName);
[DllImport("__Internal")]
private static extern void SetOnDialogTextCallback(string jsonData);
public void Start()
{
// Provide the callback function to the javascript bridge that should be invoked when
// a button is pressed
SetOnButtonPressedCallback(OnButtonClickedCallback);
}
}
HTML
<script>
// Function which sends the message to Unity
function OnButtonPressed(buttonName) {
// Get the input field
const txtMessage = document.getElementById("txtMessage");
// Get the message
const message = txtMessage.value;
// Clear the input field
txtMessage.value = "";
// Send message to the Unity scene
unityInstance.UnityAngularUIBridge.OnButtonPressed(buttonName); // UnityAngularUIBridge is undefined, and whether I do unityInstance._UnityAngularUIBridge or unityInstance.Module._UnityAngularUIBridge or unityInstance.Module.UnityAngularUIBridge, it always stays undefined
}
</script>
Basically, the problem I have is that I am unable to access UnityAngularUIBridge from the webpage side of the equation. I have tried to look through the emscripten documents for help, but I haven’t found any example that discusses this in a way that makes sense to me, as those docs are mostly concerned with how to call native code from the jslib in unity, not how to access functions in the jslib from html.
This thread applies to Unity 2019.4 LTS.