Building a realtime multiplayer browser game in less than a day - Part 2/4

srushtika

Srushtika Neelakantam

Posted on June 12, 2020

Building a realtime multiplayer browser game in less than a day - Part 2/4

Hey again 👋🏽

Welcome to Part 2 of this article series where we are looking at the step by step implementation of a realtime multiplayer game of space invaders with Phaser3 and Ably Realtime.


Here's the full index of all the articles in this series for context:


In first part of this series, we learned about the basics of gaming and the Phaser 3 library. In this article, we'll gain an understand of various networking protocols, architectures and system design to build multiplayer games.

Architecture and system design

Networking can be tricky for multiplayer games if not done right. All the players need to have the ability to communicate at all times and they all need to have a state that’s always synchronized.

There are a couple of strategies to go about this:

1) Peer-to-peer (P2P): As the name suggests, each player(client) in this strategy directly communicates with every other player. For games with a small number of players, this might be a good strategy to get up and running quickly.

P2P game design

However, this approach has two immediate downsides:

1a) If a client is responsible for deciding what happens to them in the game, they can end up cheating by hacking something on the client side. Even if they are not exactly cheating, they could be claiming something that may not be true for someone else just because of network lag

1b) In the P2P strategy, every player is connected to every other player and communicates in that way too, leading to an n-squared complexity. This means, our game wouldn't scale linearly for when hundreds of players start playing the game online.

2) Client-Server: As for most of the web, the client-server strategy applies pretty nicely for multiplayer games too, with a scope for high scalability. This strategy allows us to have a game server that can be authoritative i.e. to be a single source of truth about the game state at any given point.

Client-Server game design

The game server holds the game logic and controls what happens on the client side. This enables a fully synchronized game environment for all the players involved. All communication between the players happens only via this game server and never directly.

We'll use the client-server strategy in our Space Invaders game. But before we continue, a note on network lag and liner interpolation:

In a live networked game, there's a high possibility that a minor network lag might cause a bad experience for the person playing it. A common workaround is a technique called Linear Interpolation which allows predictively moving the game objects while the client is waiting for the next update to arrive, making sure the overall experience is as smooth as possible.

According to Unity, "When making games it can sometimes be useful to linearly interpolate between two values. This is done with a function called Lerp. Linearly interpolating is finding a value that is some percentage between two given values. For example, we could linearly interpolate between the numbers 3 and 5 by 50% to get the number 4."

Linear interpolation is a very useful technique to keep in mind. It is out of scope for this tutorial, but I might add this as a feature in some future commits to the project.

Choosing the right networking protocol

HTTP/ Long Polling/ WebSockets/ MQTT - What, which and why?

HTTP’s stateless request-response mechanism worked perfectly well for the use-cases we had when the web originally evolved, letting any two nodes communicate over the internet. Since it was all stateless, even if the connection dropped, you could easily restore the communication from that very point.

HTTP

However, with applications moving to realtime implementations, and trying to ensuring a minimal-latency sharing of data just as it is created in the real world, the traditional request-response cycles turned out to cause a huge overhead. Why? Well, in general, the high-frequency request-response cycles lead to more latency since each of these cycles require setting up a new connection every time.

Logically, the next step would be a way to minimize these cycles for the same amount of data flow. Solution? Long polling!

Long Polling

With long polling, the underlying TCP socket connection could be persisted (kept open) for a little longer than usual. This gave the server an opportunity to collate more than one piece of data to send back in a single response rather than doing so in individual responses. Also, it almost completely eliminated the case of empty responses being returned due to lack of data. Now the server could just return a response whenever it has some data to actually give back.

However, even the long polling technique involved a connection setup and high frequency request-response cycles, similar to the traditional HTTP based communications, with our original problem of increased latency still causing issues.

For most multiplayer games, including the one we are building, the speed of data is absolutely critical, down to the nearest millisecond. Neither of the above options proves 100% useful. 😐

Hello WebSockets! 💡🔄⏱

WebSockets

The WebSockets protocol, unlike HTTP, is a stateful communications protocol that works over TCP. The communication initially starts off as an HTTP handshake but if both the communicating parties agree to continue over WebSockets, the connection is simply elevated giving rise to a full-duplex, persistent connection.

Bi-directional communication

This means the connection remains open for the complete duration of the application being used. This gives the server a way to initiate any communication and send off data to pre-subscribed clients, so they don’t have to keep sending requests inquiring about the availability of new data. And, that's exactly what we need in our game!

Just a quick side note, if you plan to add any IoT based controllers to the game later on, WebSockets might seem a bit heavy as IoT devices are very constrained in terms of bandwidth and battery - In those cases, you can use the MQTT protocol which is very similar to WebSockets but fits well within the IoT constraints. It also comes with an in-built implementation of the Pub/Sub messaging pattern (discussed shortly), you can read more about it in a separate MQTT conceptual deep dive article. I won't be discussing it here as our game in its current state doesn't require it.

Going back to Websockets, how do we get this protocol working? We could always write a WebSockets server from scratch. In fact, I even wrote an article a while back about how you can implement a WebSocket server in Node.js.

However, building this game is enough of a task in itself, so we don't really want to get bogged down by side projects/ yak-shaving. Lucky for us, there are loads of WebSocket libraries that we can use to spin up a server in no time. The most popular open-sourced library for this purpose is Socket.io, and it has its share of good tutorials and implementations in the Phaser community.

As mentioned in a deep-dive article for WebSockets, the number of concurrent connections a server can handle is rarely the bottleneck when it comes to server load. Most decent WebSocket servers can support thousands of concurrent connections, but what’s the workload required to process and respond to messages once the WebSocket server process has handled receipt of the actual data?

Typically, there will be all kinds of potential concerns, such as reading and writing to and from a database, integration with a game server, allocation and management of resources for each client, and so forth. As soon as one machine is unable to cope with the workload, you’ll need to start adding additional servers, which means now you’ll need to start thinking about load-balancing, synchronization of messages among clients connected to different servers, generalized access to client state irrespective of connection lifespan or the specific server that the client is connected to -– the list goes on and on.

There’s a lot involved when implementing the WebSocket protocol, not just in terms of client and server implementation details, but also with respect to support for other transports (like MQTT) to ensure robust support for different client environments.

We'd also have to think of broader concerns, such as authentication and authorization, guaranteed message delivery, reliable message ordering, historical message retention, and so forth, depending on the specific use-case and game logic. A reliably ordered message stream is especially important in most cases as it makes all the client-side interpolation extremely straight forward. Otherwise, we'd need to use packet reconstruction and other techniques to implement this ourselves.

We can get out of this complexity nightmare by just using a serverless realtime messaging architecture which can support these by default. As you read in the first part of this article series, for our multiplayer space invaders game we'll make use of Ably's realtime messaging service which comes with a distributed network and serves as a one-stop solution to all the complexities we discussed above.

Ably Realtime

Understanding the Publish/Subscribe (Pub/Sub) messaging pattern

With always-on connections in WebSockets, comes the concept of subscriptions. To put it very simply, in a Pub/Sub messaging pattern, you can have clients that publish some data and clients that subscribe to that data, or both. "Subscription" is asynchronous: like a subscription to a magazine, you let the provider/publisher know only once that you are interested in a particular magazine, and every time they have a new issue, they send it over.

PubSub message architecture

Similarly, with message subscriptions, you let the publisher know only once and then wait for the callback method to be invoked when they have relevant information to share. Remember, what makes pub/sub possible is that the connection is still open, and communication is bi-directional. That's all we need to know about Pub/Sub to build our game, but if you are interested in learning more, I'd recommend reading through "Everything You Need To Know About Publish/Subscribe".

The last thing we need to understand before we start writing some code, is the concept of Channels. In any realtime app with a bunch of clients, there's a lot of moving data involved. Channels help us group this data logically and let us implement subscriptions per channel, allowing us to write the correct callback logic for different scenarios.

Channels in our game

For a scenario with two players, our channels will look something like this:

Game channels design

The diagram might seem super complicated, so let's try to break it down and understand what's happening.

Since we're implementing the client-server strategy, the players and the server will communicate via Ably's realtime platform. The server will be authoritative, i.e. be the single source of truth with regards to the game state, and it'll make sure all the players are in sync.

To do this, we'll start off with two main channels:

  • The game-room channel: We'll use this to fan out the game state and player join/leave updates
  • The dead-player channel: We'll use this to listen to updates about a player's death due to bullet hit

As shown in the diagram above, we also have a unique channel for every player. This will be used by individual players to publish their input to the game server(left and right arrow key presses), so it can then fan it out to all the players as part of the game state updates on the game-room channel.

Now that we have a good overall sense of how the game is designed, we can jump into the implementation details of keeping all players in sync in Part 3 - Implementing the server-side code to keep all players in sync


All articles in this series:

A separate release relevant to this tutorial is available on GitHub if you'd like to check it out.

You can also follow the Github project for latest developments on this project.


As usual, if you have any questions, please feel free to reach out to me on Twitter @Srushtika. My DMs are open :)

💖 💪 🙅 🚩
srushtika
Srushtika Neelakantam

Posted on June 12, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related