9. Adding a player¶
Okay, okay, I know we already added a player in Step 5.
9.1. The create event¶
To set the player up, we only have to make minimal adjustments. Create a new code block in the create event, and insert it before the other block.
Start the code block with
mp_sync();
This will tell the engine to sync this object. Once this was created on one client, this instance wil also be sent to all other players.
If you add one player instance to your room and connect 4 players togther, each player will have four player instances.
Now we need to tell the engine what variables to sync and how:
mp_addPosition("Pos",5*room_speed);
This will tell the engine to sync the position variables x
and y
every 5 seconds. 5*room_speed
means 5 seconds. The first argument of
mp_addPosition
is the name of group. You can choose that however you
want.
What we just added is a so called variable group. The group is
called “Pos”, get’s synced every 5 seconds and sync the variables x
and y
.
Let’s set how the position should be synced:
mp_setType("Pos",mp_type.SMART);
This changes the sync type to the variable group “Pos”. The default sync type is FAST. We are changing it to SMART. Here is a list of what every sync type does:
- FAST (default, you don’t have to use
mp_setType
if you want to use that): In our case, if we had chosen FAST, the engine would send the position of our player every 5 seoncds once. Since we are using UDP for networking, there is no assurance, that it will actually arrive. That means we don’t know if the other players actually get our position every 5 seoncds. The packet could get lost. However, using FAST is very... FAST. You will see when we want to use FAST later in this section. - IMPORTANT: When using IMPORTANT, in our case the position would still be synced every 5 seoncds. However, we are using a special way in GMnet ENGINE to make sure the packet arrives. Every 5 seconds, the server/client will flood the other players with the information, until they respond back, that they got it. That’s how we assure the information is actually sent. This is however a pretty traffic heavy operation and using that in too short intervals will cause lagg in both connection and framerate.
- SMART: This is like IMPORTANT but better. Instead of syncing every 5 seconds, we will only sync every 5 seconds what has changed. If only the x position changed, we are not syncing the y position. If our position has not changed at all, we are not syncing. This is basicly a more traffic-friendly version of IMPORTANT.
We are using SMART here and relatively large intervals, because we only sync the position as a backup. As you will see later, we are going to sync the button inputs every single step. We only sync the position to make sure, the players don’t get desynchronized.
The** button input later will be synced FAST**. That means **some packets could be lost**, and after some time the player could be on two completly different parts of the level, depending on the complexity of your platformer. To make sure that doesn’t happen, **we reset the position every 5 seconds**.
Now, the thing is, if we reset it every 5 seconds, it might happen that the players are just some pixels off. In that case our players might slightly “flicker” whenever the position resets. That doesn’t look nice.
Let’s add a little tolerance range. If the position at is less than 20 pixels away from where it should be, the other players should not apply this change locally, so it doesn’t flicker that much:
mp_tolerance("Pos",20);
Great. Position is set up. Now, just to make sure, let’s also sync the basic Game Maker physics and drawing variables:
/**
* Tell the engine to add the basic drawing variables:
* image_alpha,image_angle,image_blend,image_index,image_speed,image_xscale
* image_yscale,visible
*/
mp_addBuiltinBasic("basicDrawing",15*room_speed);
mp_setType("basicDrawing",mp_type.SMART);
/**
* Tell the engine to add the builtin GameMaker variables:
* direction,gravity,gravity_direction,friction,hspeed,vspeed
*/
mp_addBuiltinPhysics("basicPhysics",15*room_speed);
mp_setType("basicPhysics",mp_type.SMART);
We don’t need to sync them that frequently. If we sync physics to
frequently that might look weird and we are only syncing the basic
Drawing stuff for the player color (image_blend
) which doesn’t
change anyway, and the image_xscale
and iamge_angle
which
controls how the player faces, which isn’t that critical.
Now we also want to sync the name:
mp_add("playerName","name",buffer_string,60*room_speed);
mp_setType("playerName",mp_type.SMART);
This is using mp_add
to sync our own variables. The syntax is
slightly more compelx and we need to do some things to make this work
later, but let’s just see what we got here: * The first argument is
again the name of the group * In the second argument you specify the
names of the variables you want to sync, seperated by commas. We
stored the player name in the “name” variable. * The third argument is
the buffer type. You can find information on which buffer type you
need to use on this manual page. All variables
need to have the same buffer type. buffer_string
simply means that
the variable “name” stores a string. * The last argument is the syncing
interval, just as before.
We decide for a 60 seconds interval, because the name will never change. We could have also used 20 years, that wouldn’t make a difference. We only need to make sure the engine syncs it on login and some other critical events, and it does that automatically, no matter what interval we choose. We still need to make it SMART because we need to make sure it REALLY arrived at those events.
Next up are the controls. Remember how we stored the button input in seperate variables? Well now you might know why:
mp_add("controls","pressed_jump,pressed_left,pressed_right",buffer_bool,1);
The “buffer type” is buffer_bool
, because our “pressed_” variables
are booleans. 1/0, true/false.
This will sync the button input to all players every single frame no matter what. We don’t want to have it SMART or IMPORTANT. FAST is the way to go, since there is no point in checking if the data arrives, because we are syncing the button input every step anyway.
9.2. Some things needed when using mp_add
¶
mp_add
to sync our own variables, we need to
make some code changes. We need to send the variables to the engine at
the beginning of the step, and retrieve the data at the end.mp_addPosition
,
mp_addBuiltinBasic
and mp_addBuiltinPhysics
, because we
hardcoded them.If you don’t know what any of that means what I just said, don’t worry. It’s complicated.
The only things you need to know, we will explain them now:
For every object where you use mp_add
add the following to the
begin step event:
mp_map_syncIn("name",self.name);
mp_map_syncIn("pressed_jump",self.pressed_jump);
mp_map_syncIn("pressed_left",self.pressed_left);
mp_map_syncIn("pressed_right",self.pressed_right);
Replace the names with the names of your synced variables. These are the variables that we created above for our tutorial player.
All changes to these variables need to be made BEFORE using these
functions. That means you either have to change them in begin step, or
call mp_map_syncIn
again after you changed variables. We recommend
the first. And that’s also what we are going to do in a minute.
In the end step event add the following code to retrieve the variables again:
self.name = mp_map_syncOut("name", self.name);
self.pressed_jump = mp_map_syncOut("pressed_jump", self.pressed_jump);
self.pressed_left = mp_map_syncOut("pressed_left", self.pressed_left);
self.pressed_right = mp_map_syncOut("pressed_right", self.pressed_right);
9.3. Setting up controls for synchonization¶
Last thing we need to do, is to move this code out of the step event we created earlier:
self.pressed_jump = keyboard_check(vk_space);
self.pressed_left = keyboard_check(vk_left);
self.pressed_right = keyboard_check(vk_right);
Simply remove it. In begin step, add the following code before the other code:
if (htme_isLocal()) {
self.pressed_jump = keyboard_check(vk_space);
self.pressed_left = keyboard_check(vk_left);
self.pressed_right = keyboard_check(vk_right);
}
It should now look like this:
if (htme_isLocal()) {
self.pressed_jump = keyboard_check(vk_space);
self.pressed_left = keyboard_check(vk_left);
self.pressed_right = keyboard_check(vk_right);
}
mp_map_syncIn("name",self.name);
mp_map_syncIn("pressed_jump",self.pressed_jump);
mp_map_syncIn("pressed_left",self.pressed_left);
mp_map_syncIn("pressed_right",self.pressed_right);
All the code we just pasted in begin step does, is check if this is the instance that was locally created and then writes the button input of the players into the variables.
This way when we have 4 players, we only move the instance we control,
the instance we locally created. The self.pressed_jump...
variables
will be changed by the other 3 players for the rest of the three
instances.
Look at the following table from the view of player 1:
Instance/Player | Controlled via buttons | Controlled via engine |
---|---|---|
Ours / Player 1 | Yes | No |
Player 2’ s | No | Yes, buttons from Player 2 |
Player 3’ s | No | Yes, buttons from Player 3 |
Player 4’ s | No | Yes, buttons from Player 4 |
9.4. Test¶
Fire up two games and create a server / connect to 127.0.0.1.
You should now see both players, and should see that we now have a multiplayer platformer.