This tutorial assumes you already have a basic game that shows a login box and has a "player object" and an "other player object" set up. If you don't, the getting started tutorial covers the basics of creating a new game.
This post will consist of two parts. In the first part, I'll cover the ideas and theories behind instance synchronization. I'll explain what the different types of instance synchronization are, and when to use them. In the second part, I'll show how to get instance synchronization to work in your game.
In this tutorial, I'll use the word "client" a few times. A client is an instance of the game, connected to the server. For example, if there are three players playing your game, that means three clients are connected to the server. When a player moves, that moment is sent from one client to the other clients.
There are three basic types of instance synchronization: One-time, Extended and Full synchronization. Every type of synchronization can be local or not local.
One-time instance synchronization synchronizes the instance exactly once. It does not synchronize it's destroy or any variable that changes after it's synchronized.
Extended instance synchronization will synchronize the instance twice: Once when calling gms_instance_sync(...), and once when the instance is destroyed. This type of synchronization does not synchronize any variables that are changed after creating it.
Full instance synchronization will synchronize the instance when calling gms_instance_sync(...), when a variable changes, and when it's destroyed. This type of synchronization is very similar to how the "player object" is synchronized. Instances will be sent to other players when they join the room.
Local instance synchronization will only synchronize the instance if it's close to the player. It'll try to predict which instances will be seen by the player, and the server will only synchronize those instances. For full instance synchronization, the instance will still be synchronized to other players, but its variables and position are only synchronized if the player is close enough to the instance.
Whether the player can see the instance or not, is determined by the "Syncing-distance". This distance can be set on the site, under Developer -> Game-settings -> Syncing distance. A good estimate for how large the Syncing distance should be, is to take the room_width or room_height (whichever one is higher) and multiply it by 1.5.
Each type can be represented by a constant:
One-time synchronization: is_onetime
Extended synchronization: is_extended
Full synchronization: is_full
By default, these constants are all not local. To make one of the synchronisation types local, its constant must be bitwise or'ed ('|') with the constant isc_local. (bitwise operators are outside the scope of this tutorial, there are some great tutorials about bitwise operators out there if you're interested) For example:
When creating a multiplayer game, it's important to keep the data usage to an absolute minimum. Instance synchronization can use a lot of data. Therefore, it's important you use the right type of synchronization for the right type of instance.
To decide what type of synchronization you need, you should consider whether the moment an instance is destroyed and how and when the instance moves or changes variables is predictable.
For example: A bullet is destroyed when it collides with a wall. Because a collision with the wall will happen at the same time on all clients, it is not necessary to synchronize the destroy of the bullet.
On the other hand, assume an object is destroyed when a player clicks on it. Not all players will click on the same object at the same time, so for the other clients there is no way of knowing, without explicitly sending the destroy to the other clients, whether the instance has been destroyed.
The same goes for movement or variables. If the value of the variable or the position of the object is predictable, it does not need to be sent.
The points above can be formulated in 3 questions:
"Is it possible to know what the value of a variable of the object is and will be without sending it?"
"Is it possible to know what the position of the object is and will be without sending it?"
"Is it possible to know at what moment in time the object will be destroyed, without sending it?"
Each different type of instance synchronization will fulfil a different need:
One-time synchronization: position predictable, destroy predictable, variables predictable.
Extended synchronization: position predictable, destroy unpredictable, variables predictable.
Full synchronization: position unpredictable, destroy unpredictable, variables unpredictable.
Note how not all possible combinations are covered. The three types are the most common ones. There aren't many cases where the value of variables is unpredictable, but the position is.
That's all you need to know to get started. Keep in mind you'll most likely (and should) use One-time and Extended synchronization most of the time. Full synchronization uses a lot of CPU power and data compared to the other two options, so use it with care!
Now that I've covered the different types of synchronization and when to use them, I'll use this second part of the tutorial to show a few examples & describe how to add instance synchronization to your game. I'll be using the game created in the getting started tutorial as a start.
Make sure you've set the Syncing distance on the GameMaker Server website.
Add a call to gms_settings_declare_syncable_object right before the call to gms_settings:
I'll cover two of the three different types of instance synchronization here:
One-time instance synchronization
Full instance synchronization
Extended instance synchronization is very similar to One-time synchronization.
Create a new object and name it "obj_bullet" and give it a sprite.
Open obj_player, and add a Global Mouse Left Pressed event.
Add the following piece of code:
In GM:Studio, you'll need to do a little more work when synchronizing an instance variable. Note how in the GM8 code above, the custom variable is indicated to the extension by simply passing a string containing the variable name to gms_instance_sync(...). Internally the extension will use variable_local_get to obtain the variable value. In GM:Studio variable_local_get is removed.
GameMaker Server has a special function that is used to set both the variable name and value for instance synchronisation, gms_instance_sync_var_add(...). Add the following piece of code to the Global Mouse Left Pressed event:
Regardless of what type of synchronization you're using, these variables are only sent once to the other clients. The variables x, y, speed and direction are always sent.
Once the instance is created on the other clients, the User Defined 12 Event is called.
You'll also need to manually set the custom synchonized variables, because internally the extension uses variable_local_set, which is once again, removed in GM:Studio. Open obj_bullet and add this piece of code to the Event User 12 event:
variable_map is a variable that contains a ds_map with all the synchronized variables in it. The ds_map is destroyed after the User Defined 12 event has executed.
The speed and direction (and any custom variables / the variable_map variable) are not set in the create event of obj_bullet. You must use the User Defined 12 event if you want to use the synchronized variables.
The game is now in a state you can run it. If you want, you can test what you have so far.
If you want, you can add walls to your game. Try adding a piece of code that'll destroy the bullet once it hits a wall. (remember, when obj_bullet hits a wall is predictable).
Try giving the player health and make the bullets do damage to the other players. Keep in mind obj_other_player is only a representation of the obj_player object on another client. Handle the collision with obj_bullet in obj_player only. To check if the player fired the bullet, use special owner variable that's automatically set after calling gms_instance_sync(...).
Let get on to something more advanced. Create an object and name it obj_npc. For the purpose of this tutorial, give it a completely white sprite. We'll use that later on to assign a random image_blend color to every NPC.
In the create event:
Set alarm[0] to 1 second
Set the speed to 4
Add this code to the alarm[0] event:
Add a call to randomize() in the GMS object or another initialization object to make sure the NPC will walk randomly every time.
Destroy the instance when it hits a bullet.
Create an object named obj_npc_spawner. Make it spawn obj_npc every 3 seconds, until there are 5 obj_npcs walking around:
Create:
Alarm[0]:
When tasked with changing the code above to synchronize the instance every time it's created, your first reaction would probably be to just add a gms_instance_sync below the instance_create line. Though that would work, it also creates a problem. Consider this: If 3 players are online at the same time, every single client will have the obj_npc_spawner spawn an NPC every 3 seconds. That means 3 NPCs will spawn every 3 seconds. If 5 players are online, every 3 seconds up to 5 new NPCs will spawn.
To keep the spawn rate the same, only one client should create instances. To decide which client should spawn the instances, the master player can be used. The master player is assigned by the server to one client, and will change automatically when a player logs out or other players join. Change the code to this:
Because image_blend is a built-in variable, it can be obtained and set by the extension automatically. There's no need to add a call to gms_instance_sync_var_add(...) in this case.
Note the alarm will go off regardless of whether the player is the master player or not. This is done so that in case the master player disconnects, the alarm on the client that becomes the next master player is still running. The gms_self_ismaster() check is done every 3 seconds on every client to make sure another client will be able to pick up seamlessly when the master player disconnects.
The NPC object itself will also need to be changed a little. If every client tried to change the direction of the NPC every second, it would get a mess very quickly. The obj_npc object would move in multiple directions at the same time, because every client set it to a different direction. That's why an instance can only be controlled by one client at a time. The instance is "owned" by a particular instance. The functions gms_instance_is_owner(id) and gms_instance_get_owner(id) will return whether the client owns the instance and what player owns the instance respectively.
Unlike the master player, instances do not change owner automatically. Every instance will need to be handed over to another player by calling gms_instance_handover(...) or gms_instance_handover_all(). The server will decide what player the instance is handed over to. This means if you don't want instances to be destroyed when a player moves to another room or logs out, you need to hand over instances in these events:
Room end
Game end
Edit the alarm[0] event of obj_npc:
Add this code to the step event to update the image_blend variable if the instance is not owned by the client:
Lastly, make sure the instance is handed over to another player when the owner of the instance disconnects or changes room:
Game End & Room End event:
You may want to add an Outside Room event to obj_npc, and make it warp in both directions to keep the NPC's on the screen. Add an instance of obj_npc_spawner to rm_play.
That's it!Finally. This tutorial is much longer than I intended to make it, but I wasn't able to make it any shorter without removing parts of the tutorial. Let me know what you think! If something is unclear, please ask about it in the comment section below, or send me a PM on the GameMaker Forums. What subject should I do next?