Built-in API
Network synchronization
When building multiplayer games, it's crucial to keep all players in sync by efficiently transmitting game states and events over the network. YAHAHA's multiplayer networking package offers several tools to achieve this:
Remote Procedure Calls (RPCs)
RPCs are ideal for sending transient events that require minimal overhead. Use them when transmitting short-lived, one-time occurrences, such as player actions or environmental effects. RPCs are lightweight and straightforward, making them suitable for time-sensitive updates.
SyncVars
SyncVars are excellent for managing game state and ensuring that all players have access to the latest values. Use them when you want to automatically synchronize state changes, such as player positions, health, or scores. The variables handle the serialization and deserialization of data, simplifying the process of keeping the game world consistent across clients. The choice between RPCs and SyncVars depends on the specific requirements of your game and the nature of the data you need to transmit.
- RPCs are best suited for quick, one-time communications where low latency is crucial. They are straightforward to implement but can face performance issues under heavy load.
- SyncVars excel at managing and synchronizing game state across multiple clients, ensuring that all players have the latest information. They simplify state management but require careful design to avoid performance pitfalls when dealing with numerous variables.
RPC APIs
In YAHAHA, processes can seamlessly communicate with one another through the use of Remote Procedure Calls (RPCs). The Rpc attribute facilitates various communication patterns, including server-to-client, client-to-server, and client-to-client interactions.
When utilizing reliable RPCs, developers can ensure that messages are not only received but also executed on the remote side, providing a robust communication mechanism. However, there are scenarios where reliability may not be necessary—such as for non-essential events like particle effects or sound effects—allowing developers to choose unreliable RPCs for those cases.
Reliable RPCs guarantee that messages are received in the exact order they were sent, but this ordering is only maintained among RPCs associated with the same NetworkObject. Different NetworkObjects may have their reliable RPCs invoked, but the execution order can vary between them. In simpler terms, the in-order execution of reliable RPCs is guaranteed only within the context of a single NetworkRoot. If you find that an RPC is updated frequently—multiple times per second—it may be more efficient to implement it as an unreliable RPC to optimize performance.
Methods
Basic methods
-
IsClient()
Checks whether the local environment is running as a client within the network. Returns true if the local machine is running as the client; false otherwise.
-
IsServer()
Checks whether the local environment is running as a server within the network. Returns true if the local machine is running as the server; false otherwise.
-
IsHost()
Checks whether the local environment is acting as the host in a multiplayer network setup, such as a peer-to-peer or client-hosted configuration. Returns true if the local machine is running as the host; false otherwise.
-
IsOwner()
Checks whether the object is owned by the local player or if the object itself represents the local player object. Returns true if the object is owned by the local player or is the local player object; false otherwise.
-
IsOwnedByServer()
Checks whether the object is owned by any player. Returns true if the object is not owned by any player (either owned by the server or unowned); false otherwise.
Advanced methods
Parameters
- Topic:
String. The topic used to identify the message being sent. - func:
function<table>It yields until the subscription is properly registered and returns a connection object. - messageTable:
tableornil. The contents of the message to send to the actor, serving as a container for code that can be safely split into its own thread.
Methods
---This function registers a callback to begin listening to the given topic. The callback is invoked when a topic receives a message. It can be called multiple times for the same topic.
---@param topic string Determines where to listen for messages.
---@param func function<table> It yields until the subscription is properly registered and returns a connection object. callback field table: Developer supplied payload.
---@return com.yahaha.builtin.SubscribeConnection
function script.SubscribeAsync(topic, func) end
---This function sends the provided message to all subscribers to the topic, triggering their registered callbacks to be invoked.
---Owning client send to server reliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncClientToServer(topic, messageTable) end
---Owning client send to server and broadcast to all clients reliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncClientToClients(topic, messageTable) end
---Server send to all clients reliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncServerToClients(topic, messageTable) end
---Server send to owner reliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncServerToOwner(topic, messageTable) end
---Owning client send to server unreliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncClientToServerUnreliable(topic, messageTable) end
---Owning client send to server and broadcast to all clients unreliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncClientToClientsUnreliable(topic, messageTable) end
---Server send to all clients unreliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncServerToClientsUnreliable(topic, messageTable) end
---Server send to owner unreliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncServerToOwnerUnreliable(topic, messageTable) end
---Server send to owner unreliable RPC
---@param topic string
---@param messageTable table|nil
function script.PublishAsyncCustom(topic, messageTable) end
Code samples
The following example sets up a subscription to the "PlayerBasic" topic, and when a message is received on that topic, it prints some information about the received message. Additionally, when the script starts, it publishes a message to all clients subscribed to the "PlayerBasic" topic, containing a playerid value of 1.
-- Subscribe to the "PlayerBasic" topic with a callback function
script.SubscribeAsync("PlayerBasic", function (t, params)
-- Print a message indicating that a message has been received
print("RECEIVE !!!! " .. t.playerid)
-- Print details about the parameters received
print("params sentTime: " .. params.SentTime ..
", receiveTime: " .. params.ReceiveTime ..
", SenderClientId: " .. params.SenderClientId)
end)
-- Define what happens when the script starts
script.OnStart(function()
-- Publish a message to all clients subscribed to the "PlayerBasic" topic
-- The message contains a table with player ID information; in this case, the ID is set to 1,
-- which could represent the player who triggered this event.
script.PublishAsyncServerToClients("PlayerBasic", {playerid = 1})
end)
SyncVars
SyncVars provide a persistent way to synchronize properties between servers and clients. Unlike RPCs, which function as one-time messages that do not reach clients who are not connected at the moment of sending. SyncVars ensure that vital data remains consistently updated across all connected players. These variables are versatile and can be employed in various network architectures, whether you are using a traditional client-server model or a decentralized authority setup.
To declare a SyncVar, you can use the following functions: local number = script.DeclareSyncVarNumber(key, permissionTypeInt) local luatablescript.DeclareSyncVarLuaTable(key, permissionTypeInt)
Both SyncVarNumber and SyncVarTable act as wrappers around the stored value, meaning you'll access the actual synchronized value through the GetValue() method.
SyncVars automatically synchronize in two key scenarios:
- New Players Joining: When a new client enters the game, the current state of any relevant SyncVars is instantly shared with them, ensuring they are up to date.
- Value Changes: If a SyncVar's value is modified, all connected clients that have previously subscribed to the
AddChangedEventwill be notified of this change. To manage access to SyncVars, you can utilizeNetworkVariablePermission, allowing you to set rules for reading and writing these variables.
Access permission
Read permissions
There are two options for reading SyncVar:GetValue():
-
Public: Both the owner and other clients can access the value. This is ideal for global information, such as player scores or health, that all players should be aware of. For example, you might use public permissions to track the open or closed state of a door.
-
Private: Only the owner and the server can read the value. This is suitable for player-specific data, like inventory or ammo count, that should remain confidential between the server and the respective player.
Write permissions
There are two options for writing SyncVar:GetValue():
-
Server: Only the server can modify the value. This is the default setting for client-server environments and is useful for maintaining server-controlled states, such as the status of NPCs or global environmental conditions (e.g., day or night).
-
Client: Only the owner of the SyncVar can change its value. This is the default for decentralized authority setups and is beneficial for player-specific attributes, like character skins or customization options.
Methods
-
NetworkVariablePermission.PublicClientVariables Only the owner of the object can modify the variable, but everyone can read its value.
-
NetworkVariablePermission.PublicServerVariables Only the server can modify the variable, but all players can read its value.
-
NetworkVariablePermission.PrivateClientVariables Only the owner of the object can modify the variable, and only the owner can read its value.
-
NetworkVariablePermission.PrivateServerVariables Only the server can modify the variable, and only the server can read its value.
-
script.DeclareSyncVarNumber(key, permissionTypeInt)
Declares a synchronized variable of type number. Parameters:
key: A string that represents the name of the variable.permissionTypeInt: An integer that specifies the permission type for the variable (e.g., who can read or write to it). -
script.DeclareSyncVarLuaTable(key, permissionTypeInt)
Declares a synchronized variable of type Lua table. Parameters:
key: A string that represents the name of the variable.permissionTypeInt: An integer that specifies the permission type for the variable.|
Code Samples
The example effectively tracks and updates the speed of an object in a networked environment, allowing the owner to modify it while ensuring that other clients can read its value.
- Declare a synchronized numeric variable named "Speed" with PublicClientVariables permission
local speed = script.DeclareSyncVarNumber("Speed", NetworkVariablePermission.PublicClientVariables)
-- Update the game loop
script.OnUpdate(function()
-- Check if the current object is the owner
if IsOwner() then
-- If the object is the owner, increment the speed value by 1 every frame
speed:SetValue( speed:GetValue() + 1)
end
end)
The following example manages a player's health in a networked environment, allowing for real-time updates and synchronization across clients. The health value increases by 1 every second for the owner of the script, and any changes to the health value are logged to the console.
---@type com.yahaha.builtin.NetworkVariableLuaTable
-- Declare a variable to hold the NetworkVariableLuaTable type
local property = script.DeclareSyncVarLuaTable("Property", NetworkVariablePermission.PublicClientVariables)
-- Create a synchronized variable table named "Property" with public client access
property:SetValue({ Health = 1, Name = "Player" })
-- Initialize the table with default values for Health and Name
property:AddChangedEvent(function()
-- Add an event listener that triggers when the property changes
print("Property changed to " .. property:GetValue().Health)
-- Print the new Health value whenever the property is updated
end)
local lastTickTime = UnityEngine.Time.time
-- Store the time of the last update
local function UpdateHealth()
-- Define a function to update the Health value
if not script.IsOwner() then
-- Check if the current script instance is the owner
return
end
if UnityEngine.Time.time - lastTickTime > 1 then
-- Check if more than 1 second has passed since the last update
property.Value.Health = property.Value.Health + 1
-- Increment the Health value in the table
property:SetDirty()
-- Mark the property as dirty to trigger synchronization
lastTickTime = UnityEngine.Time.time
-- Update the lastTickTime to the current time
print(property.Value.Name .. " Health: " .. property.Value.Health)
-- Print the player's name and the updated Health value
end
end
script.OnUpdate(function()
-- Register the UpdateHealth function to be called on every frame update
UpdateHealth()
end)