Ga naar hoofdinhoud

Multiplayer Overzicht

Architectuur en concepten voor multiplayer functionaliteit.

Architectuur

┌─────────────────────────────────────────────────────────────────┐
│ Multiplayer Architectuur │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Client 1 │ │ Client 2 │ │
│ │ (Godot) │ │ (Godot) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ │ WebSocket │ WebSocket │
│ │ │ │
│ └────────────┬───────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ SpacetimeDB │ │
│ │ Server │ │
│ │ (Rust WASM) │ │
│ └───────────────┘ │
│ │
│ Data Flow: │
│ 1. Client stuurt reducer call │
│ 2. Server valideert & update database │
│ 3. Server broadcast changes naar subscribed clients │
│ 4. Clients ontvangen updates via callbacks │
│ │
└─────────────────────────────────────────────────────────────────┘

SpacetimeDB Integratie

Verbinding Maken

# spacetime_client.gd
extends Node

signal connected()
signal disconnected()
signal connection_error(error: String)

const SERVER_URL = "wss://mainnet.spacetimedb.com"
const MODULE_NAME = "milenas_treehouse"

var _client: SpacetimeDBClient = null
var _identity: String = ""
var is_connected: bool = false

func _ready() -> void:
_client = SpacetimeDBClient.new()
add_child(_client)

_client.on_connect.connect(_on_connected)
_client.on_disconnect.connect(_on_disconnected)
_client.on_error.connect(_on_error)

func connect_to_server() -> void:
var token = _load_auth_token()

_client.connect_to_db(
SERVER_URL,
MODULE_NAME,
token
)

func _on_connected(identity: String, token: String) -> void:
_identity = identity
is_connected = true

_save_auth_token(token)
_setup_subscriptions()

connected.emit()

func _on_disconnected() -> void:
is_connected = false
disconnected.emit()

Subscriptions

func _setup_subscriptions() -> void:
# Subscribe naar relevante tabellen
_client.subscribe([
"SELECT * FROM Player WHERE identity = :identity",
"SELECT * FROM PlayerCharacter WHERE player_id = :player_id",
"SELECT * FROM InventoryItem WHERE player_id = :player_id",
"SELECT * FROM OnlinePlayer", # Alle online spelers
"SELECT * FROM ChatMessage WHERE timestamp > :last_seen",
"SELECT * FROM FriendRequest WHERE target_id = :player_id"
])

Online Players

Tracking

# online_players_manager.gd
extends Node

signal player_joined(player_data: Dictionary)
signal player_left(player_id: int)
signal player_updated(player_id: int, data: Dictionary)

var online_players: Dictionary = {} # player_id -> player_data

func _ready() -> void:
SpacetimeDB.OnlinePlayer.on_insert.connect(_on_player_online)
SpacetimeDB.OnlinePlayer.on_delete.connect(_on_player_offline)
SpacetimeDB.OnlinePlayer.on_update.connect(_on_player_update)

func _on_player_online(row: OnlinePlayer) -> void:
# Skip self
if row.player_id == GameState.player_id:
return

var player_data = {
"player_id": row.player_id,
"display_name": row.display_name,
"current_scene": row.current_scene,
"position": Vector2(row.position_x, row.position_y),
"character_data": _parse_character_data(row.character_data)
}

online_players[row.player_id] = player_data
player_joined.emit(player_data)

func _on_player_offline(row: OnlinePlayer) -> void:
if row.player_id in online_players:
online_players.erase(row.player_id)
player_left.emit(row.player_id)

func _on_player_update(old_row: OnlinePlayer, new_row: OnlinePlayer) -> void:
if new_row.player_id not in online_players:
return

var player_data = online_players[new_row.player_id]
player_data.current_scene = new_row.current_scene
player_data.position = Vector2(new_row.position_x, new_row.position_y)

player_updated.emit(new_row.player_id, player_data)

Remote Player Instantiation

# scene_manager.gd
func _on_player_joined(player_data: Dictionary) -> void:
# Only spawn if same scene
if player_data.current_scene != current_scene_name:
return

_spawn_remote_player(player_data)

func _spawn_remote_player(player_data: Dictionary) -> void:
var remote_player = REMOTE_PLAYER_SCENE.instantiate()
remote_player.player_id = player_data.player_id
remote_player.display_name = player_data.display_name
remote_player.position = player_data.position
remote_player.setup_character(player_data.character_data)

_remote_players[player_data.player_id] = remote_player
players_container.add_child(remote_player)

Multiplayer Features

Gedeelde Wereld

┌─────────────────────────────────────────────────────────────┐
│ Shared World Data │
├─────────────────────────────────────────────────────────────┤
│ │
│ Persistent (Database): Real-time (Subscriptions): │
│ ├── Player inventories ├── Player positions │
│ ├── Farm plots ├── Player animations │
│ ├── Club data ├── Chat messages │
│ ├── Quest progress ├── Emotes/reactions │
│ └── Pet data └── Activity indicators │
│ │
└─────────────────────────────────────────────────────────────┘

Club System

# Spelers kunnen clubs joinen en samen spelen
func get_club_members_online() -> Array:
var members = []
var club = ClubManager.get_current_club()

if club.is_empty():
return members

for player_id in online_players:
var player = online_players[player_id]
if player.club_id == club.id:
members.append(player)

return members

func teleport_to_club_member(player_id: int) -> void:
if player_id not in online_players:
return

var target = online_players[player_id]
SceneManager.change_scene(target.current_scene, target.position)

Samen Activiteiten

# Farming samen
func can_help_farm(plot: FarmPlot) -> bool:
# Andere spelers kunnen helpen met water geven
return plot.owner_id != GameState.player_id and not plot.is_watered

func help_water_plot(plot: FarmPlot) -> void:
SpacetimeDB.call_reducer("help_water_farm_plot", [
plot.owner_id,
plot.plot_index
])

# Geeft friendship bonus aan beide spelers

Connection States

enum ConnectionState {
DISCONNECTED,
CONNECTING,
CONNECTED,
RECONNECTING,
ERROR
}

var connection_state: ConnectionState = ConnectionState.DISCONNECTED

func _update_connection_ui() -> void:
match connection_state:
ConnectionState.DISCONNECTED:
connection_indicator.modulate = Color.GRAY
connection_label.text = "Offline"
ConnectionState.CONNECTING:
connection_indicator.modulate = Color.YELLOW
connection_label.text = "Verbinden..."
ConnectionState.CONNECTED:
connection_indicator.modulate = Color.GREEN
connection_label.text = "Online"
ConnectionState.RECONNECTING:
connection_indicator.modulate = Color.ORANGE
connection_label.text = "Opnieuw verbinden..."
ConnectionState.ERROR:
connection_indicator.modulate = Color.RED
connection_label.text = "Verbindingsfout"

Reconnection Logic

const MAX_RECONNECT_ATTEMPTS = 5
const RECONNECT_DELAY = 2.0

var reconnect_attempts: int = 0

func _on_disconnected() -> void:
connection_state = ConnectionState.RECONNECTING
_attempt_reconnect()

func _attempt_reconnect() -> void:
if reconnect_attempts >= MAX_RECONNECT_ATTEMPTS:
connection_state = ConnectionState.ERROR
_show_reconnect_dialog()
return

reconnect_attempts += 1

await get_tree().create_timer(RECONNECT_DELAY * reconnect_attempts).timeout

connect_to_server()

func _on_connected(identity: String, token: String) -> void:
reconnect_attempts = 0
connection_state = ConnectionState.CONNECTED
# ...

Offline Mode

# Beperkte functionaliteit wanneer offline
func is_feature_available(feature: String) -> bool:
if not SpacetimeClient.is_connected:
match feature:
"chat": return false
"friends": return false
"clubs": return false
"trading": return false
"farm": return true # Lokale cache
"inventory": return true # Lokale cache
_: return true

return true

func _show_offline_notice(feature: String) -> void:
NotificationManager.toast(
"Je moet online zijn voor deze functie",
"warning"
)

Volgende