Ga naar hoofdinhoud

Speler Acties Reducers

Reducers voor algemene speler acties: movement, chat, connectie.

Connection Lifecycle

on_connect

Automatisch aangeroepen wanneer een client verbindt.

#[reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) {
let identity = ctx.sender;
let now = ctx.timestamp;

// Mark als online
ctx.db.player_online().insert(PlayerOnline {
identity,
connected_at: now,
});

// Update last_online
if let Some(mut player) = ctx.db.player().identity().find(identity) {
player.last_online = now;
ctx.db.player().identity().update(player);
}

log::info!("Player connected: {}", identity);
}

Trigger: Automatisch bij WebSocket connect.

Effecten:

  • PlayerOnline record toegevoegd
  • Player.last_online bijgewerkt

on_disconnect

Automatisch aangeroepen wanneer een client disconnect.

#[reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) {
let identity = ctx.sender;

// Remove from online list
if let Some(online) = ctx.db.player_online().identity().find(identity) {
ctx.db.player_online().identity().delete(online.identity);
}

// Update last_online
if let Some(mut player) = ctx.db.player().identity().find(identity) {
player.last_online = ctx.timestamp;
ctx.db.player().identity().update(player);
}

log::info!("Player disconnected: {}", identity);
}

Trigger: Automatisch bij WebSocket disconnect.

Effecten:

  • PlayerOnline record verwijderd
  • Player.last_online bijgewerkt

Position & Movement

update_position

Update speler positie (frequent aangeroepen tijdens movement).

#[reducer]
pub fn update_position(
ctx: &ReducerContext,
scene: String,
x: f32,
y: f32,
direction: u8,
is_moving: bool,
) -> Result<(), String>

Parameters:

ParamTypeBeschrijving
sceneStringHuidige scene naam
xf32X coordinaat
yf32Y coordinaat
directionu80=down, 1=up, 2=left, 3=right
is_movingboolBeweegt de speler?

Validatie:

  • Speler moet geregistreerd zijn

Client Code:

# In _process of bij movement
func _sync_position() -> void:
if not client or not client.is_connected():
return

client.reducers.update_position(
current_scene,
character.position.x,
character.position.y,
_get_direction(),
is_moving
)

func _get_direction() -> int:
if velocity.y > 0: return 0 # down
if velocity.y < 0: return 1 # up
if velocity.x < 0: return 2 # left
if velocity.x > 0: return 3 # right
return last_direction

Optimalisatie:

# Niet elk frame, maar op interval
var sync_timer: float = 0.0
const SYNC_INTERVAL: float = 0.1 # 10x per seconde

func _process(delta: float) -> void:
sync_timer += delta
if sync_timer >= SYNC_INTERVAL:
sync_timer = 0.0
_sync_position()

Chat

send_chat

Stuur een chat bericht.

#[reducer]
pub fn send_chat(
ctx: &ReducerContext,
message: String,
channel: String,
target: Option<String>,
) -> Result<(), String>

Parameters:

ParamTypeBeschrijving
messageStringBericht tekst (1-500 chars)
channelString"global", "local", "whisper"
targetOption<String>Username voor whispers

Channels:

ChannelBereikGebruik
globalAlle online spelersAlgemene chat
localZelfde scene/locatieLokale communicatie
whisperSpecifieke spelerPrivé berichten

Validatie:

  • Bericht 1-500 karakters
  • Geldige channel naam
  • Voor whisper: target moet bestaan

Client Code:

# Global chat
func send_global(text: String) -> void:
client.reducers.send_chat(text, "global", null)

# Whisper
func send_whisper(to_username: String, text: String) -> void:
client.reducers.send_chat(text, "whisper", to_username)

# Local chat
func send_local(text: String) -> void:
client.reducers.send_chat(text, "local", null)

Chat Ontvangen:

func _setup_chat() -> void:
client.db.chat_message.subscribe_to_inserts(&"ChatMessage", _on_chat_received)

func _on_chat_received(msg) -> void:
match msg.channel:
"global":
_add_to_chat("[Global] %s: %s" % [msg.sender_name, msg.message])
"local":
if _is_in_same_scene(msg.sender):
_add_to_chat("[Local] %s: %s" % [msg.sender_name, msg.message])
"whisper":
_add_to_chat("[Whisper from %s] %s" % [msg.sender_name, msg.message])

Voorbeeld: Complete Chat UI

extends Control

@onready var chat_input: LineEdit = $ChatInput
@onready var chat_log: RichTextLabel = $ChatLog
@onready var channel_selector: OptionButton = $ChannelSelector

var client = null
var current_channel: String = "global"

func setup(spacetime_client) -> void:
client = spacetime_client
client.db.chat_message.subscribe_to_inserts(&"Chat", _on_message)

func _on_message(msg) -> void:
var color = _get_channel_color(msg.channel)
chat_log.append_text("[color=%s][%s] %s: %s[/color]\n" % [
color, msg.channel, msg.sender_name, msg.message
])

func _get_channel_color(channel: String) -> String:
match channel:
"global": return "#ffffff"
"local": return "#aaffaa"
"whisper": return "#ffaaff"
return "#ffffff"

func _on_send_pressed() -> void:
var text = chat_input.text.strip_edges()
if text.is_empty():
return

var target = null
if current_channel == "whisper":
target = whisper_target_input.text

client.reducers.send_chat(text, current_channel, target)
chat_input.text = ""

Multiplayer Synchronisatie

Andere Spelers Zien

# Subscribe op posities
func _setup_multiplayer() -> void:
client.db.player_position.subscribe_to_inserts(&"Pos", _on_player_enter)
client.db.player_position.subscribe_to_updates(&"Pos", _on_player_move)
client.db.player_position.subscribe_to_deletes(&"Pos", _on_player_leave)

func _on_player_enter(pos) -> void:
if pos.identity == my_identity:
return # Skip self

if pos.scene != current_scene:
return # Niet in zelfde scene

_spawn_remote_player(pos)

func _on_player_move(old_pos, new_pos) -> void:
if new_pos.identity == my_identity:
return

var remote = remote_players.get(new_pos.identity)
if remote:
remote.target_position = Vector2(new_pos.x, new_pos.y)
remote.is_moving = new_pos.is_moving

Volgende