Cocos2d-xna-multiplayer-tutorial

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

Cocos2D-XNA Multiplayer Game Tutorial

Cocos2d xna multiplayer tutorial

What are we going to do?
  • We will modify the “Gone Bananas” Cocos2d-XNA iOS sample available on Xamarin’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 make their monkey collect the maximum number of bananas in the allotted time.
  • The user’s will get real-time feedback about the other user’s monkey performance adding to the excitement of the game.
Getting the required files

This sample depends on Cocos2d-XNA and MonoGame as mentioned on Xamarin’s site. First you need to ensure you have downloaded the Cocos2d-xna and MonoGame sources.

git clone git://github.com/mono/cocos2d-xna.git
git clone git://github.com/mono/MonoGame.git

Next you need to download the latest version of AppWarp Xamarin SDK (1.5 as of now)

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

Xamarin Studio Project Setup

Open the downloaded GoneBananas solution in Xamarin studio. You will see project reference errors like below.

Xamarin Initial Setup

You need to add references to the missing projects (*.iOS.csproj) from the files we downloaded. Also add a reference to the downloaded AppWarpMonoLibrary. Your setup project should look like below.

Xamarin Final Setup

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 Constants.cs file in the GoneBananas sample project and add the values there. For example :-

public static String AppKey = "p020f89cb3b517b568b5519a95eaeedf67dc152ddec1c5fcfeafc25a50a49d9f";
public static String SecretKey = "q377b4546b51203b86d828dbbf17990157506339764fd48d28caf56eb42cc95d";
Running the multiplayer sample

Now that we’ve got everything ready, we can run the sample and see the game in action. Since this is a 2 player game, you will need to run this on 2 emulators/devices simultaneously.

You will see the following screen

Game Start

After you tap to start, 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.

in game waiting

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 tap on the screen to make the monkey move along the horizontal axis at the bottom and collect their bananas (yellow color). At the same time, users can see their opponent move and collect its bananas (red) in real-time! This in-game realtime communication is the power of AppWarp.

running game

Once the 20 seconds are over, the result of the game (scores of both players) are shown in a game over screen.

game over

How does the integration with AppWarp work

Starting the game

First you need to initialize the WarpClient singleton with your application keys (GoneBananasGame.cs)

WarpClient.initialize(Constants.AppKey, Constants.SecretKey);

Next you need to connect to AppWarp cloud and join a game room (GameStartLayer.cs)

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 (GameStartLayer.cs) 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 override void OnEnter()
{
	WarpClient.GetInstance ().AddConnectionRequestListener (this);
	WarpClient.GetInstance ().AddRoomRequestListener (this);
	WarpClient.GetInstance ().AddZoneRequestListener (this);
	Context.isRoomCreator = false; 
	status = "Tap Screen to Go Bananas!";
	base.OnEnter ();
}

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 GoneBananasGame.cs file.

WarpClient.GetInstance().Connect(Context.username); 

The result of connection is provided in the following callback

public void onConnectDone(ConnectEvent eventObj)
{
	status = "onConnectDone.."+eventObj.getResult ();
	if (eventObj.getResult () == WarpResponseResultCode.SUCCESS) {
		WarpClient.GetInstance ().initUDP ();
		WarpClient.GetInstance ().JoinRoomInRange (1, 1, true);
	}
}

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 eventObj)
{
	status = "onJoinRoomDone " + eventObj.getResult ();
	if (eventObj.getResult () == WarpResponseResultCode.SUCCESS) {
		Context.gameRoomId = eventObj.getData ().getId ();
		WarpClient.GetInstance ().SubscribeRoom (Context.gameRoomId);
	} else {
		WarpClient.GetInstance ().CreateRoom ("bananas", "monkey", 2, null);
	}
}

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 can move to the game play screen.

public void onSubscribeRoomDone(RoomEvent eventObj)
{
	status = "onSubscribeRoomDone "+eventObj.getResult ();
	if (eventObj.getResult() == WarpResponseResultCode.SUCCESS)
	{
		CCDirector.SharedDirector.ReplaceScene (GameLayer.Scene);
	}
}

Game Play

The code for the game play is in the file GameLayer.cs. In the constructor of this class, we setup the sprites and determine whether we are ready to start or not. If the client joined an existing room (with 1 player already inside), we are ready to go and will need to inform the other player inside to start the game on their end too. On the other hand, if the client is the one who created a room (had failed to join an existing room) it will need to wait for the second player before starting the game.

if (!Context.isRoomCreator) {
	byte[] message = buildStartMessage ();
	sendWarpUpdate (message);
	beginLocalGame ();
}

The messages exchanged between the clients (in the game room) are received through notification listener interface which also needs to registered. Similar to how we did in GameStartLayer.cs

public override void OnEnter()
{
	WarpClient.GetInstance ().AddNotificationListener (this);
	base.OnEnter ();
}

Once the game begins, the client will generate random bananas which fall from the top to bottom of the screen at a fixed interval. It will also update the remote player about the newly added bananas so that they can be rendered on the remote end as well.

CCSprite AddLocalBanana ()
{
	var banana = new CCSprite ("BananaLocal");
	banana.Tag = 0;
	double rnd = new Random ().NextDouble ();
	double randomX = (rnd > 0) 
		? rnd * CCDirector.SharedDirector.WinSize.Width - banana.ContentSize.Width / 2 
		: banana.ContentSize.Width / 2;
	banana.Position = new CCPoint ((float)randomX, CCDirector.SharedDirector.WinSize.Height - banana.ContentSize.Height / 2);
	byte[] message = buildBananaMessage ((float)randomX);
	sendWarpUpdate (message);
	AddChild (banana);
	var moveBanana = new CCMoveTo (5.0f, new CCPoint (banana.Position.X, 0));
	var moveBananaComplete = new CCCallFuncN ((node) => {
		node.RemoveFromParentAndCleanup (true);
		localBananas.Remove(banana);
	});
	var moveBananaSequence = new CCSequence (moveBanana, moveBananaComplete);
	banana.RunAction (moveBananaSequence);
	return banana;
}

As the player moves its monkey along the bottom of the screen to collect its bananas, it will send updates to the remote player as well so they it can also render the movement.

public override void TouchesEnded (List touches)
{
	base.TouchesEnded (touches);
	var location = new CCPoint ();
	location.X = touches [0].Location.X;
	location.Y = localMonkey.PositionY;
	float ds = CCPoint.Distance (localMonkey.Position, location);
	float dt = ds / MONKEY_SPEED;
	var moveMonkey = new CCMoveTo (dt, location);
	localMonkey.RunAction (moveMonkey);	
	byte[] message = buildMonkeyMessage (location.X);
	sendWarpUpdate (message);
}

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 sendWarpUpdate(byte[] message)
{
	if(Context.udpEnabled){
		WarpClient.GetInstance ().SendUDPUpdatePeers (message);
	}
	else{
		WarpClient.GetInstance ().SendUpdatePeers (message);
	}
}

The messages sent to the room are provided through the onUpdatePeersReceived callback. In this callback we parse the message (according to how we sent it in the buildMonkeyMessage, buildStartMessage etc.) 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 eventObj)
{
	Console.WriteLine ("onUpdatePeersReceived "+eventObj.getUpdate().Length+" bytes isUDP "+eventObj.getIsUdp());
	byte[] update = eventObj.getUpdate ();
	int updateLen = update.Length;
	BinaryReader buf = new BinaryReader(new MemoryStream(update));
	MessageType type = (MessageType)buf.ReadByte();
	float data = buf.ReadSingle ();
	char[] nameChars = buf.ReadChars (updateLen-5);
	String senderName = new String (nameChars);
	Console.WriteLine ("type: "+type+" data: "+data+" sender: "+senderName);
	if (senderName.Contains(Context.username)) {
		Console.WriteLine ("Ignoring self update");
		return;
	}
	if (type == MessageType.monkey) {
		moveRemoteMonkey (data);
	} else if (type == MessageType.banana) {
		addRemoteBanana (data);
	} else if (type == MessageType.hitcount) {
		remoteHitBananas = (int)data;
		CCSimpleAudioEngine.SharedEngine.PlayEffect ("Sounds/remotehit.mp3");
	}
	else if(type == MessageType.start){
		beginLocalGame ();
	}
}

When the game gets over (20 seconds after start), we take the local and remote hit count variables and move to the game over screen.

if(ShouldEndGame ()){
	dt = 0;
	var gameOverScene = GameOverLayer.SceneWithScore(localHitBananas, remoteHitBananas);
	CCDirector.SharedDirector.ReplaceScene (gameOverScene);
	localHitBananas = 0;
}

We also need to leave and unsubscribe the room as well as remove the listener.

public override void OnExit()
{
	WarpClient.GetInstance ().RemoveNotificationListener (this);
	WarpClient.GetInstance ().LeaveRoom (Context.gameRoomId);
	WarpClient.GetInstance ().UnsubscribeRoom (Context.gameRoomId);
	base.OnExit ();
}

Game Over

In the game over screen, we simply display the scores passed in from the game layer. 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).

// Clean up the room from the server
if (Context.isRoomCreator) {
	WarpClient.GetInstance ().DeleteRoom (Context.gameRoomId);
}

The user can tap and go back to the game start screen 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 Xamarin SDK. We used an existing Cocos2d-XNA game (GoneBananas from Xamarin) 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 Cocos2d-XNA and can be applied for any Xamarin application.