feat(map): high-quality movement map with spawns, cover, and wall-run surfaces #18
+547
-36
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user