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:
| Param | Type | Beschrijving |
|---|---|---|
| scene | String | Huidige scene naam |
| x | f32 | X coordinaat |
| y | f32 | Y coordinaat |
| direction | u8 | 0=down, 1=up, 2=left, 3=right |
| is_moving | bool | Beweegt 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:
| Param | Type | Beschrijving |
|---|---|---|
| message | String | Bericht tekst (1-500 chars) |
| channel | String | "global", "local", "whisper" |
| target | Option<String> | Username voor whispers |
Channels:
| Channel | Bereik | Gebruik |
|---|---|---|
| global | Alle online spelers | Algemene chat |
| local | Zelfde scene/locatie | Lokale communicatie |
| whisper | Specifieke speler | Privé 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