fix: create local player on client so WASD movement works

client_main.gd: when server spawns our player, create a player node with authority

- Camera follows local player from 3/4 overhead view

- player.gd WASD input sends position to server via RPC

- Server validates and broadcasts to all peers
This commit is contained in:
2026-07-02 00:15:08 -04:00
parent d060ac449d
commit f0c95dcfd2
+52 -22
View File
@@ -22,6 +22,10 @@ extends Node3D
var remote_players: Dictionary = {}
var connected: bool = false
# Our local player node (created when server spawns us)
var _local_player: Node = null
var _camera: Camera3D = null
# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------
@@ -45,8 +49,17 @@ func _connect_to_server() -> void:
_connect_to_server()
return
# Setup camera and lights
_setup_scene()
# Setup camera (fixed overhead until a player is spawned)
_camera = _make_camera()
add_child(_camera)
# Ambient light so we can see the box meshes
var light := DirectionalLight3D.new()
light.light_energy = 1.0
light.shadow_enabled = true
light.position = Vector3(10, 20, 10)
add_child(light)
light.look_at(Vector3.ZERO)
connected = true
@@ -57,31 +70,41 @@ func _connect_to_server() -> void:
print("[ClientMain] Connected to server. Peer ID: %d" % multiplayer.get_unique_id())
func _setup_scene() -> void:
# Simple 3/4 top-down camera for Phase 0 testing
var camera := Camera3D.new()
camera.current = true
camera.position = Vector3(0, 16, 12)
camera.rotation_degrees.x = -55
add_child(camera)
# Ambient light so we can see the box meshes
var light := DirectionalLight3D.new()
light.light_energy = 1.0
light.shadow_enabled = true
light.position = Vector3(10, 20, 10)
add_child(light)
light.look_at(Vector3.ZERO)
func _make_camera() -> Camera3D:
var cam := Camera3D.new()
cam.current = true
cam.position = Vector3(0, 16, 12)
cam.rotation_degrees.x = -55
return cam
func _process(_delta: float) -> void:
# Make camera follow local player if we have one
if _local_player and _camera:
_camera.position = _local_player.position + Vector3(0, 16, 12)
# Always look down at the player
var look_target := _local_player.position
look_target.y = _local_player.position.y + 2.0
_camera.look_at(look_target)
# ---------------------------------------------------------------------------
# Player replication handlers (called when server broadcasts via RPC)
# ---------------------------------------------------------------------------
func _on_remote_player_spawned(peer_id: int, pos: Vector3) -> void:
if peer_id == multiplayer.get_unique_id():
# This is OUR player — the server has authority over it,
# but we don't need to create a duplicate. Our local player
# node is managed by the server's authority system.
# The player.gd script will handle input on this peer.
# THIS IS OUR PLAYER — create a local player node and attach camera.
# The player.gd script handles WASD input and sends position to server.
if _local_player:
push_warning("[ClientMain] Local player already exists, respawning")
_local_player.queue_free()
_local_player = player_scene.instantiate()
_local_player.name = "LocalPlayer"
_local_player.position = pos
# Set authority so player.gd detects is_local = true and enables input
_local_player.set_multiplayer_authority(peer_id)
add_child(_local_player, true)
print("[ClientMain] Spawned LOCAL player at (%.1f, %.1f)" % [pos.x, pos.z])
return
if peer_id in remote_players:
@@ -107,6 +130,11 @@ func _on_remote_player_spawned(peer_id: int, pos: Vector3) -> void:
func _on_remote_player_despawned(peer_id: int) -> void:
if peer_id == multiplayer.get_unique_id():
# Our player was despawned
if _local_player:
_local_player.queue_free()
_local_player = null
print("[ClientMain] Local player despawned")
return
if peer_id in remote_players:
@@ -118,7 +146,9 @@ func _exit_tree() -> void:
if connected:
NetworkManager.stop()
# Clean up any remaining remote players
# Clean up any remaining players
if _local_player:
_local_player.queue_free()
for p in remote_players.values():
p.queue_free()
remote_players.clear()