feat: add dust2 level builder, player movement controller, and UI menu components

This commit is contained in:
DottsGit
2026-06-06 15:17:54 -04:00
parent 31336fe870
commit cd034d1aea
5 changed files with 130 additions and 64 deletions
+7 -6
View File
@@ -28,12 +28,13 @@ func _build_geometry() -> void:
fill.light_energy = 0.3
_build_dust2_layout()
_build_player()
# Override player spawn point for Dust 2
if _player:
_player.global_position = Vector3(0, 1, 50)
_player.look_at(Vector3(0, 1, 0), Vector3.UP)
func _spawn_player(pid: int) -> CharacterBody3D:
var player = super._spawn_player(pid)
player.position = Vector3(randf_range(-2, 2), 1, 50 + randf_range(-2, 2))
# Looking at (0,1,0) from (0,1,50) is facing straight down the -Z axis, which is rotation = 0,0,0
player.rotation = Vector3.ZERO
return player
func _build_dust2_layout() -> void:
# Materials
+72 -25
View File
@@ -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 ───────────────────────────────────────────────────────────────────────