The Wizardry of Fast and Reliable WebGL Multiplayer

(Original Blog post by @sandeep)
Imagine a world where you just finished your multiplayer game’s latest build, and you can’t wait to share it with your friends, fans and community. Now all you need to do is: find a reliable but affordable cloud hosting company or set up your own server, create a lobby and matchmaking server, set up a failover solution for it in case a single instance can’t handle the load, set up your game server(s), implement authority so player’s can’t cheat. They need to download a client, find the correct executable, and hope that everything works out with the configuration - the list goes on. Meanwhile, you just want feedback and maybe a bit of praise (well, a lot, as the case may be!), but instead you have another seven-headed hydra to slay.

Configure Build Sharing

What if it was just as easy as clicking Build and sharing a link, so anyone can play it immediately in their browser?

Hold on though, don’t WebGL multiplayer games suffer from high latency as a rule? So how can you have fast, but reliable multiplayer inside a browser? This what we have been working towards at coherence and here’s how we solved it.

The challenges of WebGL networking

coherence defaults to using UDP transport in Unity. UDP is often favored for multiplayer games due to its connectionless nature, allowing for faster communication without maintaining individual connections. Combined with coherence’s custom reliability layer, it provides the lowest latency possible between players.

Unfortunately, browsers block UDP transport by default. This is primarily due to security concerns. This leaves us with two options for real-time transport in browsers: WebRTC and websockets (and in desperate situations, long polling).

Websockets - simple and reliable, but slow

Websockets are the simplest to implement, there are a ton of libraries and the widest platform support. But this is built on top of TCP sockets and that also means the highest default latency just because of the protocol.

WebRTC - wait, what kind of wizardry is this?!

WebRTC

WebRTC connections on the other hand have a waterfall of possible connection types and try to find the fastest route to the server. WebRTC datachannels are one of the few methods to have UDP connections to anywhere, falling back to TCP and then to a relay server, automatically, based on the connection’s capabilities. They also allow for direct p2p connections, something we want to support soon.

image2.gif

WebRTC requires an extra signaling layer to exchange possible connection capabilities. coherence handles this seamlessly through its SDK and the developer rarely has to inspect the transport level optimizations at play.

Making WebRTC work with coherence

Client-side, the modular nature of coherence’s SDK meant that implementing a new transport was surprisingly fast. We have a JavaScript interop layer that is a drop-in replacement for the UDP transport when building for WebGL in Unity. coherence’s custom reliability layer is disabled for this transport since WebRTC is reliable by default.

image5.png

Server-side, the Replication Server also acts as the signaling server for establishing a WebRTC datachannel connection. We use the amazing Pion/WebRTClibrary on the server to enable incoming WebRTC connections.

Of course, things didn’t go that smoothly

Unity’s special JavaScript

image3.gif

We have to be extra careful when writing the JavaScript interop layer in Unity. Features we would take for granted in other JavaScript environments (like arrow functions) just do not compile (yet). We also have to be careful with allocations and interop data transfers since everything lives on a fixed-size byte array on client-side.

-Twilio, help! -Here, take our STUN and TURN servers!

STUN servers are basically like pinging lighthouses. They help a connection know its place and address on the public Internet. When establishing a connection with WebRTC, clients ping STUN servers to understand their own Internet situation and pass that information to the signaling server. The signaling server then coordinates this information with the Replication Server, figuring out the most optimal connection route.

Sometimes there is no viable route between the client and the server. This can be due to a myriad of reasons, like port blocking by the authorities or just firewall configurations. In such cases, WebRTC employs a TURN server. It is basically a relay on the public Internet that both the client and server can connect to and use to exchange packets.

After a few iterations and experimentation, we settled on using Twilio’s fast and reliable STUN and TURN servers. This ensures that WebGL builds can establish connections through Internet situations that sometimes make even native builds using UDP fail (fallback transports are coming soon to bring non-WebGL builds up to parity!).

“This was a triumph! I’m making a note here: Huge success!”

Bombastic references to Valve’s Portal aside, here’s what we arrived at:

  • We solidified our transport layer API, which makes future custom transport implementations a breeze (here’s looking at you, Steam sockets).
  • We also have seamless WebGL builds that just work™ cross-platform where the SDK establishes the fastest allowed connection to the server in a browser context with fallbacks that also just work™.

Office celebrate.gif

What does the future hold in store?

We won’t stop here, naturally. Moving forward, we aim to optimize WebRTC’s handling of network changes and extend our transport compatibility beyond Unity and WebGL builds to include pure JavaScript/TypeScript projects. Additionally, we plan to enhance our integration tests to cover mobile browsers and optimize the transport’s performance on mobile devices under various network conditions.

If you’re new to coherence and want try out this feature for free, start your journey with coherence here. If you’re already using coherence but haven’t gotten around to checking it out, read our Share Builds article. We look forward to seeing it make a real difference for you!