Getting started with multiplayer

Description

AGK provides a set of commands that can be used to quickly and easily get communications up and running for a multiplayer enabled game. The commands provided allow you to host or join a network, send messages around the network, retrieve messages and find out information such as the amount and names of players in a game.

This example progam demonstrates the process of creating a simple multiplayer game. It has support for two players, with one being able to host the game, while the other can join the game. The host player can move around the screen by using the accelerometer, and the player who joined the game can observe the host player moving around on their device.

It's important to realise that one player is always responsible for hosting or starting a game and once a game has been hosted other players can join in.

Overview

This example will handle the following:

Initial set up

The game relies on two images, chip5.png and chip25.png:

The yellow chip (chip5.png) is used for the host and the blue chip (chip25.png) is for the player who joins the game.

To get started a display aspect of 4.0 / 3.0 is chosen. This is effectively the same thing as saying our base resolution will match 1024 x 768. When the game runs on a device that has a different base resolution it will scale accordingly, while maintaining the aspect ratio of 4.0 / 3.0.

SetDisplayAspect ( 4.0 / 3.0 )

The two chip images are loaded with chip5.png using ID 1 and chip25.png using ID 2:

LoadImage ( 1, "chip5.png" )
LoadImage ( 2, "chip25.png" )

The next step is to create a sprite that uses the yellow chip, this sprite will be positioned on the left side of the screen. After this another sprite is created, which will use the blue chip and this gets positioned on the right side of the screen:

CreateSprite ( 1, 1 )
SetSpritePosition ( 1, 10, 20 )
SetSpriteSize ( 1, 10, -1 )

CreateSprite ( 2, 2 ) SetSpritePosition ( 2, 50, 20 ) SetSpriteSize ( 2, 10, -1 )

Hosting or joining a game

The main loop needs to deal with several tasks, the first of which is to allow the user to decide whether to host or join a game. A user can host a game by touching or clicking the yellow sprite on the left. If the user wants to join a game they can touch or click the blue sprite on the right.

Hosting a game

One player will need to host the game on their network. To start hosting a game call the command HostNetwork. This command takes several parameters:

The command also returns an integer that contains an ID number for the hosted network. This value needs to be stored for use later on.

This line shows a network being hosted that is called "AGK Test Game", with the player being called "Player 1", using port 48230 and returning the ID number of the network into the variable NetworkID:

NetworkID = HostNetwork ( "AGK Test Game", "Player 1", 48230 )

Joining a game

To join a hosted game call the command JoinNetwork. There are two ways of calling this command. The first option is to call JoinNetwork and pass in a network name and the name of the player e.g.

NetworkID = JoinNetwork ( "AGK Test Game", "Player 2" )

In this instance we are attempting to join a network that is called "AGK Test Game" and our player name is specified as "Player 2". Internally AGK will search for this game over the network, which is ideally suited to local area networks, where a group of devices are connected together.

The alternative variation of the command JoinNetwork will be needed when a player wants to join a game over the internet. This command's parameters are - an IP address, a port number and the name of the player. It can be called like this:

NetworkID = JoinNetwork ( "123.4.5.6", 48230, "Player 2" )

For this situation the player hosting the game will need to let the player joining the game know their IP address and the port they have selected.

Choosing to host or join a game

As mentioned earlier the yellow chip is for the player hosting the game and the blue chip is for the player joining the game. The code will check for input and find out which sprite was hit. If the sprite hit has an ID of 1 (the yellow sprite) then a network will be hosted. If the sprite hit has an ID of 2 (the blue sprite) then a network will be joined. This example will not use IP addresses as it is intended to run on a local area network. The code will also display instructions for the user through two calls to Print. Here's an extract of the code:

Print ( "Select yellow chip to host a game" )
Print ( "Select blue chip to join a game" )

if GetPointerPressed ( ) = 1 hit = GetSpriteHit ( GetPointerX ( ), GetPointerY ( ) )
if ( hit = 1 ) NetworkID = HostNetwork ( "AGK Test Game", "Player 1", 48230 ) Type = 0 endif
if ( hit = 2 ) NetworkID = JoinNetwork ( "AGK Test Game", "Player 2" ) Type = 1 endif endif

Another point to mention is that the variable Type is used to store the choice of the user, this is required later on in the game so that we know whether a user has joined or hosted a game.

Finding players in a game

After hosting or joining a game, one task that needs to be carried out is to determine how many players are involved and find out information such as their name. To begin this process call the command GetNetworkFirstClient and pass in the ID number of the hosted or joined network. This command returns an ID number for the first client or player in the game:

id = GetNetworkFirstClient ( NetworkID )

Once you have the ID number for a client you can call commands such as GetNetworkClientName and pass in the ID of the network and the ID of the player e.g.

id = GetNetworkFirstClient ( NetworkID )

name$ = GetNetworkClientName ( NetworkID, id )

After calling GetNetworkFirstClient, you can determine other clients in the list by calling GetNetworkNextClient, which will return an ID number for the next player in the list. Keep on calling this command until it returns 0, which means you have hit the end of the list and have found all the clients.

This example code demonstrates the typical procedure of finding players in a game and retrieving information, like their name:

id = GetNetworkFirstClient ( NetworkID )

while id <> 0 Print ( GetNetworkClientName ( NetworkID, id ) )
id = GetNetworkNextClient ( NetworkID ) endwhile

The host

For this example the host player will be allowed to move the yellow sprite around through the use of the accelerometer (or keyboard if no accelerometer is present). To handle this a check is first made to see whether the player is hosting the game (by checking the variable Type) and then the yellow sprite's position is updated based on any input:

if ( Type = 0 )
    x# = GetSpriteX ( 1 ) + GetDirectionX ( )
    y# = GetSpriteY ( 1 ) + GetDirectionY ( )

SetSpritePosition ( 1, x#, y# ) endif

Sending data from the host

The host is going to send the location of its sprite to the player that joins the game. The way to deal with this is to initially create a network message. Once created a message can have data added to it and then sent out across the network. A message gets created by using the command CreateNetworkMessage. This command returns an ID number for the message, which needs to be saved. This line demonstrates the creation of a message:

message = CreateNetworkMessage ( )

Integers, floats and strings can be added to the message by using the commands AddNetworkMessageInteger, AddNetworkMessageFloat and AddNetworkMessageString. These commands all have two parameters - the first is the ID number of the message and the second is the actual data. For our example the location of the host's sprite is going to be sent over the network. This can be handled using these lines of code:

AddNetworkMessageFloat ( message, GetSpriteX ( 1 ) )
AddNetworkMessageFloat ( message, GetSpriteY ( 1 ) )

With the message in place the only task remaining is to send it out. To send a message call the command SendNetworkMessage. This command has three parameters - the ID of the network to send the message over, the ID of the client to send the message to, the ID of the message to send. In our example this line is used:

SendNetworkMessage ( NetworkID, 0, message )

This has the effect of sending out the previously created message to all players on the network. The reason why all players will receive this message is because a value of 0 has been used for the client ID parameter. In situations where a message needs to go to a specific player then store the player ID from the list of players and use this as the second parameter.

The host does not need to do any more work. At this point it will continually send out the position of its sprite to the player who joins.

Retrieving data

The client can poll for messages from the server by calling the command GetNetworkMessage. This command will return an ID number for a message. If this ID number is not 0 then a message is available to be read. The aim is to continually loop round while messages are available and deal with their data. This example code demonstrates a typical way of dealing with messages:

message = GetNetworkMessage ( NetworkID )

while message <> 0 // get data from message
// delete the current message as it is no longer needed DeleteNetworkMessage ( message )
// find the next message message = GetNetworkMessage ( NetworkID ) endwhile

Notice that after data has been retrieved from the message the actual message needs to be deleted by calling DeleteNetworkMessage and passing in the ID of the message.

Data from a message can be retrieved by calling GetNetworkMessageInteger, GetNetworkMessageFloat and GetNetworkMessageString. Messages being sent out from the host contain the X and Y positions of the host's sprite, therefore to process a message correctly on the client side it's a case of retrieving the message and then making two calls to GetNetworkMessageFloat and saving the X and Y positions. The previous code block can be modified to this:

message = GetNetworkMessage ( NetworkID )

while message <> 0 // get data from message x# = GetNetworkMessageFloat ( message ) y# = GetNetworkMessageFloat ( message )
SetSpritePosition ( 1, x#, y# )
// delete the current message as it is no longer needed DeleteNetworkMessage ( message )
// find the next message message = GetNetworkMessage ( NetworkID ) endwhile

After the X and Y positions have been retrieved from the message these get applied to sprite 1 (the host's sprite).

Full code listing

Everything is now in place. Here's the final code for our program:

SetDisplayAspect ( 4.0 / 3.0 )

LoadImage ( 1, "chip5.png" ) LoadImage ( 2, "chip25.png" )
CreateSprite ( 1, 1 ) SetSpritePosition ( 1, 10, 20 ) SetSpriteSize ( 1, 10, -1 )
CreateSprite ( 2, 2 ) SetSpritePosition ( 2, 50, 20 ) SetSpriteSize ( 2, 10, -1 )
State = 0 Type = 0 NetworkID = 0
do if State = 0 Print ( "Select yellow chip to host a game" ) Print ( "Select blue chip to join a game" )
if GetPointerPressed ( ) = 1 hit = GetSpriteHit ( GetPointerX ( ), GetPointerY ( ) )
if ( hit = 1 ) NetworkID = HostNetwork ( "AGK Test Game", "Player 1", 48230 ) Type = 0 endif
if ( hit = 2 ) NetworkID = JoinNetwork ( "AGK Test Game", "Player 2" ) Type = 1 endif State = 1 endif endif
if State = 1 and IsNetworkActive ( NetworkID ) <> 0 id = GetNetworkFirstClient ( NetworkID )
while id <> 0 Print ( GetNetworkClientName ( NetworkID, id ) )
id = GetNetworkNextClient ( NetworkID ) endwhile
if Type = 0 x# = GetSpriteX ( 1 ) + GetDirectionX ( ) y# = GetSpriteY ( 1 ) + GetDirectionY ( )
SetSpritePosition ( 1, x#, y# )
message = CreateNetworkMessage ( )
AddNetworkMessageFloat ( message, x# ) AddNetworkMessageFloat ( message, y# )
SendNetworkMessage ( NetworkID, 0, message ) endif
if Type = 1 message = GetNetworkMessage ( NetworkID )
while message <> 0 x# = GetNetworkMessageFloat ( message ) y# = GetNetworkMessageFloat ( message )
SetSpritePosition ( 1, x#, y# )
DeleteNetworkMessage ( message )
message = GetNetworkMessage ( NetworkID ) endwhile endif endif
Sync ( ) loop

Conclusion

This is a very simplistic approach to dealing with multiplayer functionality. A game is likely to need much more comprehensive code to deal with extra players, and in particular extra attention to the messages being sent and retrieved. Unlike this example, which simply sends X and Y positions of a sprite, a more realistic scenario would be for a game to be sending numerous messages. As an example, a host may need to keep track of player lives, scores, positions and certain in game actions such as firing weapons. One way of dealing with this is to ensure that every message being sent out always starts with an ID number, followed by the relevant data. This example shows two messages being sent out, the first is given an arbitrary ID of 10 and contains the number of lives for the host, the second has an ID of 50 and shows the score for the host:

message = CreateNetworkMessage ( )
AddNetworkMessageInteger ( message, 10 )
AddNetworkMessageInteger ( message, lives)
SendNetworkMessage ( networkID, 0, message )

message = agk::CreateNetworkMessage ( ) AddNetworkMessageInteger ( message, 50 ) AddNetworkMessageInteger ( message, score ) SendNetworkMessage ( networkID, 0, message )

Whenever retrieving messages the code would need to call GetNetworkMessageInteger to find out what kind of message is being delivered e.g. lives or score and from there know the exact layout and whether it's necessary to obtain 2 floats and an integer or a string, 1 float and 5 integers.