Replicating properties using Celte
Game objects properties can be replicated over the network, but doing so is a complex task as each property can have a different logic for its replication. As Celte is not yet tightly integrated in a game engine, changes in properties are currently broadcasted to the topic of the entity container owning the entity only if the property has been registered manually as being replicated. The user can define callbacks to update the value of properties directly into the engine.
Clients send their inputs to the input channel of the entity container that contains their avatar. All clients can use that input to simulate the client entity. Other entities have to be correctly synchronized for simulation, keeping random values only to the server. Any discrepency between the simulated values and the real one on the server node can be corrected by doing a rollback to the replicated property when an update is sent to the replication topic of the entity container owning the entity.
GODOT API (code bindings)
The Godot binding and editor addon expose a scene-driven replication configuration rather than a raw per-property getter/setter registration in editor scripts.
Primary API you'll use from GDScript / the editor plugin:
RegisterReplicatedPropertyToConfig(path, property, is_angle, interpolate, interpolationIntervalMs, property_type)
— add a property to the entity's replication config. Thepath
is relative to theCEntity
parent node (empty string for the parent itself).property_type
should be a Godot variant type (e.g.TYPE_VECTOR3
).GetReplicationConfig()
/SetReplicationConfig(config)
— read/write the replication config programmatically.- The editor addon (Replication Editor) calls
RegisterReplicatedPropertyToConfig
when you add a property in the UI; the configuration is stored on the node as thereplication_config
exported property (so it is saved with the scene).
Example: register a Vector3 property from script using the same binding the addon uses
# path: path to the child node relative to the entity parent (empty string means the parent node itself)
var path = ""
var property = "position"
var is_angle = false
var interpolate = true
var interpolationIntervalMs = 200
var property_type = TYPE_VECTOR3
current.RegisterReplicatedPropertyToConfig(path, property, is_angle, interpolate, interpolationIntervalMs, property_type)
Notes about serialization and supported types:
- The runtime serializes a few types with specialized serializers:
VECTOR3
,VECTOR2
, andCOLOR
are serialized to compact JSON objects. Other types fall back toJSON::stringify
. - The editor limits selectable types to common ones: BOOL, INT, STRING, VECTOR2, VECTOR3, COLOR (see the replication editor UI).
Thread-safety reminder:
- The setter/handler that applies network updates runs on the engine main loop (the binding polls updates in
_process
). Even so, the setter is effectively a network-driven callback and may change game state; keep setters lightweight and usecall_deferred
or engine queues when updating scene state.
Interpolation support (rollback smoothing):
- The runtime supports interpolation for properties when
interpolate
is enabled for a replicated property. When enabled the engine will create interpolation data and smoothly lerp from the current value to the rollback target overinterpolationIntervalMs
milliseconds. This is implemented in the nativeInterpolate
helpers (seeInterpolate.cpp
).
Godot addon UI
The Celte Godot addon now provides a small UI to configure replicated properties from the editor, so you don't need to call RegisterReplicated
manually in code for simple cases.
What the addon does (assumptions):
- It exposes a
Replicated Properties
panel in the Celte plugin or in theCEntity
inspector. - Each entry lets you enter the property name and optionally the names of the getter and setter methods (or select them from the scene). When you save the scene the addon persists this configuration in the scene metadata and wires it at runtime by calling
CEntity.RegisterReplicated(...)
for the corresponding entity instance.
How to use the addon (quick steps):
- Open the scene that contains your
CEntity
node. - Select the
CEntity
node and open theReplicated Properties
panel provided by the Celte addon (look in the inspector or the Celte dock). - Click
Add Property
and enter:- Name: the replication key (e.g.
pos
). - Getter: (optional) the method name that returns a string representing the value (if empty the addon will use the property value as a string).
- Setter: the method name to call when a network update arrives (the setter will be called with a single String argument).
- Name: the replication key (e.g.
- Save the scene. At runtime the plugin will call
RegisterReplicated
for that entity and the configured getter/setter will be used.
Notes and caveats:
- The setter is invoked when an update arrives from the network; it is still not thread-safe. Use
call_deferred
, engine queues, or otherwise forward the update to the main thread in your setter implementation. - The addon stores the replication configuration in the scene (metadata). If you change the property name or method names, update the scene's config accordingly.
- The addon aims to cover common use-cases. For advanced or dynamic replication rules (computed getters, conditional replication, custom serialization) continue to call
RegisterReplicated
from code.
Complete example for position replication, very simple interpolation
extends CharacterBody3D
var _rollback_interp_modifier = Vector3(0, 0, 0)
var _rollback_received_timestamp = Time.get_ticks_msec()
var rollabackDt = 200 # ms, time it takes to correct errors from rollback
var _rollback_target_pos = position
# call this in celte initialization of the entity, after OnSpawn has been called
func initReplicatedProps(entity):
entity.RegisterReplicated("pos",
func(): return JSON.stringify([position.x, position.y, position.z]), # get
func(p): call_deferred("update_position", p)) # set
func update_position(p: String):
var arr = JSON.parse_string(p)
var pos: Vector3 = Vector3(arr[0], arr[1], arr[2])
_rollback_interp_modifier = (pos - position) / rollabackDt
_rollback_received_timestamp = Time.get_ticks_msec()
_rollback_target_pos = pos
func correct_position_from_rollback_data(delta):
# if we are too far away we snap directly to the rollback position
if position.distance_to(_rollback_target_pos) > 5.0:
position = _rollback_target_pos
print("SNAPPING position to ", _rollback_target_pos)
return
var curr_time = Time.get_ticks_msec()
if (curr_time - _rollback_received_timestamp) > rollabackDt:
return # we are done with the rollback
position += _rollback_interp_modifier * delta * 1000 # delta is in seconds
func _physics_process(delta):
var entity = get_node("CEntity")
if (entity == null):
push_error("CEntity is null in physics process")
if not entity.IsOwnedByCurrentPeer():
correct_position_from_rollback_data(delta) # correct the position from the last rollback received
# process input data
move_and_slide()
Quick recipe: using addon + code together
You can combine the addon configuration with small glue code in the scene when you need an engine-side setter that does extra work. Example:
- Use the addon to register the replicated key
pos
and set the setter method name toon_network_pos
. - Implement the setter in the same script and forward the actual update to a deferred method that updates engine state:
func on_network_pos(p: String):
# addon called this method when a network update arrived
call_deferred("update_position", p)