Crayta takes a unique approach to how you develop multiplayer games, something that usually requires solving difficult programming problems and acquiring special infrastructure for your server. Instead of having the server and the client running separately, trying to synchronize the game instances, in Crayta everything runs in the cloud, both the server and the client, and even the development environment itself.
By default all game logic that you write in Crayta runs in the server and the engine takes care of replicating all movement, entity updates, collisions and physics etc. to the connected clients. When explicit client logic is required, for example to update something on the HUD like a health bar, any script can easily include that and communicate updates to the code running on the server.
In this tutorial we will explore how to handle the Server/Client relationship in your code and how to communicate between the two.
You will not easily find something similar to Crayta in other game engines, where everything requires a rigorous architecture to handle the synchronization between the server and the client.
Crayta will take care of most of the hardest parts of making a multiplayer game, leaving you to concentrate on creating the gaming experience.
Where is my code executing?
Crayta makes it very easy to start building a world, adding objects and effects and from the very first moment have your friends connect and play together. When adding code to your game, to enable custom behavior and logic, at first it might be confusing where the code is executing and in what order.
Crayta doesn’t require you to write your server and client logic separately. In the same script you can have server code and client code, that can share and reuse methods.
So how do we decide where our code should execute? In Crayta there are a number of functions that are called entry points and can execute code in one of the following contexts:
- All clients
- Local client only
Let’s take the Init method and its variations to see how it is used there to get a better understanding of how networking works in Crayta.
After the game world and all of its entities have been created on the server the Init method will be called in any script using it. It will execute the code in the server and all state updates happening to entities, as a result of the code running, will be replicated to all connected clients. This method is useful when executing gameplay sensitive code that aims to have the same result executed to all connected clients.
After all entities, that have been created, arrived to each connected client, separately, the ClientInit method will be called. This will execute the code to each connected client but not in a synchronous manner since the entities will arrive to each client at a different time. This is useful when code has to run to all connected clients, but it doesn’t have a crucial effect on the gameplay when not in sync. For example powerups spinning or particles effects animating. This method is valid and can be used in any entity, except the User and Player entities and their children.
On the Player or User, the LocalInit method can be used. It is called immediately after the ClientInit and it executes only to the local client. It can be used to update things like the HUD that are visible only to the local client.
How to handle communication?
Code executing to the server or the client only isn’t always useful, quite often you will have to send a message from the server to the clients or the other way around. Crayta provides an easy to use API to help you with that.
This method can be used in the server, in any script, to communicate with all connected clients:
entity:SendToAllClients(string eventName, ... args)
When called you have to pass the eventName, that is a string of the name of the function that will be executed. It will find and call this function on all scripts of this Entity on all connected clients.
Optionally you can pass any number of arguments that will be made available to the function when executed.
This method can be used in the server to communicate with the local client only:
entity:SendToLocal(string eventName, ... args)
As with the previous method, the name of the function to be executed is passed as the first argument. This function will be called on all scripts of this Entity on the local client that owns the Player or User this script is attached to.
A number of arguments can be passed to be made available to the function when executed.
This method is useful to communicate changes required only by the local client, like HUD updates.
This method can be used in the client to communicate with the server:
entity:SendToServer(string eventName, ... args)
It will call eventName on all scripts attached to this Entity on the server, together with any arguments given.
IsLocal / IsClient
There are two helper methods that can be used in any script to check if the code executing is running on the server or in the client.
This method will check if this Entity is on the client.
This method will check if this Entity is owned by the local client, that means it is the User or Player entity, or it is child to these entities.
Another way to easily communicate changes between the server and client is the property bag. All properties defined in a script automatically replicate to the clients and can therefore be used to transmit state and data without the need to use the Send methods.
The advantage of doing that is that new players will automatically get the latest state of a script, rather than having to catch up on their OnUserLogin and send it manually.
In the below example you can see how we can see the value on both the server and the client, we can modify it on the server and see the change reflected on the client.