ad48f38ca5
Replaces the overhead box-mesh view with a full FPS character: - mouse look, WASD movement, jump (Space), crouch (C), sprint (Shift), lean (Q/E) - 6 weapons (LMB shoot, R reload, scroll switch, F melee, G drop) - Crosshair HUD, ammo counter, sprint bar - Client sends position to server at 20Hz for multiplayer visibility - Server broadcasts positions to all peers - Remote players shown as boxes (placeholder) source: https://godotengine.org/asset-library/asset/2652 (MIT license)
152 lines
5.3 KiB
GDScript
152 lines
5.3 KiB
GDScript
## Client Main — Client entry point for testing.
|
|
##
|
|
## Connects to the server. For the local player, spawns a full FPS
|
|
## character (from the ChaffGames FPS template) with mouse look,
|
|
## weapons, HUD, and movement. For remote players, creates simple
|
|
## box-mesh representations for visibility.
|
|
##
|
|
## The local FPS character moves independently (single-player style).
|
|
## Position is replicated to the server each tick so other clients
|
|
## see where we are.
|
|
|
|
extends Node3D
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Exports
|
|
# ---------------------------------------------------------------------------
|
|
@export var server_host: String = "68.202.6.107"
|
|
@export var server_port: int = 34197
|
|
@export var fps_scene: PackedScene = preload("res://client/template/player_character.tscn")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# State
|
|
# ---------------------------------------------------------------------------
|
|
# remote_players[peer_id] = Node3D — visual representation of other players
|
|
var remote_players: Dictionary = {}
|
|
var connected: bool = false
|
|
|
|
# Our local FPS character
|
|
var _local_player: Node = null
|
|
|
|
# Position sync timer
|
|
var _sync_timer: float = 0.0
|
|
const SYNC_RATE: float = 20.0 # Hz — how often to send position to server
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lifecycle
|
|
# ---------------------------------------------------------------------------
|
|
func _ready() -> void:
|
|
# Connect to server after a short delay
|
|
await get_tree().create_timer(0.5).timeout
|
|
_connect_to_server()
|
|
|
|
func _connect_to_server() -> void:
|
|
if OS.has_environment("SERVER_HOST"):
|
|
server_host = OS.get_environment("SERVER_HOST")
|
|
if OS.has_environment("SERVER_PORT"):
|
|
server_port = int(OS.get_environment("SERVER_PORT"))
|
|
|
|
print("[ClientMain] Connecting to %s:%d ..." % [server_host, server_port])
|
|
|
|
var err: Error = NetworkManager.join_server(server_host, server_port)
|
|
if err != OK:
|
|
push_error("[ClientMain] Connection failed: %s" % error_string(err))
|
|
await get_tree().create_timer(2.0).timeout
|
|
_connect_to_server()
|
|
return
|
|
|
|
connected = true
|
|
|
|
# Connect replication signals from NetworkManager
|
|
NetworkManager.remote_player_spawned.connect(_on_remote_player_spawned)
|
|
NetworkManager.remote_player_despawned.connect(_on_remote_player_despawned)
|
|
|
|
print("[ClientMain] Connected to server. Peer ID: %d" % multiplayer.get_unique_id())
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Local FPS character + position sync
|
|
# ---------------------------------------------------------------------------
|
|
func _process(delta: float) -> void:
|
|
# Sync our position to the server at a fixed rate
|
|
if _local_player:
|
|
_sync_timer += delta
|
|
if _sync_timer >= 1.0 / SYNC_RATE:
|
|
_sync_timer = 0.0
|
|
var pos: Vector3 = _local_player.global_position
|
|
rpc_id(1, "_send_position", pos)
|
|
|
|
@rpc("unreliable")
|
|
func _send_position(pos: Vector3) -> void:
|
|
# Server receives our position and broadcasts to other clients
|
|
pass
|
|
|
|
# Server broadcasts other players' positions to us
|
|
@rpc("unreliable", "authority")
|
|
func _replicate_position(pos: Vector3, moving_peer_id: int) -> void:
|
|
# Update the position of a remote player on our screen
|
|
if moving_peer_id in remote_players:
|
|
remote_players[moving_peer_id].position = pos
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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 — spawn the full FPS character
|
|
if _local_player:
|
|
push_warning("[ClientMain] Local player already exists, despawning old")
|
|
_local_player.queue_free()
|
|
|
|
_local_player = fps_scene.instantiate()
|
|
_local_player.name = "LocalPlayer"
|
|
_local_player.global_position = pos
|
|
add_child(_local_player, true)
|
|
|
|
print("[ClientMain] Spawned LOCAL FPS player at (%.1f, %.1f)" % [pos.x, pos.z])
|
|
return
|
|
|
|
if peer_id in remote_players:
|
|
push_warning("[ClientMain] Remote player %d already exists, skipping" % peer_id)
|
|
return
|
|
|
|
# Create a remote player node for visualization (simple box mesh)
|
|
var player := Node3D.new()
|
|
player.name = "RemotePlayer_%d" % peer_id
|
|
player.set_multiplayer_authority(peer_id)
|
|
player.position = pos
|
|
|
|
# Add a simple box mesh so we can see where other players are
|
|
var mesh := MeshInstance3D.new()
|
|
mesh.mesh = BoxMesh.new()
|
|
mesh.mesh.size = Vector3(1, 1, 1)
|
|
mesh.position.y = 0.5
|
|
player.add_child(mesh)
|
|
|
|
add_child(player, true)
|
|
remote_players[peer_id] = player
|
|
|
|
print("[ClientMain] Spawned remote player %d at (%.1f, %.1f)" % [peer_id, pos.x, pos.z])
|
|
|
|
func _on_remote_player_despawned(peer_id: int) -> void:
|
|
if peer_id == multiplayer.get_unique_id():
|
|
if _local_player:
|
|
_local_player.queue_free()
|
|
_local_player = null
|
|
print("[ClientMain] Local player despawned")
|
|
return
|
|
|
|
if peer_id in remote_players:
|
|
remote_players[peer_id].queue_free()
|
|
remote_players.erase(peer_id)
|
|
print("[ClientMain] Despawned remote player %d" % peer_id)
|
|
|
|
func _exit_tree() -> void:
|
|
if connected:
|
|
NetworkManager.stop()
|
|
|
|
if _local_player:
|
|
_local_player.queue_free()
|
|
for p in remote_players.values():
|
|
p.queue_free()
|
|
remote_players.clear()
|