Skip to content

Instantly share code, notes, and snippets.

@zeplar-exe
Last active May 21, 2023 23:33
Show Gist options
  • Save zeplar-exe/5eb164530f2cde095a0fe4b12a863155 to your computer and use it in GitHub Desktop.
Save zeplar-exe/5eb164530f2cde095a0fe4b12a863155 to your computer and use it in GitHub Desktop.
A limited but working implementation of a node property synchronizer for Godot multiplayer (supports client->server and server->client replication)
using System.Collections.Generic;
using Godot;
using Godot.Collections;
public partial class WorkingMultiplayerSynchronizer : Node
{
private Array<NodePath> ServerToClientProperties { get; }
private Array<NodePath> ClientToServerProperties { get; set; }
private double TimeElapsed { get; set; }
[Export] public Node? ReplicationRelativeNode { get; set; }
[Export] public double ReplicationInterval { get; set; }
public WorkingMultiplayerSynchronizer()
{
ServerToClientProperties = new Array<NodePath>();
ClientToServerProperties = new Array<NodePath>();
}
// RpcContext.Server
public void AddServerToClientReplicate(NodePath propertyPath)
{
ServerToClientProperties.Add(propertyPath);
}
// RpcContext.Server
public void RemoveServerToClientReplicate(NodePath propertyPath)
{
ServerToClientProperties.Remove(propertyPath);
}
// RpcContext.Server
public void AddClientToServerReplicate(NodePath propertyPath)
{
ClientToServerProperties.Add(propertyPath);
Rpc(nameof(UpdateClientToServerProperties), ClientToServerProperties);
}
// RpcContext.Server
public void RemoveClientToServerReplicate(NodePath propertyPath)
{
if (!ClientToServerProperties.Remove(propertyPath))
return;
Rpc(nameof(UpdateClientToServerProperties), ClientToServerProperties);
}
[Rpc]
// RpcContext.Client
private void UpdateClientToServerProperties(Array<NodePath> array)
{
ClientToServerProperties = array;
}
public override void _Process(double delta)
{
if (ReplicationInterval != 0) // 0 = Every _process call
{
TimeElapsed += delta;
if (TimeElapsed < ReplicationInterval)
return;
}
var syncDictionary = new Godot.Collections.Dictionary<NodePath, Variant>();
var properties = Multiplayer.IsServer() ? ServerToClientProperties : ClientToServerProperties;
foreach (var propertyPath in properties)
{
var value = GetValueOfPropertyPath(propertyPath);
if (value == null)
continue;
syncDictionary[propertyPath] = value.Value;
}
if (Multiplayer.IsServer())
{
Rpc(nameof(ReplicateClient), syncDictionary);
}
else
{
RpcId(1, nameof(ReplicateServer), syncDictionary);
}
}
[Rpc]
// RpcContext.Client
private void ReplicateClient(Dictionary properties)
{
foreach (var pair in properties)
{
var propertyPath = pair.Key.AsNodePath();
var value = pair.Value;
SetValueOfPropertyPath(propertyPath, value);
}
}
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
// RpcContext.Server
private void ReplicateServer(Dictionary properties)
{
foreach (var pair in properties)
{
var propertyPath = pair.Key.AsNodePath();
if (!ClientToServerProperties.Contains(propertyPath)) // Disallow client->server replication not enabled by the server
continue;
var value = pair.Value;
SetValueOfPropertyPath(propertyPath, value);
}
}
private Variant? GetValueOfPropertyPath(NodePath path)
{
var parts = path.ToString().Split(':');
return (ReplicationRelativeNode ?? this).GetNode(parts[0])?.Get(parts[1]);
}
private void SetValueOfPropertyPath(NodePath path, Variant value)
{
var parts = path.ToString().Split(':');
(ReplicationRelativeNode ?? this).GetNode(parts[0])?.Set(parts[1], value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment