Making a multiplayer game with libGDX — Part 1 : Project setup

Sagnik Dutta
7 min readMay 8, 2021

libGDX is an open-source, cross-platform Java game development framework. It’s fairly popular in the game development community and has been used to make a lot of popular games. It also has excellent documentation which makes learning it a breeze if you have some prior development experience.

While game engines like Unity or Unreal Engine are easy to get started with and are great for rapid development, if you’re like me and just want to take a step back and dive into the nitty gritty details these engines abstract out from you, or you just want to polish up your OOP or Java skills, I would strongly urge you to give libGDX a go.

In this series, I am going to walk you through the process of creating a simple multiplayer game using libGDX. The goal is to understand the various concepts involved and set up a workflow which you can then extend and apply to your own games. In this article, we are going to concentrate on setting up our application by adding in all of the dependencies. I will also be explaining the function of each of these dependencies in our project.

I would like to stress though that this series is not recommended for someone who’s completely new to programming. I am going to assume that you have some prior experience with software development and are familiar with the concepts of OOP.

Let’s get started!

Prerequisites

  1. Java 8 or above (https://adoptopenjdk.net/)
  2. Node.js 14 or above (https://nodejs.org/)
  3. An IDE or text editor of your choice. I personally prefer using IntelliJ for the Java side of things and Sublime Text 3 for the Node.js bit.
  4. Basic understanding of programming in Java and Nodejs (javascript).

That’s it! That’s all you need to follow along.

Setup

So to start off, we need to follow the instructions in https://libgdx.com/dev/ and generate a libGDX project through the project generator. Once that is done, go ahead and open up the project in any IDE of your choice.

If you look inside the root directory of your project, you’ll find a file named build.gradle. Open it up and scroll right to the bottom of the file and you should see a block like this

project(":core") {
apply plugin: "java-library"


dependencies {
api "com.badlogicgames.gdx:gdx:$gdxVersion"
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"

}
}

We are going to add in a few more dependencies in here, the final version should look like this.

project(":core") {
apply plugin: "java-library"


dependencies {
api "com.badlogicgames.gdx:gdx:$gdxVersion"
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
compileOnly "org.projectlombok:lombok:1.18.20"
annotationProcessor "org.projectlombok:lombok:1.18.20"
implementation "com.google.dagger:dagger:2.35.1"
annotationProcessor "com.google.dagger:dagger-compiler:2.35.1"
api "io.socket:socket.io-client:2.0.0"
}
}

Don’t worry, I’ll explain the function of each of these dependencies in just a bit, but for now this is all we need to do on in our Java project.

Next up, create a separate directory. This is where we are going to create our game server using nodejs. Open up a terminal and run the following command

npm init

This will start a wizard where you can enter the name of your app, description, license, etc. Type in any name and just leave everything else at default for now (you can always edit this later). This will create a file called package.json.

Next, create another file called index.js and leave it blank for now. We’ll come back to this later. Run the following commands

npm install --save express
npm install --save socket.io

This will download and install express and socket.io. These are the only two dependencies we need on our server.

That’s it! We are done with setup!

Dependencies

In this section I’ll walk you through the dependencies we added to our project and their function. The dependencies we included are Lombok, Dagger, Socket.io (client in Java project and server in Nodejs project) and Express. I will skip the explanation for Express since it’s so popular it’s almost synonymous with any Nodejs server. If you want to dive right into building the game, you can skip this section although I would only recommend doing that if you are already familiar with all of these.

Now that that’s out of the way, let’s take a look at our dependencies one by one.

  1. Lombok

Lombok is a library which auto-generates boiler plate code which we would otherwise have to manually add in. This is done by annotating our classes and fields with Lombok annotations. This is especially useful while defining POJOs. For example, if we did this the regular way with constructors and getters and setters, we would have something like this.

public class ExamplePojo {

private int field1;

private Boolean field2;

public ExamplePojo(int field1, Boolean field2) {
this.field1 = field1;
this.field2 = field2;
}

public int getField1() {
return field1;
}

public Boolean getField2() {
return field2;
}

public void setField1(int field1) {
this.field1 = field1;
}

public void setField2(Boolean field2) {
this.field2 = field2;
}
}

If we use Lombok however, the equivalent code would look like

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ExamplePojo {

private int field1;

private Boolean field2;
}

All of the getters and setters and constructors are still generated when we compile this code (You might need to configure your IDE to allow annotation processing for the changes to reflect in the IDE).
As you can see, this improves readability a lot since all of the distracting boilerplate code is removed. There are a lot of annotations in the Lombok library and they allow you to fine tune the code generation exactly to your liking.

I personally love using the @Builder and @Value annotations since I generally try to make my POJOs immutable and the builder pattern improves the readability of the code a lot.
We can add Lombok to a gradle project by including the following dependencies

compileOnly   "org.projectlombok:lombok:1.18.20"
annotationProcessor "org.projectlombok:lombok:1.18.20"

2. Dagger

Dagger is a compile-time dependency injection framework which can automatically inject dependencies and create objects for us. For example, let’s assume the classes in our project have a dependency graph like this.

So our java code would look something like this.

class ClassA {
private ClassB classB;
private ClassC classC;

public ClassA(ClassB classB, ClassC classC) {
this.classB = classB;
this.classC = classC;
}
}

class ClassB {
public ClassB() {
}
}

class ClassC {
private ClassD classD;

public ClassC(ClassD classD) {
this.classD = classD;
}
}

class ClassD {
public ClassD() {
}
}

public class Main {
public static void main(String[] args) {
ClassA classA = new ClassA(new ClassB(), new ClassC(new ClassD()));
}
}

See that main() method? In order to create an object of ClassA we had to instantiate every single object in it’s dependency tree. You can see how this can get very unwieldy when you have a project with hundreds of classes.

This is where Dagger comes in. Dagger can automatically check the dependency tree and generate code to inject the required objects for us. All we have to do is annotate the constructors of the classes in the dependency tree with @javax.inject.Inject which is a standard java annotation, and create a Dagger component to provide the class we are interested in. We can even make our classes Singleton by annotating them with @javax.inject.Singleton. So our modified code will look like this.

import dagger.Component;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
class ClassA {
private ClassB classB;
private ClassC classC;

@Inject
public ClassA(ClassB classB, ClassC classC) {
this.classB = classB;
this.classC = classC;
}
}

@Singleton
class ClassB {

@Inject
public ClassB() {
}
}

@Singleton
class ClassC {
private ClassD classD;

@Inject
public ClassC(ClassD classD) {
this.classD = classD;
}
}

@Singleton
class ClassD {

@Inject
public ClassD() {
}
}
@Singleton
@Component
interface AppComponent {
ClassA getClassA();
}

public class Main {
public static void main(String[] args) {
AppComponent appComponent = DaggerAppComponent.create();
ClassA classA = appComponent.getClassA();
}
}

See the main() method now? We can directly fetch an instance of ClassA by using the AppComponent interface we annotated with @Component and Dagger takes care of all of the dependency injection for us. You will notice that we are referring to a class called DaggerAppComponent , this class will be generated by dagger when you compile your code and it will have the same name as your component interface with a ‘Dagger’ prefix. In case you have your components in a separate package (which you most certainly will in an actual application), the generated component class will be present in the same package as the Component interface.

The example we saw was a case where Dagger was auto injecting all of our dependencies for us. We also have the ability to control which dependencies are being used by using dagger modules but we will get into that when we start building our game.

We can add dagger to a gradle project by adding the following dependencies

implementation "com.google.dagger:dagger:2.35.1"
annotationProcessor "com.google.dagger:dagger-compiler:2.35.1"

3. Socket.io

Socket.io is a library which acts as a wrapper over Websockets. The main difference between websocket communication and standard http is that requests in http are unidirectional (the client makes requests to the server to which the server responds), whereas Websockets are bidirectional (both the server and the client can send requests to each other).

This is especially helpful for our use case of making a multiplayer game because each player would also need to be aware of the other player’s actions. The way we could have achieved this using http is by using long polling. That would however result in a lot of calls to the server especially if we want to keep our view updated in real time.

Using socket.io, we can make the server send messages to each client whenever the server’s state changes, which means that clients do not need to keep polling the server and can just act on the events sent by the server.

It does introduce some challenges with libGDX though, since all screen renders are done by the main thread whereas, the socket listeners would be running on other async threads. We will go through how we can tackle this problem and still keep our code modular in the next part of this series.

Conclusion

Thanks for sticking around so far! Hopefully you learnt something new. We’ll dive into making our game in the next part.

--

--

Sagnik Dutta

Software Engineer who loves making games on the weekends!