WIP checkpoint: Implement Godot dedicated server scene + player spawning
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[gd_scene load_steps=2 format=3]
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/player_node.gd" id="1"]
|
[ext_resource type="Script" path="res://scripts/player_spawner.gd" id="1"]
|
||||||
|
|
||||||
[node name="Player" type="Node3D"]
|
[node name="Player" type="Node3D"]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ extends CharacterBody3D
|
|||||||
## Represents a connected player on the server. Handles movement,
|
## Represents a connected player on the server. Handles movement,
|
||||||
## input processing, and state synchronization.
|
## input processing, and state synchronization.
|
||||||
|
|
||||||
class_name PlayerNode
|
|
||||||
|
|
||||||
|
|
||||||
# --- Signals ---
|
# --- Signals ---
|
||||||
signal health_changed(new_health: int)
|
signal health_changed(new_health: int)
|
||||||
signal player_died(player_node: PlayerNode)
|
signal player_died(player_node: PlayerNode)
|
||||||
@@ -65,12 +62,14 @@ func _physics_process(delta: float) -> void:
|
|||||||
|
|
||||||
# Apply friction on ground
|
# Apply friction on ground
|
||||||
if is_on_floor():
|
if is_on_floor():
|
||||||
var move_dir = velocity.xz
|
var move_dir := Vector2(velocity.x, velocity.z)
|
||||||
if move_dir.length() > 0.01:
|
if move_dir.length() > 0.01:
|
||||||
var friction_factor = clampf(FRICTION * delta, 0.0, 1.0)
|
var friction_factor := clampf(FRICTION * delta, 0.0, 1.0)
|
||||||
velocity.xz = move_dir.normalized() * move_dir.length() * (1.0 - friction_factor)
|
velocity.x = move_dir.normalized().x * move_dir.length() * (1.0 - friction_factor)
|
||||||
|
velocity.z = move_dir.normalized().y * move_dir.length() * (1.0 - friction_factor)
|
||||||
else:
|
else:
|
||||||
velocity.xz = Vector2.ZERO
|
velocity.x = 0.0
|
||||||
|
velocity.z = 0.0
|
||||||
|
|
||||||
# Move and slide
|
# Move and slide
|
||||||
velocity.y = clampf(velocity.y, -max_fall_speed, max_fall_speed)
|
velocity.y = clampf(velocity.y, -max_fall_speed, max_fall_speed)
|
||||||
|
|||||||
+38
-20
@@ -3,6 +3,7 @@
|
|||||||
## and runs the simulation tick loop at 20 Hz.
|
## and runs the simulation tick loop at 20 Hz.
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
## Maximum number of concurrent players.
|
## Maximum number of concurrent players.
|
||||||
const MAX_PLAYERS := 16
|
const MAX_PLAYERS := 16
|
||||||
|
|
||||||
@@ -30,13 +31,18 @@ var is_running: bool = false
|
|||||||
## Accumulated real time for fixed timestep simulation.
|
## Accumulated real time for fixed timestep simulation.
|
||||||
var accumulator: float = 0.0
|
var accumulator: float = 0.0
|
||||||
|
|
||||||
|
## Player scene resource (loaded once).
|
||||||
|
var _player_scene: PackedScene
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Set this node as the multiplayer authority.
|
# Preload the player scene once.
|
||||||
multiplayer.multiplayer_peer = _create_server_peer()
|
_player_scene = preload("res://scenes/player.tscn")
|
||||||
multiplayer.server = self
|
|
||||||
|
|
||||||
|
# Create and assign the ENet server peer.
|
||||||
|
network_peer = _create_server_peer()
|
||||||
if network_peer:
|
if network_peer:
|
||||||
|
multiplayer.multiplayer_peer = network_peer
|
||||||
is_running = true
|
is_running = true
|
||||||
print("[Server] Started on port %d" % SERVER_PORT)
|
print("[Server] Started on port %d" % SERVER_PORT)
|
||||||
else:
|
else:
|
||||||
@@ -44,23 +50,27 @@ func _ready() -> void:
|
|||||||
get_tree().quit(1)
|
get_tree().quit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Server creation
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func _create_server_peer() -> ENetMultiplayerPeer:
|
func _create_server_peer() -> ENetMultiplayerPeer:
|
||||||
var peer := ENetMultiplayerPeer.new()
|
var peer := ENetMultiplayerPeer.new()
|
||||||
var error := peer.create_server(SERVER_PORT, MAX_PLAYERS)
|
var error := peer.create_server(SERVER_PORT, MAX_PLAYERS)
|
||||||
if error != OK:
|
if error != OK:
|
||||||
printerr("[Server] create_server error: %d" % error)
|
printerr("[Server] create_server error: %d" % error)
|
||||||
return ENetMultiplayerPeer()
|
return null
|
||||||
return peer
|
return peer
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Fixed-timestep simulation loop
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if not is_running:
|
if not is_running:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Handle disconnect events.
|
|
||||||
if multiplayer.peer_disconnected.is_connected(_on_peer_disconnected):
|
|
||||||
pass # already connected
|
|
||||||
|
|
||||||
# Fixed timestep accumulator.
|
# Fixed timestep accumulator.
|
||||||
accumulator += delta
|
accumulator += delta
|
||||||
while accumulator >= TICK_DURATION:
|
while accumulator >= TICK_DURATION:
|
||||||
@@ -74,7 +84,7 @@ func _tick() -> void:
|
|||||||
|
|
||||||
# Process player inputs and update state.
|
# Process player inputs and update state.
|
||||||
for peer_id in players:
|
for peer_id in players:
|
||||||
var player: Node = players[peer_id]
|
var player: Node3D = players[peer_id]
|
||||||
if player and player.has_method("process_server_input"):
|
if player and player.has_method("process_server_input"):
|
||||||
player.process_server_input(tick)
|
player.process_server_input(tick)
|
||||||
|
|
||||||
@@ -86,17 +96,19 @@ func _broadcast_snapshot() -> void:
|
|||||||
"""Broadcast the current authoritative state to all connected clients."""
|
"""Broadcast the current authoritative state to all connected clients."""
|
||||||
var snapshot_data := {
|
var snapshot_data := {
|
||||||
"tick": tick,
|
"tick": tick,
|
||||||
"players": {}
|
"players": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
for peer_id in players:
|
for peer_id in players:
|
||||||
var player: Node = players[peer_id]
|
var player: Node3D = players[peer_id]
|
||||||
if player and player.has_method("get_server_state"):
|
if player and player.has_method("get_server_state"):
|
||||||
snapshot_data.players[peer_id] = player.get_server_state()
|
snapshot_data.players[peer_id] = player.get_server_state()
|
||||||
|
|
||||||
# Send snapshot to all peers except the server itself.
|
# Send snapshot to all peers except the server itself (peer_id == 1).
|
||||||
var peer_ids: Array = multiplayer.get_peers()
|
var peer_ids: Array = multiplayer.get_peers()
|
||||||
for pid in peer_ids:
|
for pid in peer_ids:
|
||||||
|
if pid == 1: # 1 is the server itself
|
||||||
|
continue
|
||||||
rpc_id(pid, "_receive_snapshot", snapshot_data)
|
rpc_id(pid, "_receive_snapshot", snapshot_data)
|
||||||
|
|
||||||
|
|
||||||
@@ -106,7 +118,9 @@ func _receive_snapshot(snapshot_data: Dictionary) -> void:
|
|||||||
pass # Handled by client scene.
|
pass # Handled by client scene.
|
||||||
|
|
||||||
|
|
||||||
# --- Connection lifecycle ---
|
# ---------------------------------------------------------------------------
|
||||||
|
# Connection lifecycle
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func _on_peer_connected(peer_id: int) -> void:
|
func _on_peer_connected(peer_id: int) -> void:
|
||||||
print("[Server] Peer %d connected." % peer_id)
|
print("[Server] Peer %d connected." % peer_id)
|
||||||
@@ -117,7 +131,7 @@ func _on_peer_connected(peer_id: int) -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Spawn a Player node for this peer.
|
# Spawn a Player node for this peer.
|
||||||
var player := preload("res://scripts/player.tscn").instantiate()
|
var player: Node3D = _player_scene.instantiate()
|
||||||
player.name = "Player_%d" % peer_id
|
player.name = "Player_%d" % peer_id
|
||||||
player.peer_id = peer_id
|
player.peer_id = peer_id
|
||||||
add_child(player)
|
add_child(player)
|
||||||
@@ -129,18 +143,20 @@ func _on_peer_connected(peer_id: int) -> void:
|
|||||||
func _on_peer_disconnected(peer_id: int) -> void:
|
func _on_peer_disconnected(peer_id: int) -> void:
|
||||||
print("[Server] Peer %d disconnected." % peer_id)
|
print("[Server] Peer %d disconnected." % peer_id)
|
||||||
if players.has(peer_id):
|
if players.has(peer_id):
|
||||||
var player: Node = players[peer_id]
|
var player: Node3D = players[peer_id]
|
||||||
if player.is_inside_tree():
|
if player.is_inside_tree():
|
||||||
player.queue_free()
|
player.queue_free()
|
||||||
players.erase(peer_id)
|
players.erase(peer_id)
|
||||||
|
|
||||||
|
|
||||||
# --- RPCs ---
|
# ---------------------------------------------------------------------------
|
||||||
|
# RPCs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@rpc("any_peer", "call_remote", "reliable")
|
@rpc("any_peer", "call_remote", "reliable")
|
||||||
func _kick(peer_id: int, reason: String) -> void:
|
func _kick(reason: String) -> void:
|
||||||
print("[Server] Kicking peer %d: %s" % [peer_id, reason])
|
print("[Server] Kicking caller: %s" % reason)
|
||||||
rpc_id(peer_id, "_disconnect", reason)
|
rpc_id(get_rpc_sender_id(), "_disconnect", reason)
|
||||||
|
|
||||||
|
|
||||||
@rpc("call_remote", "reliable")
|
@rpc("call_remote", "reliable")
|
||||||
@@ -149,7 +165,9 @@ func _disconnect(reason: String) -> void:
|
|||||||
get_tree().quit(0)
|
get_tree().quit(0)
|
||||||
|
|
||||||
|
|
||||||
# --- Signal connections ---
|
# ---------------------------------------------------------------------------
|
||||||
|
# Signal connections
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
func _enter_tree() -> void:
|
||||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||||
|
|||||||
Reference in New Issue
Block a user