Developing Lag Compensated Multiplayer Game, pt. 1: Offline

Developing smooth multiplayer action games was always somewhat baffling to me. How should I split client-server responsibilities and make up for network latencies so that game action seems instant? I wanted to find answers to those questions and if they spark your curiosity too, read on!

This is the first part of the series, which introduces the offline game. Other parts are available here:

What we’re going to make

We’ll take the 1979 arcade classic Asteroids and modify it’s premise a bit, so it becomes a deathmatch. Ironically, we’ll skip the eponymous asteroids. In the case of our game, they’ll be replaced by other players.

Choice of technologies

We’ll use Java 8, LibGDX as a game framework, Jackson for data serialisation and implementations of Socket.IO for client-server communication.
If you’re not familiar with LibGDX yet I encourage you to do their simple game tutorial before reading further, and if that got you interested, definitely dive into Udacity’s excellent Game Development with LibGDX and How to Make a Platformer Game Using LibGDX.

Reference repository

Everything that we’re going to build here is available as a code at reference repository. Please note that part of the code is omitted in this series, so it would the best if you have it tabbed somewhere.

Lonely in the Universe

We’ll start with developing an offline game and enhance it with multiplayer capabilities later. In this particular part, we’ll deal with moving a ship around and shooting bullets into the void, and we’ll add another mockup player in the future.

So, set up a fresh LibGDX project with only Desktop subproject and let’s get into it.

LibGDX project setup

When LibGDX is done initializing, you’ll need to change sourceCompatibility from 1.6 to 1.8 in build.gradle files in both desktop and core subprojects. Note that if you’d want to support Android and GWT-based HTML builds you’d have to use 1.7 at most. However, for this tutorial, all we care about is the desktop. I also find lambdas and streams introduced in 1.8 very useful :).
Go ahead and delete everything inside core/src/com/asteroids/game and we’ll start developing packages from scratch. In this part, all of our development will happen inside of core subproject.

Vectors Utils

I know, I know. Utility classes are evil. But in this particular case we’re missing a utility method on a Vector2 class that we can’t augment ourselves, so it’s justified.
The Vectors class will live in util package, and it will provide a method that returns a Vector2 pointed in a direction according to the rotation. This will be very helpful in a moment when we’ll be rotating things.

Don’t worry (or don’t get your hopes up) – that’s about all the math we’re going to touch.

Controls

We’ll tackle controls package next, because our upcoming models will use its interface directly. We want our controls to let us know whether the player wants to move forwards, move left, move right or shoot. This is exactly how our interface is going to look like:

For now, there will be just one implementation of that interface, and since keyboard controls make the most sense in our case, we’ll start with that. It’s quite straightforward:

Model

Another package called model will contain our domain model classes and interfaces. Let’s take a bird’s eye look at original game’s screenshot and name them:

We’re inside of Arena. There’s a Player that owns a Ship. Ship is able to shoot Bullets.
Also, Ship and Bullet share a common trait – they’re Visible, which means they have some sort of shape and a color. This abstraction will come in handy when we’ll be about to render our game.
That gives us model classes Arena, Player, Ship and Bullet, as well as interface Visible.

Visible

This will basically be our “thing on the scene”. It will have color and a shape.
We’ll use LibGDX’s Polygons to represent underlying shapes because they’re easy to work with and have good support for what we’ll need later (rendering, collision detection).

Arena

Arena will be responsible for keeping Visible things inside of its bounds.
Asteroids game has a particular way of dealing with arena bounds – whenever something reached one of those, it’s immediately teleported to the other side of parallel bound. For the sake of simplicity, we’ll do the easiest implementation, which moves whole objects rather than keeping them partially on both sides.

Player

Player will be a rather simple entity, responsible mostly for managing his Ship. Notice that at any given moment a Player might not have a Ship – for example, his Ship was just destroyed and he waits to receive another one. Therefore Ship won’t be final, and it will also be Optional.

Ship

Compared to Player, Ship will be somewhat more sophisticated. It will deal with basic physics of the movement (velocity and drag) and set its underlying shape position accordingly to these coefficients.
It will also be responsible for outputting Bullets somewhere (more specifically, into the Container of Bullets, but we’ll talk more about containers later). In order to do that we need to know if it canShoot (enough time has passed since the last shot) and if itwantsToShoot (the key indicating shot was pressed).
It will be the first class we see that implements Visible interface. A common pattern for creation Visible things will be to use a set of points (vertices) to initialize its Polygon.

Bullet

Bullet will be our last model that we will create (and another Visible thing). It will move at a constant speed in a steady direction determined by the shooter – but within a limited range.
We’ll also store a Player that this bullet was shot by because it will be important in the foreseeable future (for instance when we know who shot the bullet that destroyed some other ship,
it’s trivial to award the shooter with points).

Container

So we have our models and that’s neat, but soon enough we’ll going to want to perform operations on groups of them. One such operation was already hinted in the Ship – whenever it shots, it has to output a Bullet somewhere. The most natural thing to do is to put it among other Bullets. That’s the basic notion of a Container – it holds on t a group of Things, which makes it easy to perform batch operations on them. Every Container inside this package will implement this interface:

Bullets Container

For now this will be our only Container, but more will come. It’s main responsibility will be to hold on to all Bullets that’ve been shot, update them and remove those that went out of range.

Rendering

Separation of rendering and business logic is important in programming in general, but it gets especially important in multiplayer games. We’ll talk more about it in upcoming parts, for now let’s keep our rendering neatly separated from our models and containers. To achieve this goal we’ll introduce a Renderer – an object that will hold on to another object it knows how to render, and given a right tool (which in our case will be LibGDX’s ShapeRenderer) is able to put pixels on the screen for us.

Visible Renderer

First natural candidate for having it’s own Renderer is anything that implements Visible interface, as we can directly obtain all the information needed to render it directly from Visible itself.

Player Renderer

Player is a peculiar entity, given that its Ships can change and at times be unavailable. We’ll need a VisibleRenderer for current Ship, and we’d also like Renderers that hold on to previous Ships to be garbage collected. We don’t want to manage all this complexity directly inside of a game loop, so we’ll create a dedicated PlayerRenderer. Note the default usage of WeakHashMap which will discard entries dedicated for Ships that do not exist anymore.

Container Renderer

Lastly, we’ll need a generic Renderer for Containers. Given the variadic nature of their content which can disappear at any given moment (for example Bullets can go out of range), we’ll use similar caching mechanism as in Player renderer. We’ll also externalise creation of underlying Renderers to upper layer because we can’t possibly know how to instantiate them all here.

Putting it all together

We now have all the ingredients ready to assemble the first version of our game. One last decision to make is to determine our world’s size. Let’s go with 800×600. It will also be the size of the viewport we’ll use. Let’s initialize dependencies in the Game class and then inject them into the Screen. You can see that these classes will serve as the integration layer for everything we’ve done up until this point.

In the game loop (render method) we follow a pattern of updating first and then rendering.

Pause

Let’s reflect on what we’ve accomplished so far. Go ahead and run desktop project in Gradle (you can do that quickly with ./gradlew desktop:run command on MacOS/Linux or gradlew.bat desktop:run on Windows). It should look something like this:

If it doesn’t, compare your code to the reference material and try to spot the difference.

Cool, so that’s a start. Now let’s introduce another player. Albeit offline and immobile, it will be the basis for our upcoming support for multiple players and client-server architecture.

Players Container

So far we’ve been dealing with just one player, but now our needs call for another Container – a PlayersContainer. It’ll have a bit more going on inside compared to BulletsContainer – we’ll include methods for streaming Player‘s Ships and streaming their obtained Bullets.

A place to start

We haven’t determined yet where the Ship should be introduced to the world. For the sake of quick feedback Polygon‘s default (0, 0) starting point was good enough, but now we’ll add starting position to the Ship:

A way to get there

In the actual game, the situation where a player gets his ship blasted by another player and needs a new one will happen often. That means we will need another entity that will take care of respawning ships – which naturally points as towards a Respawner name. It’s neither a model nor a container. It’s a managing entity, hence we will introduce a new manager package.

Our Respawner will have to know everything needed to determine whether a Player needs a new Ship and how to instantiate it. Its logic will be quite simple: scan through Players, find ones without Ships and give them a new one at some random point inside of arena bounds.

Noop Controls

Our immobile Player needs to be instantiated with Controls nonetheless, so let’s create an implementation that prevents him from moving:

Blast Away

There’s one last thing we’ll implement before the network part: registering hits between the Bullets and the Players.

Collision Detection

A fundamental part of registering hits is the ability to tell when Bullet and Player collided. There are whole books written on the subject, but fortunately, we’re standing on the shoulders of a giant (LibGDX), so the algorithmic work has already been done. Intuitively, being Visible means you can collide with something, so let’s add a default method to our Visible interface:

Yup, that’s it. Collision detection in one line. LibGDX’s Intersector works great with Polygons and we’ll take full advantage of that here.

Handling hits

We’ll need to introduce little changes on our models, making them react to collisions. In the case of Bullet it’s going to mark it for future removal. In the case of Player it will be removing his Ship.

We’ll also need to remove Bullets that have hit something. It’s going to be another job of BulletsContainer:

Hadron Players and Bullets Collider

Now that we have our bits and pieces of collision-related logic spread across the code, let’s wrap them up with another manager entity – a Collider. As name suggest, it’s going to orchestrate collision checks between objects and appropriately notify them if such collision occurs.

Putting it back together again

Remember AsteroidsGame and AsteroidsScreen? We’re going to revisit them now and add everything new we’ve made. PlayersContainer, Respawner, Collider as well call methods to update their state in render.

First, let’s update dependencies graph in AsteroidsGame. Note that apart from a few new objects, we’ll also change the way Players are rendered. Now they’re in a Container, so we’ll use ContainerRenderer and pass PlayerRenderer as underlying renderer.

Having initialized our dependencies, we can use them in AsteroidsScreen:

That concludes our offline part. In the next part, we’ll introduce server infrastructure and get ready to start sending some data.
Now, take a minute to run what’s been done so far and enjoy :).

If you are ready for the next part, try part 2 of Developing Lag Compensated Multiplayer Game – this time we set up the server.

Subscribe to our newsletter
Menu