feat: add dust2 level builder, player movement controller, and UI menu components
This commit is contained in:
+72
-25
@@ -24,6 +24,31 @@ func _ready() -> void:
|
||||
_build_materials()
|
||||
_build_geometry()
|
||||
_build_hud()
|
||||
|
||||
# Multiplayer Spawning
|
||||
var spawner = MultiplayerSpawner.new()
|
||||
spawner.name = "PlayerSpawner"
|
||||
spawner.spawn_path = "." # Spawn directly as children of the level
|
||||
spawner.spawn_function = _spawn_player
|
||||
add_child(spawner)
|
||||
|
||||
var nm = get_node_or_null("/root/NetworkManager")
|
||||
if nm and nm.multiplayer.is_server():
|
||||
# Spawn existing players
|
||||
for pid in nm.player_stats.keys():
|
||||
spawner.spawn(pid)
|
||||
|
||||
# Spawn late joiners
|
||||
nm.player_connected.connect(func(pid):
|
||||
spawner.spawn(pid)
|
||||
)
|
||||
|
||||
# Despawn on disconnect
|
||||
nm.player_disconnected.connect(func(pid):
|
||||
var p = get_node_or_null(str(pid))
|
||||
if p:
|
||||
p.queue_free()
|
||||
)
|
||||
|
||||
|
||||
func _build_materials() -> void:
|
||||
@@ -40,7 +65,6 @@ func _build_geometry() -> void:
|
||||
_build_speed_corridor()
|
||||
_build_target_dummy()
|
||||
_build_lighting()
|
||||
_build_player()
|
||||
|
||||
|
||||
# ── Utility ───────────────────────────────────────────────────────────────────
|
||||
@@ -261,12 +285,24 @@ func _build_lighting() -> void:
|
||||
|
||||
# ── Player ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func _build_player() -> void:
|
||||
func _spawn_player(pid: int) -> CharacterBody3D:
|
||||
var player := CharacterBody3D.new()
|
||||
player.name = "Player"
|
||||
player.position = Vector3(0, 1.0, 0)
|
||||
add_child(player)
|
||||
_player = player
|
||||
player.name = str(pid)
|
||||
player.set_multiplayer_authority(pid)
|
||||
|
||||
# Use a slightly random spawn position to avoid exact overlapping
|
||||
player.position = Vector3(randf_range(-2, 2), 2.0, randf_range(-2, 2))
|
||||
|
||||
# Network Synchronizer
|
||||
var sync = MultiplayerSynchronizer.new()
|
||||
sync.name = "MultiplayerSynchronizer"
|
||||
sync.set_multiplayer_authority(pid)
|
||||
var rep_config = SceneReplicationConfig.new()
|
||||
rep_config.add_property(":position")
|
||||
rep_config.add_property(":rotation")
|
||||
rep_config.add_property("HeadPivot:rotation")
|
||||
sync.replication_config = rep_config
|
||||
player.add_child(sync)
|
||||
|
||||
# Collision shape
|
||||
var col_shape := CollisionShape3D.new()
|
||||
@@ -282,11 +318,11 @@ func _build_player() -> void:
|
||||
if mover_script:
|
||||
player.set_script(mover_script)
|
||||
|
||||
# Humanoid Model for Player (Shadows only so it doesn't clip with camera)
|
||||
# Humanoid Model for Player (Shadows only locally, visible to others)
|
||||
var humanoid = load("res://characters/humanoid_model.gd").new()
|
||||
humanoid.name = "HumanoidModel"
|
||||
humanoid.color = Color(0.2, 0.4, 0.8) # Blueish for player
|
||||
humanoid.shadows_only = true
|
||||
humanoid.shadows_only = (pid == multiplayer.get_unique_id())
|
||||
humanoid.position = Vector3(0, -0.9, 0) # Offset from center to feet
|
||||
player.add_child(humanoid)
|
||||
|
||||
@@ -324,7 +360,7 @@ func _build_player() -> void:
|
||||
|
||||
var camera := Camera3D.new()
|
||||
camera.name = "Camera3D"
|
||||
camera.current = true
|
||||
camera.current = (pid == multiplayer.get_unique_id())
|
||||
camera.fov = 90.0
|
||||
head_pivot.add_child(camera)
|
||||
|
||||
@@ -344,27 +380,38 @@ func _build_player() -> void:
|
||||
# Store original capsule height
|
||||
sm.original_capsule_height = capsule.height
|
||||
|
||||
# ── Bootstrap: set_script() doesn't trigger _ready() ─────────────────
|
||||
# Set references BEFORE calling _ready() since sm._ready() will switch_to("ground")
|
||||
# which needs machine.player to already be set.
|
||||
# ── Dependencies ────────────────────────────────────────────────────────
|
||||
# Set references before the node enters the tree. Godot will call _ready()
|
||||
# automatically when MultiplayerSpawner adds the node to the scene.
|
||||
sm.player = player
|
||||
sm.params = player.params
|
||||
head_pivot.params = player.params
|
||||
|
||||
# Initialize in dependency order: machine (enters initial state) → player → camera
|
||||
if sm.has_method("_ready"):
|
||||
sm._ready()
|
||||
if player.has_method("_ready"):
|
||||
player._ready()
|
||||
if head_pivot.has_method("_ready"):
|
||||
head_pivot._ready()
|
||||
# Authority configuration
|
||||
var is_local = (pid == multiplayer.get_unique_id())
|
||||
if is_local:
|
||||
_player = player
|
||||
player.set_physics_process(true)
|
||||
player.set_process(true)
|
||||
sm.set_physics_process(true)
|
||||
else:
|
||||
# Disable processing/input for remote players
|
||||
player.set_physics_process(false)
|
||||
player.set_process(false)
|
||||
player.set_process_input(false)
|
||||
sm.set_physics_process(false)
|
||||
sm.set_process(false)
|
||||
sm.set_process_input(false)
|
||||
head_pivot.set_process_input(false)
|
||||
head_pivot.set_process(false)
|
||||
head_pivot.set_physics_process(false)
|
||||
var wman = camera.get_node_or_null("WeaponManager")
|
||||
if wman:
|
||||
wman.set_process_input(false)
|
||||
wman.set_process(false)
|
||||
wman.set_physics_process(false)
|
||||
|
||||
# Ensure processing is enabled
|
||||
player.set_physics_process(true)
|
||||
player.set_process(true)
|
||||
sm.set_physics_process(true)
|
||||
|
||||
_player = player
|
||||
return player
|
||||
|
||||
|
||||
# ── HUD ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user