Skip to content

Replication

Compiles

Replicationreplication: The server sending a component's changed fields to every client, so they all agree. is the server sending a component’s changed mutablemutable: A field declared with let mut. The script's running state: it changes while the game runs. fields to every client, so every copy of the component agrees. You set it up once, in type_created.

event export fn type_created() {
$net.set_replicated(true, false)
}

The first argument switches replication on for components of this script. The second decides the default for fields:

  • false: every mutable replicates unless you say otherwise. The usual choice.
  • true: no mutable replicates unless you opt it in.

A script that never needs the network says $net.set_replicated(false, false): every machine runs its own copy independently. Most shipped scripts that only drive visuals do this.

$net.set_field_replicated configures one field by name. It takes twelve values; most are false:

event export fn type_created() {
$net.set_replicated(true, false)
$net.set_field_replicated("presses", true, false, false, false, false, "", false, 0, 9999, 1, true)
}
# Parameter For presses Meaning
1 field "presses" The mutable’s name.
2 is_replicated true Send it at all. false keeps a field server-only.
3 only_to_owner false Send only to the user who owns the component.
4 is_quaternion false A vec4 that is a rotation.
5 is_normal false A vec2/vec3 that is a direction of length 1.
6 is_time_ms false An int that is a time in milliseconds.
7 is_enum "" For an int holding an enum: the enum’s name.
8 is_dependency false For a handle field: the other entity must arrive first.
9–11 quantize_min, quantize_max, quantize_step 0, 9999, 1 The range a number can take and its smallest step.
12 fire_event true Send field_replicated when a new value arrives.

The engine sends a number using only as much space as its range and step need. A field that counts to 9999 in steps of 1 is cheap; a float from 0 to 1 in steps of 0.01 is cheap. A value outside its range can’t be sent faithfully, so pick a range that covers every value the field can take.

Keep a field off the wire with is_replicated false. Use it for bookkeeping only the server needs, like when it last counted a press:

event export fn type_created() {
$net.set_replicated(true, false)
$net.set_field_replicated("last_press_ms", false, false, false, false, false, "", false, 0, 0, 0, false)
}

With fire_event on, each client gets field_replicated when a new value lands. The first argument is the field’s name, the second its previous value; the field itself already holds the new one:

let mut presses: int = 0
event export fn field_replicated(field, previous) {
if field == "presses" {
$debug.print_line("presses went from {} to {}", [previous, presses], false)
}
}
Server
Each player's game
presses = presses + 1
presses changed
0 to 9999, step 1
presses now holds the new value
field_replicated("presses", previous)
press_counter.vscript
// press_counter.vscript: counts how many times players have stepped on a
// pad, and tells every player's game when the count changes.
//
// The server owns the count. Players' games never change it; they are sent
// each new value and react to it.
//
// Sits on the same entity as a box_trigger.
// Seconds between counted presses, so standing on the pad counts once.
let cooldown_seconds: float = 1.0;
// The count. Replicated: the server's value is sent to every player.
let mut presses: int = 0;
// Server only: when the last press was counted.
let mut last_press_ms: int = -100000;
event export fn type_created() {
// Components of this script replicate.
$net.set_replicated(true, false);
// `presses` replicates as a whole number from 0 to 9999, and players'
// games get a field_replicated event each time a new value arrives.
$net.set_field_replicated("presses", true, false, false, false, false, "", false, 0, 9999, 1, true);
// `last_press_ms` is the server's own bookkeeping; keep it off the wire.
$net.set_field_replicated("last_press_ms", false, false, false, false, false, "", false, 0, 0, 0, false);
}
event export fn trigger_overlapped(other, unused) {
// Every machine sees the overlap; only the server counts it.
if !$net.is_server() || !other {
return;
}
let player: Component = $entity.get_hierarchy_singleton("catch_player.vscript", $component.get_entity(other));
if !player {
return;
}
let now: int = $time.get_gameplay_time_ms();
if now - last_press_ms < $time.seconds_to_ms(cooldown_seconds) {
return;
}
last_press_ms = now;
presses = presses + 1;
$debug.print_line("[press_counter] server counted press {}", [presses], false);
}
// On each player's game: a new count arrived from the server.
event export fn field_replicated(field, previous) {
if field == "presses" {
$debug.print_line("[press_counter] count went from {} to {}", [previous, presses], false);
}
}
For programmers

Replication is per-field delta with quantisation driven by quantize_min/max/step; bit width follows from the range. set_replicated’s second argument flips the default from opt-out to opt-in. Handle fields with is_dependency order spawn dependencies. field_replicated fires only when fire_event is set, on receivers.