Skip to content

Moving things

Compiles

What you’re moving decides how. An ordinary object moves when you change its transform. Players and balls have scripts that own their position, so work with those scripts.

Moving How Where
An ordinary object Write its transform Any event
A player Write their transform, then tell player_movement the new facing late_tick, on every machine
A loose ball Ask its ball_transform with an event Any event

position and rotation in a transform are relative to the entity’s parent:

let transform: Component = $support.entity_get_component($entity.get(), "transform.vscript")
transform.position = transform.position + vec3(0, 0, 50)

Work the position out from the clock instead of adding a little each frame, so it can’t drift:

let height: float = 60.0
let seconds_per_cycle: float = 3.0
let mut transform: Component = null
let mut start: vec3 = vec3(0, 0, 0)
event export fn created() {
transform = $support.entity_get_component($entity.get(), "transform.vscript")
start = transform.position
}
event export fn tick() {
let seconds: float = $time.ms_to_seconds($time.get_gameplay_time_ms())
let wave: float = $math.sin(seconds / seconds_per_cycle * 6.2831855)
transform.position = start + $math.vec3(0.0, 0.0, wave * height)
}

Motion computed from the clock looks the same on every machine without sending anything, so a script like this can switch replication off: $net.set_replicated(false, false) in type_created.

Rotations are quaternions. Build one from an axis and an angle. z is up, so turning around z is turning left and right:

fn face_yaw(transform: Component, degrees: float) {
transform.rotation = $math.rotation_from_axis_angle(vec3(0, 0, 1), $math.deg2rad(degrees))
}

Three things have to be right:

  1. Where: in late_tick. A player’s movement writes their position late in the frame; anything written earlier is overwritten.
  2. Who: every machine. Each player’s game simulates their movement too, so a move made only on the server is undone by the client.
  3. Facing: tell player_movement the new yaw, or the camera snaps back to the old direction.
let mut pending_player: Component = null
let mut pending_position: vec3 = vec3(0, 0, 0)
event export fn type_created() {
$net.set_replicated(true, false) // and no is_server() guard below
}
// Call this from wherever you decide to move them: a trigger, a timer.
fn request_move(player: Component, where: vec3) {
pending_player = player
pending_position = where
}
event export fn late_tick() {
if !pending_player {
return
}
let body: Entity = $component.get_entity(pending_player)
pending_player = null
let transform: Component = $support.entity_get_component(body, "transform.vscript")
if transform {
transform.position = pending_position
}
}

To also turn them, set transform.rotation and send the yaw on:

fn turn_player(body: Entity, rotation: vec4) {
let transform: Component = $support.entity_get_component(body, "transform.vscript")
transform.rotation = rotation
let movement: Component = $support.entity_get_component(body, "player_movement.vscript")
if movement {
$component.queue_event("force_set_player_yaw", movement, [$math.eulers_from_rotation(rotation).z], false)
}
}
Server
Each player's game
late_tick: write position
late_tick: write the same position
the player is here
the player is here, and stays
A player move runs everywhere. Skip the client and its own simulation puts the player back.

A ball’s ball_transform owns where it is. Ask it to move with the event it provides:

fn move_ball(ball: Component, where: vec3, rotation: vec4) {
$component.queue_event("set_position_and_rotation", ball, [where, rotation], true)
}

Leave balls someone is holding alone. See Triggers for how to tell.