feat: Implement Nakama serialization, Realtime API, and a comprehensive lobby system with UI and player management.

This commit is contained in:
Yogi Wiguna
2026-02-09 16:46:42 +08:00
parent a68878483f
commit bffa9474e9
7 changed files with 221 additions and 45 deletions
+50 -6
View File
@@ -13,7 +13,20 @@ var powerup_manager
var score: int = 0
# Display name (synced across network)
var display_name: String = ""
var _display_name: String = ""
var display_name: String:
set(value):
_display_name = value
# Update label if it exists
var name_label = get_node_or_null("Name")
if name_label:
name_label.text = _display_name
# Sync to other peers if we are authority
if is_multiplayer_authority() and is_inside_tree():
rpc("sync_display_name", _display_name)
get:
return _display_name
# Special effect states
var is_frozen: bool = false
@@ -262,6 +275,11 @@ func _ready():
rpc("sync_position", current_position)
else:
target_visual_position = global_position
# If random spawn is enabled, do NOT broadcast (0,0). Wait for set_spawn_position.
# Prevent visual "jump" by hiding until spawn position is confirmed
if LobbyManager.get_randomize_spawn() and not spawn_point_selected:
visible = false
func _init_managers():
movement_manager = load("res://scripts/managers/player_movement_manager.gd").new()
@@ -588,8 +606,9 @@ func sync_bot_status(is_bot_status: bool):
@rpc("any_peer", "call_local", "reliable")
func sync_display_name(new_name: String) -> void:
"""Sync display name across network."""
display_name = new_name
$Name.text = display_name
_display_name = new_name
if has_node("Name"):
$Name.text = _display_name
@rpc("any_peer", "call_local", "reliable")
func sync_modulate(color: Color) -> void:
@@ -893,6 +912,11 @@ func activate_powerup(effect_id: int):
func _process(delta):
# Failsafe: Ensure player is visible if spawn point is selected and random spawn is active
# This handles race conditions where set_spawn_position might be called before _ready finishes hiding
if LobbyManager.get_randomize_spawn() and spawn_point_selected and not visible:
visible = true
if is_multiplayer_authority():
# Visual debugging - show display name with connection status
$Name.text = display_name if not display_name.is_empty() else str(name)
@@ -906,8 +930,11 @@ func _process(delta):
# Client-side visual smoothing
# Only interpolate if NOT running a movement tween, OR if the drift is large (teleport/snap)
if not is_player_moving:
# Snap to target if very close (prevents micro-jitter)
if global_position.distance_squared_to(target_visual_position) < 0.001:
var dist_sq = global_position.distance_squared_to(target_visual_position)
if dist_sq > 4.0: # If distance > 2.0 units (teleport/spawn), snap immediately
global_position = target_visual_position
elif dist_sq < 0.001: # Prevent micro-jitter
global_position = target_visual_position
else:
# Interpolate towards the target position received from authority
@@ -1157,6 +1184,12 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
is_player_moving = true
if path.size() > 0:
target_position = Vector2i(path[-1].x, path[-1].y)
# FORCE SNAP START: Ensure we start animating from our actual grid position
# This prevents "jumps" if the visual node drifted or was set via global_position vs position mismatch
var start_world_pos = grid_to_world(current_position)
if global_position.distance_squared_to(start_world_pos) > 0.001:
global_position = start_world_pos
var tween = create_tween()
tween.set_trans(Tween.TRANS_LINEAR)
@@ -1167,7 +1200,8 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
step_duration = step_duration / movement_manager.speed_multiplier
for point in path:
tween.tween_property(self, "position", grid_to_world(Vector2i(point.x, point.y)), step_duration)
# Use global_position for consistency
tween.tween_property(self, "global_position", grid_to_world(Vector2i(point.x, point.y)), step_duration)
tween.tween_callback(func():
current_position = Vector2i(path[-1].x, path[-1].y)
@@ -1739,6 +1773,13 @@ func update_visual_position():
@rpc("any_peer", "call_local")
func sync_position(pos: Vector2i):
current_position = pos
# If random spawn is active, receiving a position sync essentially confirms a valid spawn
if LobbyManager.get_randomize_spawn():
spawn_point_selected = true
if not visible:
visible = true
# Always update the visual position after position sync
var new_pos = Vector3(
current_position.x * cell_size.x + cell_size.x * 0.5,
@@ -1767,6 +1808,9 @@ func set_spawn_position(pos: Vector2i):
global_position = new_pos
target_visual_position = new_pos
# Reveal character now that it's in the correct position
visible = true
@rpc("any_peer", "call_local", "reliable")