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
+107 -31
View File
@@ -370,6 +370,9 @@ func _setup_host_game():
if touch_controls:
touch_controls.set_player(player_character)
# Set host name
player_character.display_name = LobbyManager.local_player_name
# Spawn client players that joined via lobby (need to add them first)
var lobby_players = LobbyManager.get_players()
for lobby_player in lobby_players:
@@ -429,6 +432,13 @@ func _spawn_lobby_client_sync(peer_id: int):
player_character.add_to_group("Players", true)
GameStateManager.add_player(peer_id)
# Set name from LobbyManager data if available
var lobby_players = LobbyManager.get_players()
for p_data in lobby_players:
if p_data.get("id") == peer_id:
player_character.display_name = p_data.get("name", "Player")
break
# Tell all clients to create this player
rpc("add_newly_connected_player_character", peer_id)
@@ -529,43 +539,93 @@ func _start_game():
ui_manager.initialize_leaderboard_with_players(all_players)
func _assign_random_spawn_positions():
"""Assign random unique spawn positions to all players."""
# Fetch all valid walkable positions from the generated map
var valid_spawns = []
"""Assign spawn positions distributed to 4 corners (2 per corner for 8 players)."""
var enhanced_gridmap = $EnhancedGridMap
if enhanced_gridmap:
for x in range(enhanced_gridmap.columns):
for z in range(enhanced_gridmap.rows):
var ground = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z))
if ground == 0: # Walkable
valid_spawns.append(Vector2i(x, z))
# Fallback if map generation failed or is empty
if valid_spawns.size() < 12:
print("Warning: Low spawn count! Adding defaults.")
for i in range(12):
if not Vector2i(0, i) in valid_spawns:
valid_spawns.append(Vector2i(0, i))
if not enhanced_gridmap:
return
# Shuffle spawn locations
valid_spawns.shuffle()
# Lists for each quadrant
var spawns_TL = [] # Top-Left
var spawns_TR = [] # Top-Right
var spawns_BL = [] # Bottom-Left
var spawns_BR = [] # Bottom-Right
var all_spawns = [] # Fallback
var mid_x = enhanced_gridmap.columns / 2
var mid_z = enhanced_gridmap.rows / 2
for x in range(enhanced_gridmap.columns):
for z in range(enhanced_gridmap.rows):
var ground = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z))
if ground == 0: # Walkable
var pos = Vector2i(x, z)
all_spawns.append(pos)
if x < mid_x and z < mid_z:
spawns_TL.append(pos)
elif x >= mid_x and z < mid_z:
spawns_TR.append(pos)
elif x < mid_x and z >= mid_z:
spawns_BL.append(pos)
else:
spawns_BR.append(pos)
# Sort lists by distance to corners (closest to corner should be last, to be popped first)
# TL: Close to (0,0) -> Sort descending distance (so closest is at end)
spawns_TL.sort_custom(func(a, b): return a.length_squared() > b.length_squared())
# TR: Close to (13, 0)
var tr_corner = Vector2i(enhanced_gridmap.columns - 1, 0)
spawns_TR.sort_custom(func(a, b): return a.distance_squared_to(tr_corner) > b.distance_squared_to(tr_corner))
# BL: Close to (0, 13)
var bl_corner = Vector2i(0, enhanced_gridmap.rows - 1)
spawns_BL.sort_custom(func(a, b): return a.distance_squared_to(bl_corner) > b.distance_squared_to(bl_corner))
# BR: Close to (13, 13)
var br_corner = Vector2i(enhanced_gridmap.columns - 1, enhanced_gridmap.rows - 1)
spawns_BR.sort_custom(func(a, b): return a.distance_squared_to(br_corner) > b.distance_squared_to(br_corner))
# Fallback shuffle
all_spawns.shuffle()
# Get all players
var all_players = get_tree().get_nodes_in_group("Players")
# Assign positions
var spawn_index = 0
# Round-robin assignment to corners: TL, TR, BR, BL, TL, TR, BR, BL...
# Order: TL -> TR -> BR -> BL (Clockwise-ish)
var quadrants = [spawns_TL, spawns_TR, spawns_BR, spawns_BL]
for player in all_players:
if spawn_index >= valid_spawns.size():
print("Critical: Not enough spawn points for players!")
break
var spawn_pos = valid_spawns[spawn_index]
# Set position and sync to all clients
player.current_position = spawn_pos
player.position = player.grid_to_world(spawn_pos)
player.spawn_point_selected = true
player.rpc("set_spawn_position", spawn_pos)
var assigned_pos = Vector2i(-1, -1)
# Try to get from the current quadrant
var quadrant_idx = spawn_index % 4
var quadrant = quadrants[quadrant_idx]
if quadrant.size() > 0:
assigned_pos = quadrant.pop_back()
else:
# Fallback: Try other quadrants if preferred one is empty
for q in quadrants:
if q.size() > 0:
assigned_pos = q.pop_back()
break
# Ultimate fallback: Random from anywhere
if assigned_pos == Vector2i(-1, -1) and all_spawns.size() > 0:
assigned_pos = all_spawns.pop_back()
if assigned_pos != Vector2i(-1, -1):
# Set position and sync to all clients
player.current_position = assigned_pos
player.position = player.grid_to_world(assigned_pos)
player.spawn_point_selected = true
player.rpc("set_spawn_position", assigned_pos)
else:
print("Critical: No spawn point found for player ", player.name)
spawn_index += 1
# =============================================================================
@@ -910,9 +970,11 @@ func request_full_player_sync(requesting_peer_id: int):
var player_data = {
"peer_id": peer_id,
"position": player.current_position,
"name": player.display_name,
"goals": player.goals,
"playerboard": player.playerboard,
"is_bot": player.is_bot || player.is_in_group("Bots")
"is_bot": player.is_bot || player.is_in_group("Bots"),
"spawn_point_selected": player.spawn_point_selected
}
rpc_id(requesting_peer_id, "create_specific_player", player_data)
await get_tree().create_timer(0.1).timeout
@@ -933,6 +995,20 @@ func create_specific_player(data: Dictionary):
add_child(player_character)
player_character.add_to_group("Players", true)
# Set spawn flag directly so it doesn't hide itself
if data.has("spawn_point_selected") and data["spawn_point_selected"]:
player_character.spawn_point_selected = true
player_character.visible = true
# Ensure visual position matches logical
var new_pos = player_character.grid_to_world(data["position"])
player_character.global_position = new_pos
player_character.target_visual_position = new_pos
# Set display name
if data.has("name"):
player_character.display_name = data["name"]
if data["is_bot"]:
player_character.add_to_group("Bots", true)
player_character.is_bot = true