Skip to main content
GameDev.net gamedev.net

PRO Tired of ads? Read GameDev.net ad-free and help keep the community independent with GameDev Pro — $3/month.

RTS devlog #1: Architecture

RTS devlog #1: Architecture

AshleyScirra
AshleyScirra
Command and Construct RTS game · · 7 min read
9,888 0

Since my first blog I've been putting together the overall architecture of the game. You can see some of the initial work in the commit log on GitHub. This can be slow going at the start: these initial decisions are very important and can end up very difficult to change later on. In this post I'll cover the overall technical design I'm aiming for.

The game server

For multiplayer games there is generally a server that has the true state of the game, and clients aim to sync with that. The GameServer class will represent the authoritative state of the game. The GameClient class is the local counterpart that aims to sync with the state on GameServer.

Construct's Multiplayer feature works via peer-to-peer connections. This actually avoids the need to run dedicated servers, which helps make it cheap to run! (You can run TURN servers to improve connectivity, but those will only deal with a fraction of the bandwidth, as most connections can still run peer-to-peer.) In a multiplayer game one player will act as the host, and that player will also run GameServer and act as the server for all players.

One interesting design point is that when the host has a local GameServer, it will still run the same code to sync the local GameClient with the GameServer. However the link between the two will have zero latency and unlimited bandwidth, so it should sync perfectly. The main reason for this is it actually saves on coding. If there was some special case to handle being both a server and client at the same time, that means writing a lot of code to handle that mode separately. Since there will already be this whole system to manage a remote client, it can just be re-used in its entirety for a local client. Here's a diagram that shows how it will work with two players.

Two players in a multiplayer game. Each player has a GameClient. The host has GameServer.

In fact this approach extends to single-player games too! Instead of writing a whole special single player mode, there can just be a local GameServer on the same system, and it will run a game for one player and sync perfectly with GameClient (essentially just the "Player 1" box in the diagram above). I'd like to have a single-player mode for this project too if possible, and that can definitely be done. It's an interesting design point that going from a single-player design to a multiplayer design is incredibly hard - often infeasible - but going from a multiplayer design to a single-player design can actually be pretty straightforward. So thinking about the multiplayer design is still the right place to start.

Another benefit of single-player mode working the same as multiplayer is I can simulate latency and packet loss on the messages passed between GameClient and GameServer in single-player mode. This means the resilience of syncing over a poor network can also be tested in single-player mode without actually using networking, which should make testing the multiplayer code easier.

Multithreading

A cool part of this design is we can also use multithreading. In short, GameServer can go in a Web Worker.

Not all game development tools support multithreading, but it's another thing JavaScript has had for years in the form of workers. A worker provides an independent JavaScript context that runs in parallel to everything else. The system can schedule it to run on its own CPU core. It can safely communicate with other JavaScript contexts via message passing (using postMessage()) without any risk of nightmare shared memory concurrency bugs that some "unsafe" languages are subject to.

GameServer will be designed to communicate with GameClients over the network, and this also makes it easy to use a worker: instead of sending messages over a network, it sends messages to and from a worker. That's like a perfect network link with zero latency and unlimited bandwidth.

GameServer may well be running very heavy amounts of game logic to simulate hundreds or even thousands of independent units all interacting in different ways. Using a multithreaded architecture moves all this work to its own thread so it won't affect the performance of the game for the local player, as GameClient is in a different thread and so won't be slowed down even if GameServer is using a full CPU core. That's pretty cool and potentially significantly increases the upper limit of how intensive a game it can run, especially since JavaScript has outstanding performance. It will be interesting to see how far this can be pushed. I will definitely be trying some stress tests later on!

Construct can host its own runtime in a Web Worker, off the main browser thread (aka the DOM). Adding a second worker for GameServer means there are now three threads on the host system: one for the browser, one for the Construct runtime, and one for GameServer. I think this is a relatively sophisticated multithreaded architecture for a hand-coded browser game.

Diagram of three threads on the host system, and which communicate with each other.

All technology choices are a trade-off though, and there are some downsides. The main one is that by running in an entirely separate JavaScript context, there is no direct access to the Construct runtime. That means it can't rely on Construct for things like collision detection - I will have to code everything in GameServer in pure JavaScript. I don't think there will be too much of that kind of re-implementation though as GameServer doesn't have to handle anything like rendering or user interaction. I guess it might be an interesting exercise in game coding.

Funnily enough that disadvantage also turns in to a potential advantage: if GameServer is entirely self-contained, then it could even be run independently with a tool like Node.js or Deno. That provides a way to host a dedicated server in future, which would solve the problem of the game ending if the host quits, but means somebody needs to pay to run those servers. This also highlights a great strength of JavaScript: it's supported so widely that the same code can be re-used directly in different places such as both servers and clients, rather than needing to rewrite things in another language entirely.

Summary

So overall I would say the advantages of this approach are:

  • It covers both single-player and multiplayer games without needing separate modes for each
  • The same messages can be used for both network messages for multiplayer games and worker messages to talk to a local GameServer
  • Multiplayer code can be tested in single-player mode without actually using networking, by simulating latency and packet loss
  • It should work for both peer-to-peer and dedicated server network architectures, although it'll just be peer-to-peer to start with, as that's cheaper to run.
  • Multithreading avoids janking the host player's game and could significantly enhance overall performance

The disadvantages are:

  • GameServer will have to be coded fully independently, without using Construct for things like collision detection.
  • Overall this is a fairly complicated design. But hey, I signed up for a challenge.

In software, as in most engineering things, there's probably not a perfect solution that ticks all boxes - everything involves tradeoffs. But I like this set of tradeoffs and I think it will work well for this project.

More to come

I hope that helps explain the goals of this project. It's early days yet and there's a lot more to come. I'm aiming to get to something at least minimally playable as quickly as possible - so far there's been a lot of just laying foundations to build on top of, so there's not much to see in action just yet. But you can see all the code so far on the GitHub repository and I'll keep on blogging about it here! Let's see how long it takes to get a multiplayer game where two people can just move their own units around and see everything updating. Then we'll start building gameplay from there.

Discussion

Loading comments...