Building a Unity Plugin in JavaScript
Make your JS Library compatible with Unity.
Playroom for Unity is open-source. Check it out here: https://github.com/asadm/playroom-unity
Interested in trying out Playroom for Unity? Check the docs! Join the Discord channel too in case you need help, you'll find me lurking there sometimes. 😁
Creating a game is no easy task, and crafting a multiplayer experience adds an even greater level of difficulty. But thanks to industry experienced developers who've created numerous tools which make the process of creating multiplayer games easier and more productive, developers can now focus on creating fun and immersive experiences for their users, and not worry about the networking side. One growing platform for this purpose is PlayroomKit by Playroom, which mainly focuses on multiplayer web-based party games.
I'm a Unity Developer, so I've had my eyes on similar multiplayer plugins for quite a while. Some of them are:
- Normcore: https://normcore.io/
- Photon: https://assetstore.unity.com/packages/tools/network/photon-unity-networking-classic-free-1786
- PlayFab: https://learn.microsoft.com/en-us/gaming/playfab/features/multiplayer/lobby/lobby-matchmaking-sdks/multiplayer-unity-plugin-quickstart
- Hathora: https://hathora.dev/
One of my biggest complaints (and the community's) is how hard it is to get things up and running with these libraries.
All that previous knowledge helped me, when I had to implement a plan to create PlayroomKit for Unity, keeping the objective in mind, making sure I didn’t hurt Developer Experience, and using my knowledge in Unity and hacking around in C# and JS.
The Goal
Combine the ease of PlayroomKit with power of the Unity engine.
The PlayroomKit package is super simple to get started with… in JS, at least. That’s a developer experience we needed to port to Unity. At the same time, we had to make the Unity library at feature parity with the JS library, following the exact same API. (That makes it easier to write docs, at least 😃)
Here our first problem arises which is quite easy to pinpoint: Unity uses C# and PlayroomKit is a JavaScript package. So some interoperability had to occur. To achieve this, we had the following approaches:
- Convert the JS library to C#, or,
- Use a JavaScript Interpreter for .NET (JINT), or,
- Use the Interaction with browser scripting provided by Unity itself.
If you want to go deeper into the thought process of “why” we went with our final approach, I highly recommend reading Playroom’s official blog on this very topic.
The Approach
We went with the third approach, referring the Unity docs. In short, the workflow is like this:
- We work with 2 files: a JSLIB (JavaScript Library) and C# class.
- The JSLIB file acts as a bridge between PlayroomKit (or any other JS library) and C# (Unity)
The figure above shows basic working of the system.
Problem 1: Passing Data
The Unity documentation shows an example where the primitive datatypes are being used to pass data between C# and JS. This process of converting is known as:
Marshalling. This process involves converting an object's memory representation into a format suitable for storage or transmission, especially across different runtimes.
The documentation gives us a good starting point with examples for the basic datatypes:
Hello: function () {
window.alert("Hello, world!");
},
HelloString: function (str) {
window.alert(UTF8ToString(str));
},
PrintFloatArray: function (array, size) {
for(var i = 0; i < size; i++)
console.log(HEAPF32[(array >> 2) + i]);
},
AddNumbers: function (x, y) {
return x + y;
},
StringReturnValueFunction: function () {
var returnStr = "bla";
var bufferSize = lengthBytesUTF8(returnStr) + 1;
var buffer = _malloc(bufferSize);
stringToUTF8(returnStr, buffer, bufferSize);
return buffer;
},
And in C# we will define the functions like this:
[DllImport("__Internal")]
private static extern void Hello();
[DllImport("__Internal")]
private static extern void HelloString(string str);
[DllImport("__Internal")]
private static extern void PrintFloatArray(float[] array, int size);
[DllImport("__Internal")]
private static extern int AddNumbers(int x, int y);
[DllImport("__Internal")]
private static extern string StringReturnValueFunction();
To use these functions, we can call them like so:
void Start()
{
Hello();
HelloString("This is a string."); // sending a string to JS
float[] myArray = new float[10];
PrintFloatArray(myArray, myArray.Length);
int result = AddNumbers(5, 7);
Debug.Log(result);
Debug.Log(StringReturnValueFunction());
}
Now this is all great, but the issue arises when we have to deal with async code or functions with callbacks.
Problem 2: Async Code or Callbacks?!
There are great discussions on the Unity Forums regarding using async functions and for passing callbacks as well. In the case for PlayroomKit, instead of using async / await, we went with providing callbacks, (PlayroomKit already provides callback parameters wherever required). The pattern here is something like so: