Building a Unity Plugin in JavaScript
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:
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:
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:
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:
JAVASCRIPT
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:
C#
[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:
C#
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:
Important things here are:
dynCall(’v’, callback, [])
dynCall is a prefix used for dynamically calling functions exported from WebAssembly modules, commonly seen in environments like Emscripten.
Note: Unity is using the Emscripten version 2.0.19 toolchain.
dynCall is where the callback will be invoked at. the ‘v’ shows that the callback is of type void and has no parameters. Now let's say that our callback takes a string playerID has a parameter, then the code will be like this:
[DllImport("__Internal")]
public static extern void ExampleFunction(Action<string> callback);
Inside the JavaScript we will be invoking the callback like so:
var id = player.id;
var bufferSize = lengthBytesUTF8(id) + 1;
var buffer = _malloc(bufferSize);
stringToUTF8(id, buffer, bufferSize);
dynCall("vi", functionPtr, [buffer]);
The 'vi' points that the callback is with a string parameter. in case of have 2 string parameters we can use:
dynCall('vii', callback, [dataJson, buffer]);
Now you probably have seen a pattern that we are using strings mostly to pass data, that is because it is quite easy to handle JSON from both C# and JS. Unity’s built-in JsonUtility is great for Unity specific types (such as Vectors etc), but it is quite limited especially when it comes to data structures such as Dictionaries. To solve this issue, we went with the simplest open-source serializer called SimpleJSON.
Limitations:
Improvements:
Conclusion
It definitely is possible to bridge a JavaScript library into Unity. This article explored one approach deeply. If you've tried the others, drop a comment! I'd love to know the use case and keep learning.
Software engineering isn't always about creating things from scratch. More times than often, it's:
And I got to do them all. Pretty fun!
We love trying out new frontiers, to bridge useful tech with useful communities, and contributing to the overall knowledge stream. A lot of our work is R&D-based and on experimental tech. If you're interested in working with or for Grayhat, DM or comment!
This article was authored by Talha Momin , Software Engineer at Grayhat.
"everything's better with just a bit of JavaScript" words never uttered previously in this plain of existence