I’ve been working on a programming game where you are able to code units to move around and complete objectives such as fighting enemies, capturing objectives etc.
When I initially started the project it was aimed to be a Desktop Game where you could write code in Python, however as the project evolved I started realising the potential of running the game on web.
I spent a month converting the game to web and I wanted to share some of the work I’ve been working on and the challenges I faced.
While the UI and game art is still very much WIP, it showcases a very basic working concept of the game.
You are able to program directly within the site and assign them to cards. Once you have assigned some code to the card. You are able to drag the React card directly from the bottom bar into the game which will execute the code at that point. The variables x
and y
in this code are the position at which you drop the card, which is auto-filled by the engine.
As you can see there’s even a realtime indicator showing in the Unity game as I drag the React card component into the game, the intercommunication between the game and the site is very smooth.
Here’s how all of this is working.
Code Editing & Execution
To do the code editing I am using the CodeMirror package with it’s basic JavaScript support for Syntax Highlighting and very basic intellisense however I will have a better intellisense system in the coming months.
Code Execution however is more interesting, the code is running using JavaScript’s eval
functionality; however user code cannot be trusted to run directly in the main context for security and performance reasons and for the simple reason that a while true loop will cause the browser to crash.
To combat all these problems we can use Web Workers, it’s multithreading on web!
The flow is very simple, when the Play button is pressed:
- New Web Worker is created
- Messaging Interface is setup between the frontend & the web worker
- When code is submitted it is piped as a string to the Web Worker which executes it
Here’s how we are able to run code with a custom context so I can pipe in values into the user’s code such as the game and custom console logs, we can even provide whole files into the scope of the user’s code.
var context = {
game: game,
console: {
log: (...args) => {
game.logger.log(args.join(' '));
console.log(...args);
},
error: (...args) => {
// game.logger.error(args.join(' '));
console.error(...args);
},
},
Math: Math,
...require('../data/data_pb'),
Vector3: Vector3,
Vector2: Vector2,
Quaternion: Quaternion
};
const runCode = (codeStr, preCodeStr) => {
const params = Object.keys(context); // Get variable names
const values = Object.values(context); // Get variable values
const wrappedCode = `
(async () => {
${preCodeStr}
${codeStr}
})().catch(console.error);
`;
const func = new Function(...params, wrappedCode);
// Execute the function with the context's values
var val = func(...values);
// console.log = originalConsoleLog;
return val;
}
The game object is an abstraction layer that I have created in order to bridge the gap between the user’s code and the game client as the game client only receives and sends out commands using Protobufs.
Communication Between Unity & React
The initial plan to communicate commands between Unity and JavaScript had been to run a WebSocket server on the client and send message through that to the Unity game window – however, I realised very quickly that you cannot start a server on the frontend 🙁
Unity by default supports the ability to call SendMessage
and communicate to Unity’s functions through that, however it is cumbersome and I do not trust it’s speed and reliability.
To overcome this I built this very simple .jslib plugin, a JSLib is a kind of plugin that is compiled alongside the WASM when Unity builds the game, and it’s able to communicate with Unity as well.
mergeInto(LibraryManager.library, {
Init: function (fnPtr) {
console.log("Init v1.0.2");
window.sendToGameClient = function (uInt8Array) {
// Allocate memory in WebAssembly and copy the Uint8Array into it
const length = uInt8Array.length;
const buffer = Module._malloc(length);
Module.HEAPU8.set(uInt8Array, buffer);
// Call the C# function with the pointer to the array and the length
Module.dynCall_vii(fnPtr, buffer, length);
// Free memory after calling the function
Module._free(buffer);
}
},
Send: function (bytePtr, length) {
const copiedArray = new Uint8Array(length);
copiedArray.set(HEAPU8.subarray(bytePtr, bytePtr + length));
// check if the function exists
try {
if (window.receiveBytes) {
window.receiveBytes(copiedArray);
}
}
catch (e) {
console.error("Error calling receiveBytes",e);
}
}
});
This piece of code when initialised on the C# Side can create a function on the window object called sendToGameClient which takes a byte array, assigns it to memory and then calls a function through a pointer (fnPtr) it’s a very hacky, but it works surprisingly well.
So now in JS, I can simply call window.sendToGameClient
and provide any piece of data I want, not what is limited to SendMessage. We can also do the reverse, by making the frontend create a receiveBytes
function which the C# can directly call.
This way there is immediate bi-directional communication between Unity and the Web.
On top of this layer, I am using Google Protobuf to serialise and de-serialize data on either end of this pipeline for a standardised way to send and receive data.
Calling C# Functions Direct from JavaScript
Now that I had this communication setup I wanted to take it one step further.
I want to be able to call C# functions directly from JavaScript and get the returned result of said function call, this would be key to getting great interoperability between the two.
The way I handled this was using C# Reflection. I created an attribute called WebFunction which I could annotate any method.
public struct DragResult
{
public bool canDragHere;
public float x;
public float y;
}
[WebFunction]
public static async Task<bool> start_drag()
{
await UnityMainThreadDispatcher.Instance().EnqueueAsync(() =>
{
CardDraggingManager.Instance.StartDrag();
});
return true;
}
[WebFunction]
public static async Task<DragResult> stop_drag()
{
Vector3 mousePos = Vector3.zero;
await UnityMainThreadDispatcher.Instance().EnqueueAsync(() =>
{
CardDraggingManager.Instance.StopDrag();
mousePos = Origin.GetInstance(0).RelativePosition(MouseDataGenerator.activeWorldMousePosition);
});
return new DragResult
{
canDragHere = CardDraggingManager.canDropHere,
x = mousePos.x,
y = mousePos.z
};
}
Then I do some reflection magic to find all methods annotated as such and add them to a Dictionary.
Then in the JS Side I can call functions like such:
public async start_dragging_card(): Promise<boolean> {
return await (gameCommunicator.runFunction('start_drag') as Promise<boolean>);
}
public async stop_dragging_card(): Promise<DragResult> {
return await (gameCommunicator.runFunction('stop_drag') as Promise<DragResult>);
}
Now, this will send a message through the method I discussed before to C#, which will find a function with the matching name and dynamically invoke that function in runtime with reflection, it will also await the Task Result of said function and return a value back with a matching ID to the call that sent it out. This creates a seamless feedback loop between the JS and C# Side which makes it feel like you’re calling the function directly.
Then we can wrap these calls in pretty functions like start_dragging_card()
and the frontend developers (eg. me and my friend) can use it directly.
This kind of intercommunication wouldn’t usually be needed for a game but since our UI is so heavily tied into the game we needed a versatile solution such as this.
Testing with the Unity Editor to Prevent Building
The reason why I love Unity is the ability to quickly iterate, however now that I have half of the game in the web this means I have to build every single time I want to test the game with a new change.
Luckily however, I didn’t chuck out the WebSocket communication code I had written between JS and Unity. I had a realisation that when the game was running in Unity, I could still run a WebSocket server in Unity and do the intercommunication between JS and the game in this method.
All I needed was an abstraction layer that would sit above the messaging protocol.
- Message is Sent
- Broadcasted to WebSockets or Sent via JSLib Plugin
And vice versa for Receiving messages. This worked… really well.
What you’re about to see may blow your mind, but watch as I drag a card from a website, into the Unity editor.
That is very cool isn’t it, the power of abstraction! Now I can make a change in Unity and keep it in Unity but still test it with my website.
With this all complete I am now ready to actually make the gameplay and everything surrounding it, hope this was helpful or interesting to read!
See you all in the next update of the game!