Tutorial: Creating Multiplayer Games
Tutorial: Creating Multiplayer Games
Tutorial: Creating Multiplayer Games
The last part is the most difficult part of every multiplayer game. The problem is how to make sure
that both players have exactly the same view on the game. For example, in the pong game, both
players should see the ball at exactly the same place. Game Maker will provide the tools to do this
but you will have to design the communication yourself for each game you make.
Setting up a connection
The standard way in which a multiplayer game works is as follows. Each player runs a copy of the
game. They do though run in different modes. One player runs his or her game in a server mode.
The others run the game in a client mode. The server should start the game first and creates the
game session. The others can then join this session to join the game. The players must decide on the
mechanism used for communicating between the computers. On a local area network, the easiest is
to use an IPX connection (see below for more details). If all players are connected to the Internet
TCP/IP is normally used. In this protocol the clients must know the IP address of the server. So the
player running the game in server mode must give his IP address to the other players (for example
by sending them an email). You can find your IP address by using the program called winipcfg.exe
in your windows directory. You can also use the Game Maker function mplay_ipaddress()
for this. A m ore old-fashioned way of connecting is using a modem connection (in which case the
client must know the phone number of the server and provide this) or using a serial line.
Please realize that communication is getting more difficult now that people use firewalls and
routers. These tend to block messages and convert IP addresses. If you have problems setting up a
connection this might be the reason. Best first test with some commercial game whether the
connection can be made.
So for two computer to communicate they will need some connection protocol. Like most games,
Game Maker offers four different types of connections: IPX, TCP/IP, Modem, and Serial. The IPX
connection (to be more precise, it is a protocol) works almost completely transparent. It can be used
to play games with other people on the same local area network. The protocol needs to be installed
on your computer to be used. (If it does not work, consult the documentation of Windows. Or go to
the Network item in the control panel of Windows and add the IPX protocol.) TCP/IP is the internet
protocol. It can be used to play with other players anywhere on the internet, assuming you know
their IP address. On a local network you can use it without providing addresses. A modem
connection is made through the modem. You have to provide some modem setting (an initialization
string and a phone number) to use it. Finally, when using a serial line (a direct connection between
the computers) you need to provide a number of port settings. There are four GML functions that
can be used for initializing these connections:
Your game should call one of these functions exactly once. All functions report whether they were
successful. They are not successful if the particular protocol is not installed or supported by your
machine.
So the first room in our game should show the four possibilities and let the player pick one. (Or
only allow for those protocols that you want. The last two might be too slow for your game.) We
call the initialization function in the mouse event and, if successful, go to the next room. Otherwise
we give an error message. So in the mouse event of the IPX button we place the following piece of
code:
{
if (mplay_init_ipx())
room_goto_next()
else
show_message('Failed to initialize IPX connection.')
}
When the game ends, or when the game no longer wants to use the multiplayer facility, you should
use the following routine to end it:
mplay_end()ends
You should also call this routine before you want to make a new, different connection.
Game sessions
When you connect to a network, there can be multiple games happening on the same network. We
call these sessions. These different sessions can correspond to different games or to the same game.
A game must uniquely identify itself on the network. Fortunately, Game Maker does this for you.
The only thing you have to know is that when you change the game id in the options form this
identification changes. In this way you can avoid that people with old versions of your game will
play against people with new versions.
If you want to start a new multiplayer game you need to create a new session. For this you can use
the following routine:
number that indicates the maximal number of players allowed in this game (use 0 for an
arbitrary number). playname is the name of you as player. Returns whether successful.
In many cases the player name is not used and can be an empty string. Also, the session name is
only important if you want to give people the option to choose the session they want to join.
So one instance of the game must create the session. The other instance(s) of the game should join
this session. This is slightly more complicated. You first need to look what sessions are available
and then choose the one to join. There are three routines important for this:
mplay_session_find() searches
for all sessions that still accept players and returns the
So what you standard do is call mplay_session_find()to find all existing sessions. The you either
repeatedly use mplay_session_name() to show them to the player and let him make a choice, or
you immediately join the first session. (Note that finding the session takes a bit of time. So don't
call this routine in each step.)
A player can stop a session using the following routine:
mplay_session_end()ends
It is useful to first notify the other player(s) of this but this is not strictly necessary.
So in our game, the second room gives the user two choices: either to create a new session, or to
join an existing session. For the first choice we perform the following code in the mouse event:
{
if (mplay_session_create('',2,''))
{
global.master = true;
room_goto_next();
}
else
show_message('Failed to create a session.')
}
Note that we set a global variable master to true. The reason is that in the game we want to make a
distinction between the main player (called the master) and the second player (called the slave). The
master will be responsible for most of the game play while the slave simply follows him.
The second choice is to join an existing game. Here the code looks as follows.
{
if (mplay_session_find() > 0)
{
if (mplay_session_join(0,''))
{
global.master = false;
room_goto_next();
}
else
show_message('Failed to join a session.')
}
else
show_message('No session available to join.')
}
So in this game we simply join the first session that is available. Because we indicated that the
maximal number of players is 2, no other player can join the session anymore.
mplay_player_find()searches
for all players in the current session and returns the number
of players found.
mplay_player_name(numb)returns
In our third room we simply wait for the second player to join. So we put some object there and in
the step event we put:
{
if (mplay_player_find() > 1)
room_goto_next();
}
Synchronizing actions
Now that we set up the connection, created a session, and have two players in it, the real game can
begin. But this also means that the real thinking about communication must begin. The main
problem in any multiplayer game is synchronization. How do we make sure that both players see
exactly the same picture of the game world? This is crucial. When one player sees the ball in our
game at a different place than the other player, strange things can happen. (In the worst case, for
one player the ball is hit with the bat while for the other player the ball is missed.) The games easily
get out of sync, which creates havoc in most games.
What is worse, we have to do this with a limited amount of communication. If you are e.g. playing
over a modem, connections can be slow. So you want to limit the amount of communication as
much as possible. Also there might be delays in when the data arrives on the other side. Finally,
there is even the possibility that data gets lost and ne ver arrives on the other end.
How to best handle all these problems depends on the type of game you are creating. In turn-based
games you will probably use a rather different mechanism than in high-speed action games.
Game Maker offers two mechanisms for communication: shared data and messages. Shared data is
the easiest mechanism to use. Sending messages is more versatile but requires that you understand
better how communication works.
mplay_data_write(ind,val)write
For the slave bat we write a similar piece of code. Now both the master and the slave must make
sure that they place the bat of the other at the correct position. We do this in the step even. If we are
the slave, in the step event of the master bat we must set the position. So here we use the following
code:
{
if (global.master) exit;
y = mplay_data_read(1);
}
if (global.master)
{
mplay_data_write(3,x);
mplay_data_write(4,y);
}
else
{
x = mplay_data_read(3);
y = mplay_data_read(4);
}
}
With this the basic communication is done. What remains is the handling of the start of the game,
the scoring of points, etc. Again, all this is best left purely under control of the master. So the
master decides when a player looses, changes the score (for which we can use an extra location to
communicate it to the other), and lets a new ball start. You can check out the enclosed game
pong1.gmd for details.
Note that with the communication scheme described, the two games can still be a bit out of sync.
This is normally not a problem. You can avoid this by having some synchronization object that uses
some values to make sure that both sides of the game are ready before anything is drawn on the
screen. This should be used with care though because it might cause problems, like deadlock in
which both sides wait for the other.
Realize that when a player joins a game later the changed shared values are NOT send to the new
player (this would take too much time). So only new changes after that moment are send to the new
player.
Messaging
The second communication mechanism that Game Maker supports is the sending and receiving of
messages. A player can send messages to one or all other players. Players can see whether messages
have arrived and take action accordingly. Messages can be sent in a guaranteed mode in which you
are sure they arrive (but this can be slow) or in a non-guaranteed mode, which is faster.
We will first use messages to add some sound effects to our game. We need a sound when the ball
hits a bat, when the ball hits a wall, and when a player wins a point. Only the master can detect such
events. So the master must decide that a sound must be played. It is easy to do this for its own
game. It can simply play the sound. But it must also tell the slave to play the sound. We could use
shared data for this but that is rather complicated. Using a message is easier. The master simply
sends a message to the slave to play a sound. The slave listens to the messages and plays the correct
sound when asked to do so.
The following messaging routines exist:
mplay_message_send(player,id,val)sends
A few remarks are in pla ce here. First of all, if you want to send a message to a particular player
only, you will need to know the players unique id. As indicated earlier you can obtain this with the
function mplay_player_id() . This player identifier is also used when receiving messages from a
particular player. Alternatively, you can give the name of the player as a string. If multiple players
have the same name, only the first will get the message.
Secondly, you might wonder why each message has an integer identifier. The reas on is that this
helps your application to send different types of messages. The receiver can check the type of
message using the id and take appropriate actions. (Because messages are not guaranteed to arrive,
sending id and value in different messages would cause serious problems.)
For the playing of sounds we do the following. When the master determined that the ball hits the bat
it executes the following piece of code:
{
if (!global.master) exit;
sound_play(sound_bat);
mplay_message_send(0,100,sound_bat);
That is, it checks whether there is a message and if so checks to see what the id is. If this is 100 it
plays the sound that is indicated in the message value.
More general, you game typically has a controller object in your rooms that, in the step event, does
something like:
{
while (mplay_message_receive(0))
{
from = mplay_message_player();
name = mplay_message_name();
messid = mplay_message_id();
val = mplay_message_value();
if (messid == 1)
{
// do something
}
else if (messid == 2)
{
// do something else
}
// etc.
}
Carefully designing the communication protocol used (that is, indicating which messages are send
by who at what moments, and how the others must react to them) is extremely important.
Experience and looking at examples by others helps a lot.
Dead-reckoning
The pong game described above has a serious problem. When there is a hick-up in communication
the ball of the slave will temporarily stand still. No new coordinate positions arrive and, hence, it
does not move. This problem occurs in particular when the distance between the computers is large
and/or the communication is slow. When games get more complex, you will need more values to
describe the state of the game. When you change a lot of values in each step, a lot of information
must be transmitted. This can cost a lot of time, slowing your game down or making things go out
of sync.
A first way to make the communication of shared data a bit faster is to no longer demand
guaranteed communication of the data. This can be achieved by using the function:
A better technique used to remedy this problem is called dead-reckoning. Here we send information
only from time to time. In between the game itself guesses what is happening based on the
information it has.
We will now use this for our pong game. Rather than sending the ball position in each step we also
send information about the balls speed and direction. Now the slave can do most of the calculations
itself. As long as no new information arrives from the master, it simply computes where the ball
moves.
We will not use shared data in this case. Instead we use messages. We use messages that indicate a
change in ball position, ball speed, bat position, etc. The master sends such messages whenever
something changes. The controller object in the slave listens to these messages and sets the right
parameters. But if the slave receives nothing it still lets the ball move. If it makes a slight mistake, a
later message from the master will correct the position. So for example, in the step event of the ball
we put the following code:
{
if (!global.master) exit;
mplay_message_send(0,11,x);
mplay_message_send(0,12,y);
mplay_message_send(0,13,speed);
mplay_message_send(0,14,direction);
}
In the step event of the controller object we have the following code:
{
while (mplay_message_receive(0))
{
messid = mplay_message_id();
val = mplay_message_value();
// Check for bat changes
if (messid == 1) bat_left.y = val;
if (messid == 2) bat_right.y = val;
// Check for ball changes
if (messid == 11) object_ball.x = val;
if
if
if
//
if
}
}
Note that the messages don't need to be sent in a guaranteed mode. If we miss one from time to time
this is not a serious problem. You can find the adapted game in the file pong2.gmd .
Now you will be disappointed when you run game pong2. There are still hiccups. What causes
these? The reason is that transmission might be slow. This means that the slave might receive
messages that were sent a while back. As a result it will set the ball position back a bit and then,
when it receives the new messages, sets it forward again. So let us do a thir d try, which you can find
in the file pong3.gmd. This case we only exchange information when the ball hits a bat. So the
whole rest of the motion is done using dead-reckoning. The master is responsible what happens at
the side of the master's bat and the slave is responsible for what happens at the other side. While the
ball moves from one side to the other no messages are exchanged anymore.
As you will see the ball moves smoothly now. Only when it hits a bat a short hick-up can occur or
the ball might start moving before it reaches the opponents bat. The reason is that this mechanism
assumes that both games run at exactly the same speed. If one of the computers is slow this might
cause a problem. But a hick-up at a bat is much more acceptable than a hick-up during the motion.
To avoid this last type of problem you need more advanced mechanisms. For example, you can
send timing information such that each side knows how fast the game is running on the other
computer and can make corrections accordingly.
I hope you by now understand how difficult synchronization is. You might also start appreciating
how commercial games achieve this. They have to deal with exactly the same problems.
A chat program
For our second demo we make a little chat program. Here we will allow an arbitrary number of
players. Also we will allow for multiple sessions and give the player the choice to pick a particular
session. We will use messages with strings to send the text typed around.
I used a slightly more complicated mechanism to make the connection. The first room now has four
choices for the four different types of connections. But is does not make the connections. Instead it
only fills in a global variable connecttype. In the second room the player can again choose whether
to create a game (or actually a chatbox) or join one. Only now is the connection initialized.
Depending on whether the player creates or joins a game some questions are asked.
After the connection is made successfully, the session is created or joined. This time the player is
asked for his/her name such that players can be identified. The join part is a bit more complicated
this time. We construct a menu of all different session available from which the player can choose
one.
Normally, when the game that c reated the session ends, the session ends. For a chat program this is
probably not what you want. The other players should be able to continue chatting. This can be
changed using the function:
The whole mechanism of the first two rooms you will probably want to reuse for your games
because it is often largely the same.
After this we move to the c hatbox room. There is just one controller object that does all the work
here. I will not explain the details of letting the user type in the lines of text and displaying the last
couple of lines. It is all put in some scripts that you can reuse if you want. It is all rather
straightforward (when you know how to program in GML). The only part that is still interesting is
that whenever the player presses the enter key, the typed line is send to all other players, with the
players name in front of it. Also when a player joins or quits, he sends a message to all other players
indicating what he did.
Look at the file chat.gmd for details.
Conclusion
The multiplayer facilities in Game Maker make it possible to create fancy multiplayer games. The
functions though only help you with the low-level communication. You yourself have to design the
communication mechanism used. This is a careful process. It should be designed while you design
the game. It is very difficult to add effective multiplayer later. Here are some global guidelines:
For most simple game a master-slave mechanism is easiest to make
Carefully determine who is responsible for what data
Use dead-reckoning whenever possible
Try to rely as little as possible on guaranteed communication