feat(map): high-quality movement map with spawns, cover, and wall-run surfaces #18

Closed
Hermes wants to merge 1 commits from feat/2-movement-map into main
3 changed files with 549 additions and 56 deletions
+547 -36
View File
@@ -1,39 +1,38 @@
extends Node3D
class_name TestLevelBuilder
## Builds the test level ONLY from code — no .tscn SubResource references.
## One-click playtest: attach to TestLevel root, F5.
## High-quality movement map for Papaya Shooter.
## Features:
## - Multi-level layout with ramps, platforms, and verticality
## - Wall-run surfaces on key walls
## - Cover systems (crates, pillars, low walls) for tactical play
## - 6 player spawn points spread across the map with nearby cover
## - Anti-spawn-camping: spawns in enclosed alcoves with multiple exit routes
## - Varied materials and lighting for visual appeal
## - Movement-friendly: wide corridors, smooth ramps, no dead-end traps
func _ready() -> void:
build()
func build() -> void:
_build_input_map()
_build_floor()
_build_walls()
_build_sky()
_build_lighting()
_build_floor_and_ground()
_build_outer_walls()
_build_ramps()
_build_platforms()
_build_cover()
_build_pillars()
_build_wall_run_walls()
_build_spawn_points()
_build_decorations()
_build_player()
_build_ui()
func _box_static(pos: Vector3, size: Vector3, color: Color) -> StaticBody3D:
var body := StaticBody3D.new()
body.name = "Static_" + str(pos)
body.position = pos
add_child(body)
var shape := CollisionShape3D.new()
shape.shape = BoxShape3D.new()
shape.shape.size = size
body.add_child(shape)
var mesh := MeshInstance3D.new()
mesh.mesh = BoxMesh.new()
mesh.mesh.size = size
var mat := StandardMaterial3D.new()
mat.albedo_color = color
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
return body
# ── INPUT MAP ─────────────────────────────────────────────────────────────────
func _build_input_map() -> void:
var bindings := {
@@ -70,31 +69,530 @@ func _build_input_map() -> void:
InputMap.action_add_event(action, ev)
func _build_floor() -> void:
_box_static(Vector3(0, 0.0, 0), Vector3(30, 1.0, 30), Color(0.35, 0.25, 0.15))
# ── SKY ───────────────────────────────────────────────────────────────────────
func _build_sky() -> void:
var env := Environment.new()
env.background_mode = Environment.BG_COLOR
env.ambient_light_color = Color(0.15, 0.17, 0.22)
env.ambient_light_energy = 0.3
# Warm sunset-ish fog
env.fog_enabled = true
env.fog_light_color = Color(0.85, 0.75, 0.6)
env.fog_density = 0.008
env.fog_sky_affinity = 0.4
var world_env := WorldEnvironment.new()
world_env.name = "WorldEnvironment"
world_env.environment = env
add_child(world_env)
func _build_walls() -> void:
_box_static(Vector3(0, 3, -15), Vector3(30, 6, 0.4), Color(0.55, 0.45, 0.35))
_box_static(Vector3(15, 3, 0), Vector3(0.4, 6, 30), Color(0.55, 0.45, 0.35))
_box_static(Vector3(-15, 3, 0), Vector3(0.4, 6, 30), Color(0.55, 0.45, 0.35))
# ── LIGHTING ──────────────────────────────────────────────────────────────────
func _build_lighting() -> void:
# Main directional sun - warm afternoon light
var sun := DirectionalLight3D.new()
sun.name = "Sun"
sun.position = Vector3(0, 10, 0)
sun.rotation_degrees = Vector3(50, 0, 0)
sun.light_color = Color(1, 0.95, 0.85)
sun.light_energy = 1.1
sun.position = Vector3(20, 30, 10)
sun.rotation_degrees = Vector3(55, -25, 0)
sun.light_color = Color(1.0, 0.92, 0.78)
sun.light_energy = 1.4
sun.shadow_enabled = true
sun.shadow_opacity = 0.7
sun.directional_shadow_max_distance = 80.0
sun.directional_shadow_split_1 = 0.1
sun.directional_shadow_split_2 = 0.3
sun.directional_shadow_split_3 = 0.6
add_child(sun)
# Subtle fill light from opposite side
var fill := DirectionalLight3D.new()
fill.name = "FillLight"
fill.position = Vector3(-15, 10, -10)
fill.rotation_degrees = Vector3(30, 45, 0)
fill.light_color = Color(0.6, 0.7, 0.9)
fill.light_energy = 0.3
fill.shadow_enabled = false
add_child(fill)
# Point lights for spawn area warmth
_spawn_light(Vector3(-18, 3, -18), Color(1.0, 0.8, 0.5), 8.0, 1.2)
_spawn_light(Vector3(18, 3, 18), Color(0.5, 0.7, 1.0), 8.0, 1.2)
_spawn_light(Vector3(0, 5, 0), Color(1.0, 0.9, 0.7), 10.0, 0.8)
# Accent lights on platforms
_spawn_light(Vector3(-10, 6, 10), Color(0.9, 0.6, 0.3), 6.0, 0.6)
_spawn_light(Vector3(10, 6, -10), Color(0.3, 0.6, 0.9), 6.0, 0.6)
func _spawn_light(pos: Vector3, color: Color, radius: float, energy: float) -> void:
var light := OmniLight3D.new()
light.name = "PointLight_" + str(pos)
light.position = pos
light.light_color = color
light.light_energy = energy
light.omni_range = radius
light.shadow_enabled = true
light.shadow_opacity = 0.5
add_child(light)
# ── PRIMITIVE BUILDERS ────────────────────────────────────────────────────────
func _box_static(pos: Vector3, size: Vector3, color: Color) -> StaticBody3D:
var body := StaticBody3D.new()
body.name = "Static_" + str(pos)
body.position = pos
add_child(body)
var shape := CollisionShape3D.new()
shape.shape = BoxShape3D.new()
shape.shape.size = size
body.add_child(shape)
var mesh := MeshInstance3D.new()
mesh.mesh = BoxMesh.new()
mesh.mesh.size = size
var mat := StandardMaterial3D.new()
mat.albedo_color = color
mat.roughness = 0.7
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
return body
func _box_static_mat(pos: Vector3, size: Vector3, mat: StandardMaterial3D) -> StaticBody3D:
var body := StaticBody3D.new()
body.name = "Static_" + str(pos)
body.position = pos
add_child(body)
var shape := CollisionShape3D.new()
shape.shape = BoxShape3D.new()
shape.shape.size = size
body.add_child(shape)
var mesh := MeshInstance3D.new()
mesh.mesh = BoxMesh.new()
mesh.mesh.size = size
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
return body
func _ramp(pos: Vector3, size: Vector3, color: Color, rotation_y: float = 0.0) -> StaticBody3D:
var body := StaticBody3D.new()
body.name = "Ramp_" + str(pos)
body.position = pos
body.rotation.y = deg_to_rad(rotation_y)
add_child(body)
var shape := CollisionShape3D.new()
shape.shape = BoxShape3D.new()
shape.shape.size = size
body.add_child(shape)
var mesh := MeshInstance3D.new()
mesh.mesh = BoxMesh.new()
mesh.mesh.size = size
var mat := StandardMaterial3D.new()
mat.albedo_color = color
mat.roughness = 0.6
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
return body
func _ramp_mat(pos: Vector3, size: Vector3, mat: StandardMaterial3D, rotation_y: float = 0.0) -> StaticBody3D:
var body := StaticBody3D.new()
body.name = "Ramp_" + str(pos)
body.position = pos
body.rotation.y = deg_to_rad(rotation_y)
add_child(body)
var shape := CollisionShape3D.new()
shape.shape = BoxShape3D.new()
shape.shape.size = size
body.add_child(shape)
var mesh := MeshInstance3D.new()
mesh.mesh = BoxMesh.new()
mesh.mesh.size = size
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
return body
# ── MATERIALS ─────────────────────────────────────────────────────────────────
var mat_concrete: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.45, 0.43, 0.40)
m.roughness = 0.85
return m
var mat_dark_concrete: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.30, 0.28, 0.26)
m.roughness = 0.9
return m
var mat_metal: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.55, 0.58, 0.62)
m.roughness = 0.3
m.metallic = 0.7
return m
var mat_wall_run: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.25, 0.55, 0.75)
m.roughness = 0.25
m.metallic = 0.4
return m
var mat_wood: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.55, 0.38, 0.22)
m.roughness = 0.8
return m
var mat_crate: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.65, 0.50, 0.30)
m.roughness = 0.75
return m
var mat_accent_warm: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.85, 0.55, 0.25)
m.roughness = 0.5
return m
var mat_accent_cool: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.25, 0.55, 0.85)
m.roughness = 0.5
return m
var mat_spawn_red: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.75, 0.25, 0.25)
m.roughness = 0.4
return m
var mat_spawn_blue: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.25, 0.35, 0.75)
m.roughness = 0.4
return m
var mat_floor_tile: StandardMaterial3D:
get:
var m := StandardMaterial3D.new()
m.albedo_color = Color(0.38, 0.36, 0.34)
m.roughness = 0.8
return m
# ── FLOOR AND GROUND ─────────────────────────────────────────────────────────
func _build_floor_and_ground() -> void:
# Main ground floor - large tiled area
_box_static_mat(Vector3(0, -0.5, 0), Vector3(60, 1.0, 60), mat_floor_tile)
# Central raised platform (2 units high, 16x16)
_box_static_mat(Vector3(0, 1.0, 0), Vector3(16, 2.0, 16), mat_concrete)
# Central tower (4 units high, 6x6) - for vertical gameplay
_box_static_mat(Vector3(0, 3.0, 0), Vector3(6, 2.0, 6), mat_dark_concrete)
# Tower top platform
_box_static_mat(Vector3(0, 5.5, 0), Vector3(8, 1.0, 8), mat_metal)
# ── OUTER WALLS ───────────────────────────────────────────────────────────────
func _build_outer_walls() -> void:
var wall_height: float = 8.0
var wall_thick: float = 1.0
var map_half: float = 30.0
# North wall
_box_static_mat(Vector3(0, wall_height / 2.0, -map_half), Vector3(60, wall_height, wall_thick), mat_concrete)
# South wall
_box_static_mat(Vector3(0, wall_height / 2.0, map_half), Vector3(60, wall_height, wall_thick), mat_concrete)
# East wall
_box_static_mat(Vector3(map_half, wall_height / 2.0, 0), Vector3(wall_thick, wall_height, 60), mat_concrete)
# West wall
_box_static_mat(Vector3(-map_half, wall_height / 2.0, 0), Vector3(wall_thick, wall_height, 60), mat_concrete)
# ── RAMPS ─────────────────────────────────────────────────────────────────────
func _build_ramps() -> void:
# Ramps from ground to central platform (4 cardinal directions)
# North ramp
_ramp_mat(Vector3(0, 0.5, -11), Vector3(6, 0.5, 6), mat_metal, 0)
# South ramp
_ramp_mat(Vector3(0, 0.5, 11), Vector3(6, 0.5, 6), mat_metal, 0)
# East ramp
_ramp_mat(Vector3(11, 0.5, 0), Vector3(6, 0.5, 6), mat_metal, 0)
# West ramp
_ramp_mat(Vector3(-11, 0.5, 0), Vector3(6, 0.5, 6), mat_metal, 0)
# Corner ramps to elevated platforms
_ramp_mat(Vector3(-18, 0.5, -18), Vector3(4, 0.5, 8), mat_wood, 45)
_ramp_mat(Vector3(18, 0.5, 18), Vector3(4, 0.5, 8), mat_wood, 45)
_ramp_mat(Vector3(-18, 0.5, 18), Vector3(4, 0.5, 8), mat_wood, -45)
_ramp_mat(Vector3(18, 0.5, -18), Vector3(4, 0.5, 8), mat_wood, -45)
# ── PLATFORMS ─────────────────────────────────────────────────────────────────
func _build_platforms() -> void:
# Corner elevated platforms (3 units high, 8x8)
# NW corner
_box_static_mat(Vector3(-18, 2.0, -18), Vector3(8, 0.5, 8), mat_dark_concrete)
# NE corner
_box_static_mat(Vector3(18, 2.0, -18), Vector3(8, 0.5, 8), mat_dark_concrete)
# SW corner
_box_static_mat(Vector3(-18, 2.0, 18), Vector3(8, 0.5, 8), mat_dark_concrete)
# SE corner
_box_static_mat(Vector3(18, 2.0, 18), Vector3(8, 0.5, 8), mat_dark_concrete)
# Mid-side elevated walkways (2 units high, connecting central to edges)
# North walkway
_box_static_mat(Vector3(0, 1.5, -22), Vector3(4, 0.5, 12), mat_metal)
# South walkway
_box_static_mat(Vector3(0, 1.5, 22), Vector3(4, 0.5, 12), mat_metal)
# East walkway
_box_static_mat(Vector3(22, 1.5, 0), Vector3(12, 0.5, 4), mat_metal)
# West walkway
_box_static_mat(Vector3(-22, 1.5, 0), Vector3(12, 0.5, 4), mat_metal)
# Small stepping platforms for parkour
_box_static_mat(Vector3(-10, 1.0, -10), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(-13, 2.0, -13), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(-16, 3.0, -16), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(10, 1.0, 10), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(13, 2.0, 13), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(16, 3.0, 16), Vector3(3, 0.5, 3), mat_wood)
# Diagonal stepping stones
_box_static_mat(Vector3(10, 1.0, -10), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(13, 2.0, -13), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(-10, 1.0, 10), Vector3(3, 0.5, 3), mat_wood)
_box_static_mat(Vector3(-13, 2.0, 13), Vector3(3, 0.5, 3), mat_wood)
# ── COVER ─────────────────────────────────────────────────────────────────────
func _build_cover() -> void:
# Low walls (1.5 units high) for crouch cover - arranged in lanes
# Central area cover
_box_static(Vector3(-5, 0.75, -5), Vector3(4, 1.5, 0.5), Color(0.4, 0.38, 0.35))
_box_static(Vector3(5, 0.75, 5), Vector3(4, 1.5, 0.5), Color(0.4, 0.38, 0.35))
_box_static(Vector3(-5, 0.75, 5), Vector3(0.5, 1.5, 4), Color(0.4, 0.38, 0.35))
_box_static(Vector3(5, 0.75, -5), Vector3(0.5, 1.5, 4), Color(0.4, 0.38, 0.35))
# Crate clusters for cover
_crate(Vector3(-8, 0.6, 0))
_crate(Vector3(-8, 0.6, 1.3))
_crate(Vector3(-8, 1.2, 0.65))
_crate(Vector3(8, 0.6, 0))
_crate(Vector3(8, 0.6, -1.3))
_crate(Vector3(8, 1.2, -0.65))
_crate(Vector3(0, 0.6, -8))
_crate(Vector3(1.3, 0.6, -8))
_crate(Vector3(0, 0.6, 8))
_crate(Vector3(-1.3, 0.6, 8))
# Edge cover walls
_box_static(Vector3(-24, 1.0, -10), Vector3(0.5, 2.0, 6), Color(0.35, 0.33, 0.30))
_box_static(Vector3(-24, 1.0, 10), Vector3(0.5, 2.0, 6), Color(0.35, 0.33, 0.30))
_box_static(Vector3(24, 1.0, -10), Vector3(0.5, 2.0, 6), Color(0.35, 0.33, 0.30))
_box_static(Vector3(24, 1.0, 10), Vector3(0.5, 2.0, 6), Color(0.35, 0.33, 0.30))
# Mid-map corridor walls (create lanes)
_box_static(Vector3(-15, 1.5, -5), Vector3(12, 3.0, 0.4), Color(0.38, 0.36, 0.33))
_box_static(Vector3(15, 1.5, 5), Vector3(12, 3.0, 0.4), Color(0.38, 0.36, 0.33))
func _crate(pos: Vector3) -> void:
_box_static_mat(pos, Vector3(1.2, 1.2, 1.2), mat_crate)
# ── PILLARS ───────────────────────────────────────────────────────────────────
func _build_pillars() -> void:
# Structural pillars around central area
_pillar(Vector3(-10, 2.5, -10))
_pillar(Vector3(10, 2.5, -10))
_pillar(Vector3(-10, 2.5, 10))
_pillar(Vector3(10, 2.5, 10))
# Edge pillars
_pillar(Vector3(-20, 2.5, 0))
_pillar(Vector3(20, 2.5, 0))
_pillar(Vector3(0, 2.5, -20))
_pillar(Vector3(0, 2.5, 20))
func _pillar(pos: Vector3) -> void:
_box_static_mat(pos, Vector3(0.8, 5.0, 0.8), mat_metal)
# ── WALL-RUN SURFACES ────────────────────────────────────────────────────────
func _build_wall_run_walls() -> void:
# Dedicated wall-run sections - smooth, colored walls
# North wall-run section (interior face)
_box_static_mat(Vector3(-8, 4.0, -14.5), Vector3(8, 8.0, 0.5), mat_wall_run)
_box_static_mat(Vector3(8, 4.0, -14.5), Vector3(8, 8.0, 0.5), mat_wall_run)
# South wall-run section
_box_static_mat(Vector3(-8, 4.0, 14.5), Vector3(8, 8.0, 0.5), mat_wall_run)
_box_static_mat(Vector3(8, 4.0, 14.5), Vector3(8, 8.0, 0.5), mat_wall_run)
# East wall-run section
_box_static_mat(Vector3(14.5, 4.0, -8), Vector3(0.5, 8.0, 8), mat_wall_run)
_box_static_mat(Vector3(14.5, 4.0, 8), Vector3(0.5, 8.0, 8), mat_wall_run)
# West wall-run section
_box_static_mat(Vector3(-14.5, 4.0, -8), Vector3(0.5, 8.0, 8), mat_wall_run)
_box_static_mat(Vector3(-14.5, 4.0, 8), Vector3(0.5, 8.0, 8), mat_wall_run)
# Inner wall-run walls (around central tower)
_box_static_mat(Vector3(-5, 3.0, -3.5), Vector3(0.5, 4.0, 3), mat_wall_run)
_box_static_mat(Vector3(5, 3.0, 3.5), Vector3(0.5, 4.0, 3), mat_wall_run)
_box_static_mat(Vector3(-3.5, 3.0, 5), Vector3(3, 4.0, 0.5), mat_wall_run)
_box_static_mat(Vector3(3.5, 3.0, -5), Vector3(3, 4.0, 0.5), mat_wall_run)
# ── SPAWN POINTS ──────────────────────────────────────────────────────────────
# 6 spawn points spread across the map, each in a covered alcove with
# multiple exit routes to prevent spawn camping.
func _build_spawn_points() -> void:
# Spawn 1: NW corner - red team, alcove with 2 exits
_spawn_alcove(Vector3(-25, 0.0, -25), mat_spawn_red, "Spawn_Red_1")
_spawn_cover_wall(Vector3(-23, 1.0, -27), Vector3(4, 2.0, 0.3), Color(0.5, 0.2, 0.2))
_spawn_cover_wall(Vector3(-27, 1.0, -23), Vector3(0.3, 2.0, 4), Color(0.5, 0.2, 0.2))
# Spawn 2: SW corner - red team, alcove with 2 exits
_spawn_alcove(Vector3(-25, 0.0, 25), mat_spawn_red, "Spawn_Red_2")
_spawn_cover_wall(Vector3(-23, 1.0, 27), Vector3(4, 2.0, 0.3), Color(0.5, 0.2, 0.2))
_spawn_cover_wall(Vector3(-27, 1.0, 23), Vector3(0.3, 2.0, 4), Color(0.5, 0.2, 0.2))
# Spawn 3: West mid - red team, covered nook
_spawn_alcove(Vector3(-27, 0.0, 0), mat_spawn_red, "Spawn_Red_3")
_spawn_cover_wall(Vector3(-25, 1.0, -2), Vector3(0.3, 2.0, 4), Color(0.5, 0.2, 0.2))
_spawn_cover_wall(Vector3(-25, 1.0, 2), Vector3(0.3, 2.0, 4), Color(0.5, 0.2, 0.2))
# Spawn 4: NE corner - blue team, alcove with 2 exits
_spawn_alcove(Vector3(25, 0.0, -25), mat_spawn_blue, "Spawn_Blue_1")
_spawn_cover_wall(Vector3(23, 1.0, -27), Vector3(4, 2.0, 0.3), Color(0.2, 0.3, 0.5))
_spawn_cover_wall(Vector3(27, 1.0, -23), Vector3(0.3, 2.0, 4), Color(0.2, 0.3, 0.5))
# Spawn 5: SE corner - blue team, alcove with 2 exits
_spawn_alcove(Vector3(25, 0.0, 25), mat_spawn_blue, "Spawn_Blue_2")
_spawn_cover_wall(Vector3(23, 1.0, 27), Vector3(4, 2.0, 0.3), Color(0.2, 0.3, 0.5))
_spawn_cover_wall(Vector3(27, 1.0, 23), Vector3(0.3, 2.0, 4), Color(0.2, 0.3, 0.5))
# Spawn 6: East mid - blue team, covered nook
_spawn_alcove(Vector3(27, 0.0, 0), mat_spawn_blue, "Spawn_Blue_3")
_spawn_cover_wall(Vector3(25, 1.0, -2), Vector3(0.3, 2.0, 4), Color(0.2, 0.3, 0.5))
_spawn_cover_wall(Vector3(25, 1.0, 2), Vector3(0.3, 2.0, 4), Color(0.2, 0.3, 0.5))
func _spawn_alcove(pos: Vector3, mat: StandardMaterial3D, label: String) -> void:
# Floor marker for spawn point
var body := StaticBody3D.new()
body.name = label
body.position = pos
add_child(body)
var mesh := MeshInstance3D.new()
mesh.mesh = BoxMesh.new()
mesh.mesh.size = Vector3(3, 0.1, 3)
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
# No collision on the marker - it's just visual
func _spawn_cover_wall(pos: Vector3, size: Vector3, color: Color) -> void:
_box_static(pos, size, color)
# ── DECORATIONS ───────────────────────────────────────────────────────────────
func _build_decorations() -> void:
# Accent beams on outer walls
for i in range(-4, 5):
var z_pos: float = i * 6.0
_box_static_mat(Vector3(-29.5, 6.0, z_pos), Vector3(0.3, 0.3, 4.0), mat_accent_warm)
_box_static_mat(Vector3(29.5, 6.0, z_pos), Vector3(0.3, 0.3, 4.0), mat_accent_cool)
# Hazard stripes on ramp edges
for i in range(4):
var offset: float = -2.0 + i * 1.3
_box_static_mat(Vector3(offset, 0.05, -14.8), Vector3(0.8, 0.1, 0.3), mat_accent_warm)
_box_static_mat(Vector3(offset, 0.05, 14.8), Vector3(0.8, 0.1, 0.3), mat_accent_warm)
# Elevated platform edge trim
_box_static_mat(Vector3(-18, 2.5, -22), Vector3(8, 0.15, 0.15), mat_accent_cool)
_box_static_mat(Vector3(-18, 2.5, -14), Vector3(8, 0.15, 0.15), mat_accent_cool)
_box_static_mat(Vector3(-22, 2.5, -18), Vector3(0.15, 0.15, 8), mat_accent_cool)
_box_static_mat(Vector3(-14, 2.5, -18), Vector3(0.15, 0.15, 8), mat_accent_cool)
_box_static_mat(Vector3(18, 2.5, 22), Vector3(8, 0.15, 0.15), mat_accent_warm)
_box_static_mat(Vector3(18, 2.5, 14), Vector3(8, 0.15, 0.15), mat_accent_warm)
_box_static_mat(Vector3(22, 2.5, 18), Vector3(0.15, 0.15, 8), mat_accent_warm)
_box_static_mat(Vector3(14, 2.5, 18), Vector3(0.15, 0.15, 8), mat_accent_warm)
# Small detail: barrels
_barrel(Vector3(-20, 0.5, -5))
_barrel(Vector3(-20, 0.5, -6.5))
_barrel(Vector3(20, 0.5, 5))
_barrel(Vector3(20, 0.5, 6.5))
_barrel(Vector3(5, 0.5, -20))
_barrel(Vector3(-5, 0.5, 20))
func _barrel(pos: Vector3) -> void:
var body := StaticBody3D.new()
body.name = "Barrel_" + str(pos)
body.position = pos
add_child(body)
var mesh := MeshInstance3D.new()
mesh.mesh = CylinderMesh.new()
mesh.mesh.top_radius = 0.4
mesh.mesh.bottom_radius = 0.4
mesh.mesh.height = 1.0
var mat := StandardMaterial3D.new()
mat.albedo_color = Color(0.5, 0.45, 0.3)
mat.roughness = 0.6
mesh.mesh.surface_set_material(0, mat)
body.add_child(mesh)
var shape := CollisionShape3D.new()
shape.shape = CylinderShape3D.new()
shape.shape.radius = 0.4
shape.shape.height = 1.0
body.add_child(shape)
# ── PLAYER ────────────────────────────────────────────────────────────────────
func _build_player() -> void:
var player := CharacterBody3D.new()
player.name = "Player"
player.position = Vector3(0, 1.4, 8)
player.position = Vector3(0, 3.0, 10)
add_child(player)
var shape := CollisionShape3D.new()
shape.shape = CapsuleShape3D.new()
@@ -146,10 +644,13 @@ func _build_player() -> void:
sm.set_physics_process(true)
# ── UI ────────────────────────────────────────────────────────────────────────
func _build_ui() -> void:
var canvas := CanvasLayer.new()
canvas.name = "UI"
add_child(canvas)
var speed := Label.new()
speed.name = "SpeedLabel"
speed.offset_left = 16
@@ -158,11 +659,21 @@ func _build_ui() -> void:
speed.offset_bottom = 48
speed.text = "Speed: 0.0 m/s | State: --- | Chain: +0%"
canvas.add_child(speed)
var help := Label.new()
help.name = "HelpLabel"
help.offset_left = 16
help.offset_top = 52
help.offset_right = 720
help.offset_right = 900
help.offset_bottom = 108
help.text = "WASD Move | Sprint=Shift | Slide=Ctrl+S | Double-Jump=Space twice | Dash=LShift | Wall-Jump=Jump at wall"
help.text = "WASD Move | Sprint=Shift | Slide=Ctrl+S | Double-Jump=Space | Dash=LShift | Wall-Jump=Jump at wall"
canvas.add_child(help)
var map_info := Label.new()
map_info.name = "MapInfoLabel"
map_info.offset_left = 16
map_info.offset_top = 90
map_info.offset_right = 700
map_info.offset_bottom = 146
map_info.text = "Movement Map v2 | 6 Spawns (R/B) | Wall-Run: Blue surfaces | Ramps: Metal | Cover: Crates+Walls"
canvas.add_child(map_info)
-19
View File
@@ -36,25 +36,6 @@ func update(delta: float) -> void:
vel.x = hvel.x
vel.z = hvel.z
# Bunny hop: if jump pressed and on ground near-peak of arc → extra boost
if machine.input_jump_just_pressed and machine.on_ground:
vel.y = params.jump_velocity * params.bunny_hop_impulse
machine.player.velocity = vel
machine.register_chain_mechanic("bunny_hop")
machine.current_jump_count += 1
machine.on_ground = false
machine.switch_to("air")
return
# Double Jump
if machine.input_jump_just_pressed and not machine.on_ground and machine.current_jump_count < params.double_jump_max_count:
vel.y = params.double_jump_velocity
machine.player.velocity = vel
machine.current_jump_count += 1
machine.register_chain_mechanic("double_jump")
machine.switch_to("air")
return
machine.player.velocity = vel
machine.player.move_and_slide()
+2 -1
View File
@@ -73,7 +73,8 @@ func _fresh() -> void:
sm.input_dash = false
fake_player.velocity = Vector3.ZERO
for c in sm.get_children():
sm.remove_child(c.queue_free())
sm.remove_child(c)
c.queue_free()
func _expect(cond: bool, msg: String) -> void: