SpacetimeDB SDK
Integratie met SpacetimeDB voor real-time multiplayer.
Overzicht
De game gebruikt de SpacetimeDB Godot SDK (addon) voor:
- Real-time synchronisatie tussen spelers
- Persistente game state
- Server-side game logica (reducers)
- Automatisch gegenereerde type bindings
Addon Structuur
addons/SpacetimeDB/
├── core/
│ ├── spacetimedb_client.gd # Hoofd client class
│ ├── spacetimedb_connection.gd # WebSocket verbinding
│ ├── bsatn_serializer.gd # Data serialisatie
│ ├── bsatn_deserializer.gd # Data deserialisatie
│ ├── module_table.gd # Tabel interface
│ └── local_database.gd # Lokale cache
├── codegen/
│ ├── codegen.gd # Binding generator
│ └── schema_parser.gd # Schema parsing
└── nodes/
└── row_receiver/ # Data receiver nodes
Verbinding Maken
Basis Setup
extends Node
var client: SpacetimeDBClient
func _ready() -> void:
client = SpacetimeDBClient.new()
add_child(client)
# Event handlers
client.connected.connect(_on_connected)
client.disconnected.connect(_on_disconnected)
client.identity_received.connect(_on_identity)
# Verbinden
client.connect_to_server(
"ws://localhost:3000", # Server URL
"milenas-treehouse" # Module naam
)
func _on_connected() -> void:
print("Verbonden met SpacetimeDB!")
func _on_identity(identity: String) -> void:
print("Mijn identity: ", identity)
Database Tabellen
Subscriben op Data
func _on_connected() -> void:
# Subscribe op tabellen
client.subscribe([
"SELECT * FROM player",
"SELECT * FROM player_online",
"SELECT * FROM player_position",
"SELECT * FROM chat_message"
])
# Callback voor initiële data
client.initial_subscription_applied.connect(_on_data_loaded)
Lezen van Tabellen
# Alle rijen itereren
for player in client.db.player.iter():
print(player.username)
# Zoeken op primary key
var player = client.db.player.identity().find(my_identity)
# Zoeken op unique column
var player = client.db.player.username().find("milena123")
Updates Ontvangen
func _setup_listeners() -> void:
var db = client.db
# Nieuwe rij toegevoegd
db.player_online.subscribe_to_inserts(&"PlayerOnline", _on_player_joined)
# Rij gewijzigd
db.player_position.subscribe_to_updates(&"PlayerPosition", _on_player_moved)
# Rij verwijderd
db.player_online.subscribe_to_deletes(&"PlayerOnline", _on_player_left)
func _on_player_joined(player_online) -> void:
print("Speler online: ", player_online.identity)
func _on_player_moved(old_pos, new_pos) -> void:
print("Speler bewoog naar: ", new_pos.x, ", ", new_pos.y)
func _on_player_left(player_online) -> void:
print("Speler offline: ", player_online.identity)
Reducers Aanroepen
Reducers zijn server-side functies:
# Registreren
client.reducers.register_player_with_pin("milena", "Milena", "123456")
# Positie updaten
client.reducers.update_position("game", x, y, direction, is_moving)
# Chat bericht sturen
client.reducers.send_chat("Hallo allemaal!", "global", null)
# Item verplaatsen
client.reducers.move_item(from_container, from_slot, to_container, to_slot, 1)
Reducer Resultaten
# Luisteren naar reducer resultaten
client.reducer_callback.connect(_on_reducer_result)
func _on_reducer_result(reducer_name: String, status: String, message: String) -> void:
if status == "failed":
print("Reducer ", reducer_name, " mislukt: ", message)
else:
print("Reducer ", reducer_name, " succesvol")
Inventory Manager Voorbeeld
Zie scripts/inventory/inventory_manager.gd:
class_name InventoryManager extends Node
signal containers_changed
signal resources_changed
var _client = null
var _player_identity: String = ""
var _containers: Array = []
var _resources: Dictionary = {}
func setup(client, player_identity: String) -> void:
_client = client
_player_identity = player_identity
_connect_to_db_updates()
func _connect_to_db_updates() -> void:
var db = _client.db
# Container updates
db.player_container.subscribe_to_inserts(&"Container", _on_container_inserted)
db.player_container.subscribe_to_updates(&"Container", _on_container_updated)
# Resource updates
db.player_resource.subscribe_to_inserts(&"Resource", _on_resource_inserted)
func load_inventory() -> void:
_containers.clear()
for container in _client.db.player_container.iter():
if str(container.owner) == _player_identity:
_containers.append(container)
containers_changed.emit()
Auto-Generated Bindings
De SDK genereert automatisch GDScript classes voor alle tabellen:
spacetime_bindings/schema/types/
├── milenas_treehouse_v_2_player.gd
├── milenas_treehouse_v_2_player_container.gd
├── milenas_treehouse_v_2_container_item.gd
├── milenas_treehouse_v_2_item_definition.gd
└── ...
Bindings Genereren
# Na wijzigingen aan server schema
cd server
spacetime build
spacetime generate --lang gdscript --out-dir ../spacetime_bindings
Offline Modus
Als SpacetimeDB niet beschikbaar is:
var is_offline: bool = false
func _on_connection_failed() -> void:
is_offline = true
# Fallback naar lokale opslag
_load_from_local_storage()
func _save_locally() -> void:
var file = FileAccess.open("user://save.json", FileAccess.WRITE)
file.store_string(JSON.stringify(game_data))
Best Practices
1. Reducer Validatie
Alle business logica zit in reducers - de client stuurt alleen requests:
# Goed: Server valideert
client.reducers.move_item(from, to, 1)
# Fout: Client valideert (kan worden omzeild)
if _has_item(from): # Niet doen!
_move_item_locally(from, to)
2. Subscribe Filtering
Alleen data ophalen die je nodig hebt:
# Goed: Gefilterd
client.subscribe([
"SELECT * FROM player_position WHERE scene = 'game'"
])
# Minder goed: Alles ophalen
client.subscribe([
"SELECT * FROM player_position"
])
3. Optimistische Updates
Voor snellere UI feedback:
func move_item(from: int, to: int) -> void:
# Lokaal direct updaten (optimistisch)
_update_ui_immediately(from, to)
# Server call
client.reducers.move_item(from, to, 1)
# Bij fout: rollback (via update callback)
Debugging
Logs Bekijken
# Server logs
spacetime logs milenas-treehouse -f
# In Godot: Debug print
print(client.db.player.iter().size(), " players in database")
Connection Status
func _process(_delta) -> void:
if client.is_connected():
connection_label.text = "Online"
else:
connection_label.text = "Offline"
Volgende Stappen
- Scenes Overzicht - Game scenes
- Multiplayer - Multiplayer features