java-multiplayer-libgdx-tutorial

*We assure you that we do not spam. You may receive occasional emails from us.
 You can always unsubscribe.

Java Multiplayer libgdx Tutorial

Game Start Screen

What are we going to do?
  • We will modify the “superjumper” libgdx sample available on libgdx’s site.
  • Convert it in to a 2 player real-time game using AppWarp Cloud.
  • The game will match the players and the users need to reach to castle to win game.
  • The user will get real-time feedback about the other user’s performance adding to the excitement of the game.
Eclipse project setup

Next you need to download the libgdx sample game (superjumper) project from this git repository.

Open the downloaded superjumper solution in eclipse. You will see project like below.

Set up super jumper

To create multiplayer i have used AppWarp Java SDK (1.5 as of now)

About libgdx dependencies

This sample depends on libgdx as mentioned on libgdx’s site. If you try to run superjumper sample available on libgdx site you will get error about libraries like gdx, gdx-backend-lwjgl, gdx-jnigen, gdx-openal. To remove these error you need to set these projects as library projects to your app(superjumper).

But to make your work easy i have included all libraries in libs folder of superjumper git repository

to know more about libgdx project setup watch this video or you can read this tutorial

Getting your AppWarp application keys

Since you will be integrating with AppWarp cloud services, you need to get your application keys from ShepHertz developer dashboard AppHq. These keys identify your application zone on ShepHertz cloud service and are required so that AppWarp cloud can segregate messages belonging to different applications.

Follow the simple steps by signing up (free) and getting your application keys mentioned at AppWarp site

Now open WarpController.java file in the superjumper sample project and add the values there. For example :-

public static String AppKey = "14a611b4b3075972be364a7270d9b69a5d2b24898ac483e32d4dc72b2df039ef";
public static String SecretKey = "55216a9a165b08d93f9390435c9be4739888d971a17170591979e5837f618059";
Running the multiplayer sample

Now that we’ve got everything ready, we can run the sample and see the game in action. Since this game has option to play singleplayer and multiplayer, you will need to run this on 2 emulators/devices simultaneously to play multiplayer game.

After you tap to multiplayer button, the game will connect to AppWarp and join a game room. Once inside the game room, the client will wait for the second player to join the room before the game will start.

Game Wait Screen

Now you need to do the same from the second emulator/device. AppWarp matchmaking APIs will make this second user join the same game room and the game should begin. Players need to reach the castle to complete the game. At the same time, users can see their opponent move in real-time! This in-game realtime communication is the power of AppWarp.

Game Play

Game will be considered completed in these three condition

  1. User Left: In this case one player left the game other player will be considered as winning user as his opponent has left the game.
  2. Level Complete: If any user reaches to castle then this user will be winning user.
  3. Game Over: If user hit the squirrel or user falls then other user will be declared as winning user.

Game Over

How does the integration with AppWarp work

Starting the game

First you need to initialize the WarpClient singleton with your application keys (WarpController.java)

WarpClient.initialize(apiKey, secretKey);

Next you need to connect to AppWarp cloud and join a game room (WarpController.java)

Note that AppWarp SDK provides its functionality through asynchronous APIs. This means you simply add the corresponding request listeners to the WarpClient instance to receive responses and notifications.

The file (WarpController.java) has all the code we need to do this step. It will make connection requests, room requests and zone requests (to create a room if required). So we add the corresponding listeners in OnStart()

public WarpController()
{
	initAppwarp();
	warpClient.addConnectionRequestListener(new ConnectionListener(this));
	warpClient.addChatRequestListener(new ChatListener(this));
	warpClient.addZoneRequestListener(new ZoneListener(this));
	warpClient.addRoomRequestListener(new RoomListener(this));
	warpClient.addNotificationListener(new NotificationListener(this));
}
private void initAppwarp(){
	try {
		WarpClient.initialize(apiKey, secretKey);
		warpClient = WarpClient.getInstance();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

Now that the listener is setup, we can go ahead and connect. To connect to AppWarp cloud, the client needs to pass in a unique username. In the sample I will just use a random string (you can take input from user or use some 3rd party service like facebook to uniquely identify user as well). The random username string is generated in MainMenuScreen.java file.

WarpClient.connectWithUserName(userName);

The result of connection is provided in the following callback

public void onConnectDone(ConnectEvent e) {
	if(e.getResult()==WarpResponseResultCode.SUCCESS){
		callBack.onConnectDone(true);
	}else{
		callBack.onConnectDone(false);
	}
}
public void onConnectDone(boolean status){
	if(status){
		warpClient.initUDP();
		warpClient.joinRoomInRange(1, 1, false);
	}else{
		isConnected = false;
		handleError();
	}
}

If its successful, we go ahead and try to join a room. We can also optionally initialize UDP (will be used in game play later). To join the room, we use the JoinRoomInRange method with parameters (1,1) which request the server to put the client in a room with exactly 1 user in it. If this fails we will create a new 2 player room and join that.

public void onJoinRoomDone(RoomEvent event){
	if(event.getResult()==WarpResponseResultCode.SUCCESS){// success case
		this.roomId = event.getData().getId();
		warpClient.subscribeRoom(roomId);
	}else if(event.getResult()==WarpResponseResultCode.RESOURCE_NOT_FOUND){// no such room found
		HashMap data = new HashMap();
		data.put("result", "");
		warpClient.createRoom("superjumper", "shephertz", 2, data);
	}else{
		warpClient.disconnect();
		handleError();
	}
}

Once the room is joined (either now or after creating a new one), the client needs to subscribe it. This is required to receive notifications from the room (required in game play). These concepts are explained in details here. Once subscribed we check room detail by calling getLiveRoomInfo if room has two users then we start the game otherwise wait for other user to join this room.

WarpClient.getLiveRoomInfo(roomId);
public void onGetLiveRoomInfo(String[] liveUsers){
	if(liveUsers!=null){
		if(liveUsers.length==2){
			startGame();	
		}else{
			waitForOtherUser();
		}
        }else{
			warpClient.disconnect();
			handleError();
	}
}

Game Play

The code for the game play is in the file MultiplayerGameScreen.java. If user enter on this screen this means that both user are in room and game is started. Player will has to play his game and he will also get update of other user. Other user will be shown as Green Monster on your screen.

As the player moves on the screen to complete the level, it will send updates to the remote player as well so they it can also render the movement. see in WorldRenderer.java(multiplayer)

private void renderBob () {
{
...
...
    if (side < 0){
        batch.draw(keyFrame, world.local_bob.position.x + 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
        sendLocation(world.local_bob.position.x + 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
    }else{
        batch.draw(keyFrame, world.local_bob.position.x - 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
        sendLocation(world.local_bob.position.x - 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
    }
}

The messages are sent through a utility method we've written for this sample. WarpClient allows the client to broadcast byte arrays to the room it is in. These can be sent over either TCP (default) or UDP. Remember we had initialized UDP in the first screen after successfully connecting to the cloud service.

private void sendLocation(float x, float y, float width, float height){
  try {
	JSONObject data = new JSONObject();
	data.put("x", x);
	data.put("y", y);
	data.put("width", width);
	data.put("height", height);
	WarpController.getInstance().sendGameUpdate(data.toString());
	} catch (Exception e) {
		// exception in sendLocation
	}
  }

The messages sent to the room are provided through the onUpdatePeersReceived callback. In this callback we parse the message and identify the sender, the type of message and the data associated with the message. We react on the messages accordingly.

public void onUpdatePeersReceived(UpdateEvent event) {
	callBack.onGameUpdateReceived(new String(event.getUpdate()));
}
try {
	JSONObject data = new JSONObject(message);
	float x = (float)data.getDouble("x");
	float y = (float)data.getDouble("y");
	float width = (float)data.getDouble("width");
	float height = (float)data.getDouble("height");
	renderer.updateEnemyLocation(x, y, width, height);
} catch (Exception e) {
	// exception 
}

Game Over

When the game finish we simply update room property. As other user receive notification it update its UI depending upon message received.

public void updateResult(int code, String msg){
	if(isConnected){
		STATE = COMPLETED;
		HashMap properties = new HashMap();
		properties.put("result", code);
		warpClient.lockProperties(properties);
	}
}

lockProperties

When two remote player are playing same game it is possible both can finish at the same time which can lead to race condition. Its best that the server resolves such conditions and hence we use the lockProperties API. So when the game completes, the user sends a lockProperties request to server to lock the result property. Once the result is locked for a user, the server will fail subsequent lockProperties request for the same property. To know more this AppWarp arbitration feature click here

As the game finish other user get notification update the screen on StartMultiplayerScreen.java depending upon the code to show the reason to finish game.

public void onGameFinished (int code) {
	if(code==WarpController.GAME_WIN){
		this.msg = game_loose;
	}else if(code==WarpController.GAME_LOOSE){
		this.msg = game_win;
	}else if(code==WarpController.ENEMY_LEFT){
		this.msg = enemy_left;
	}
	update();
	game.setScreen(this);
}

We also need to leave and unsubscribe the room as well as remove the listener and if the game is not in running mode then we also delete rooms. Since in this game we are using AppWarp dynamic rooms, its good practice to delete them once used (Empty dynamic rooms will anyway be automatically deleted after 60 minutes).

public void handleLeave(){
	if(isConnected){
		warpClient.unsubscribeRoom(roomId);
		warpClient.leaveRoom(roomId);
		if(STATE!=STARTED){
			warpClient.deleteRoom(roomId);
		}
		warpClient.disconnect();
	}
}
private void disconnect(){
	warpClient.removeConnectionRequestListener(new ConnectionListener(this));
	warpClient.removeChatRequestListener(new ChatListener(this));
	warpClient.removeZoneRequestListener(new ZoneListener(this));
	warpClient.removeRoomRequestListener(new RoomListener(this));
	warpClient.removeNotificationListener(new NotificationListener(this));
	warpClient.disconnect();
}

The user can tap and go back to the MainMenuScreen from here and we will restart the process. However this time we can simply start by finding a room (as we will already be connected).

Summary

In this article we saw how we can develop a multiplayer game using AppWarp Java SDK. We used an existing libgdx superjumper sample and extended it by integrating AppWarp Cloud features. We saw how clients connect to AppWarp, join and play in game rooms. The integration concepts are independent of the use of libgdx and can be applied for any Java application.

Building for iOS using Robovm

To deploy superjumper multiplayer for IOS you can use Robovm. Follow the steps explained as you would for any other project. In addition you'll need to do the following changes.

1. Add these properties to robovm.xml
    
      org.apache.harmony.xnet.provider.jsse.OpenSSLProvider
      org.apache.harmony.security.provider.cert.DRLCertFactory
      com.android.org.bouncycastle.jce.provider.BouncyCastleProvider
      org.apache.harmony.security.provider.crypto.CryptoProvider
      org.apache.harmony.xnet.provider.jsse.JSSEProvider
      com.android.org.bouncycastle.jce.provider.JCEMac$SHA1
    
    
2. To change Screen from background thread we have used following code.
  Gdx.app.postRunnable(new Runnable() {
	@Override
	public void run () {
		game.setScreen(new MultiplayerGameScreen(game, StartMultiplayerScreen.this));
	}
  });
This is required as otherwise, we get the following error as the callback from AppWarp is not on UI Thread.
Exception in thread "MessageDispatchThread" java.lang.IllegalArgumentException: Error compiling shader
3. Sound is disabled in superjumper. This is because of issues with using sounds on iOS through RoboVm.