feat: add Nebula and Asteroid Field arenas

Introduce arena_02 (Nebula) and arena_03 (Asteroid Field) alongside
arena_01, listed in a new ArenaRegistry (scripts/arena_registry.gd) as
the single source of truth for available arenas. Free Play lets the
player pick an arena from the main menu; Match/Spectate each pick one
at random per session; Training keeps its own fixed arena_01.

Nebula gets an RL-style visual treatment: a near-invisible glass
boundary material shared by all arenas, a baked equirect nebula sky
texture, a drifting-dust particle system, and a Blender-modeled
station/debris/planet decoration set. GameMode gains a
_get_arena_scene_path() hook so modes can instantiate their arena in
code instead of hardcoding it in the scene.
This commit is contained in:
Josh Creek
2026-08-03 19:09:16 +01:00
parent 1af3e0410d
commit 171cd4a840
35 changed files with 683 additions and 12 deletions
+142
View File
@@ -0,0 +1,142 @@
[gd_scene load_steps=15 format=3]
[ext_resource type="Script" path="res://scripts/arena.gd" id="1_iywne"]
[ext_resource type="PackedScene" path="res://objects/arena_boundary.tscn" id="2_bndry"]
[ext_resource type="PackedScene" uid="uid://cofdcxo5170rs" path="res://objects/goal.tscn" id="6_p57ef"]
[ext_resource type="Texture2D" path="res://assets/textures/sky_nebula.png" id="7_nebula"]
[ext_resource type="PackedScene" path="res://assets/models/nebula_station.glb" id="8_station"]
[ext_resource type="PackedScene" path="res://assets/models/nebula_debris.glb" id="9_debris"]
[ext_resource type="Texture2D" path="res://assets/textures/particle_glow.png" id="10_dust"]
[ext_resource type="PackedScene" path="res://assets/models/nebula_planet.glb" id="11_planet"]
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_nebula"]
panorama = ExtResource("7_nebula")
[sub_resource type="Sky" id="Sky_nebula"]
sky_material = SubResource("PanoramaSkyMaterial_nebula")
[sub_resource type="Environment" id="Environment_nebula"]
background_mode = 2
sky = SubResource("Sky_nebula")
ambient_light_source = 2
ambient_light_color = Color(0.65, 0.35, 0.75, 1)
ambient_light_energy = 0.45
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dust"]
transparency = 1
shading_mode = 0
billboard_mode = 1
vertex_color_use_as_albedo = true
albedo_color = Color(1, 0.75, 0.9, 1)
albedo_texture = ExtResource("10_dust")
emission_enabled = true
emission = Color(1, 0.6, 0.85, 1)
emission_energy_multiplier = 2.5
[sub_resource type="QuadMesh" id="QuadMesh_dust"]
material = SubResource("StandardMaterial3D_dust")
size = Vector2(0.4, 0.4)
[sub_resource type="Gradient" id="Gradient_dust"]
offsets = PackedFloat32Array(0, 0.15, 0.85, 1)
colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 0.55, 1, 1, 1, 0.55, 1, 1, 1, 0)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_dust"]
gradient = SubResource("Gradient_dust")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_dust"]
emission_shape = 3
emission_box_extents = Vector3(26, 11, 28)
direction = Vector3(0, 1, 0)
spread = 180.0
initial_velocity_min = 0.1
initial_velocity_max = 0.45
gravity = Vector3(0, 0, 0)
turbulence_enabled = true
turbulence_noise_strength = 0.7
turbulence_noise_scale = 0.6
turbulence_influence_min = 0.35
turbulence_influence_max = 0.65
scale_min = 0.5
scale_max = 1.6
color_ramp = SubResource("GradientTexture1D_dust")
[node name="Arena" type="Node3D"]
script = ExtResource("1_iywne")
[node name="Boundary" parent="." instance=ExtResource("2_bndry")]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.866025, -0.383022, 0.321394, 0, 0.642788, 0.766044, -0.5, -0.663414, 0.55667, 0, 11, 0)
light_color = Color(0.85, 0.75, 1, 1)
shadow_enabled = true
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_nebula")
[node name="GoalTeam0" parent="." instance=ExtResource("6_p57ef")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.79, 18)
[node name="GoalTeam1" parent="." instance=ExtResource("6_p57ef")]
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.79, -18)
team = 1
[node name="BallSpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
[node name="SpawnsTeam0" type="Node3D" parent="."]
[node name="Spawn1" type="Marker3D" parent="SpawnsTeam0"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.8, 13.5)
[node name="SpawnsTeam1" type="Node3D" parent="."]
[node name="Spawn1" type="Marker3D" parent="SpawnsTeam1"]
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 2.8, -13.5)
[node name="NebulaDust" type="GPUParticles3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, 0)
amount = 500
lifetime = 22.0
preprocess = 8.0
randomness = 1.0
visibility_aabb = AABB(-30, -14, -32, 60, 28, 64)
process_material = SubResource("ParticleProcessMaterial_dust")
draw_pass_1 = SubResource("QuadMesh_dust")
[node name="Decoration" type="Node3D" parent="."]
[node name="NebulaStationA" parent="Decoration" instance=ExtResource("8_station")]
transform = Transform3D(1.8, 0, 0, 0, 1.8, 0, 0, 0, 1.8, 28, 5, -6)
rotation = Vector3(0, -0.436332, 0)
[node name="NebulaDebrisA" parent="Decoration" instance=ExtResource("9_debris")]
transform = Transform3D(1.2, 0, 0, 0, 1.2, 0, 0, 0, 1.2, -22, 9, 11)
rotation = Vector3(0.261799, 0.698132, 0.087266)
[node name="NebulaDebrisB" parent="Decoration" instance=ExtResource("9_debris")]
transform = Transform3D(0.8, 0, 0, 0, 0.8, 0, 0, 0, 0.8, 34, 3, 15)
rotation = Vector3(-0.174533, 1.919862, 0.349066)
[node name="NebulaDebrisC" parent="Decoration" instance=ExtResource("9_debris")]
transform = Transform3D(1.5, 0, 0, 0, 1.5, 0, 0, 0, 1.5, -30, 11, -21)
rotation = Vector3(0.087266, -1.047198, -0.261799)
[node name="NebulaStationB" parent="Decoration" instance=ExtResource("8_station")]
transform = Transform3D(1.5, 0, 0, 0, 1.5, 0, 0, 0, 1.5, -32, 7, 9)
rotation = Vector3(0.05, 1.134464, -0.02)
[node name="NebulaStationC" parent="Decoration" instance=ExtResource("8_station")]
transform = Transform3D(1.3, 0, 0, 0, 1.3, 0, 0, 0, 1.3, 9, 8, 42)
rotation = Vector3(-0.03, 2.740167, 0.06)
[node name="NebulaDebrisD" parent="Decoration" instance=ExtResource("9_debris")]
transform = Transform3D(1.0, 0, 0, 0, 1.0, 0, 0, 0, 1.0, 13, 15, -35)
rotation = Vector3(0.4, 1.3, -0.2)
[node name="NebulaDebrisE" parent="Decoration" instance=ExtResource("9_debris")]
transform = Transform3D(0.9, 0, 0, 0, 0.9, 0, 0, 0, 0.9, -11, 4, 33)
rotation = Vector3(-0.3, -0.9, 0.15)
[node name="NebulaPlanet" parent="Decoration" instance=ExtResource("11_planet")]
transform = Transform3D(30, 0, 0, 0, 30, 0, 0, 0, 30, -55, 38, -80)
+54
View File
@@ -0,0 +1,54 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://scripts/arena.gd" id="1_iywne"]
[ext_resource type="PackedScene" path="res://objects/arena_boundary.tscn" id="2_bndry"]
[ext_resource type="PackedScene" uid="uid://cofdcxo5170rs" path="res://objects/goal.tscn" id="6_p57ef"]
[ext_resource type="Shader" path="res://shaders/sky_asteroid_field.gdshader" id="7_asteroid"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_asteroid"]
shader = ExtResource("7_asteroid")
[sub_resource type="Sky" id="Sky_asteroid"]
sky_material = SubResource("ShaderMaterial_asteroid")
[sub_resource type="Environment" id="Environment_asteroid"]
background_mode = 2
sky = SubResource("Sky_asteroid")
ambient_light_source = 2
ambient_light_color = Color(0.6, 0.45, 0.3, 1)
ambient_light_energy = 0.3
[node name="Arena" type="Node3D"]
script = ExtResource("1_iywne")
[node name="Boundary" parent="." instance=ExtResource("2_bndry")]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.866025, -0.383022, 0.321394, 0, 0.642788, 0.766044, -0.5, -0.663414, 0.55667, 0, 11, 0)
light_color = Color(1, 0.85, 0.65, 1)
shadow_enabled = true
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_asteroid")
[node name="GoalTeam0" parent="." instance=ExtResource("6_p57ef")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.79, 18)
[node name="GoalTeam1" parent="." instance=ExtResource("6_p57ef")]
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.79, -18)
team = 1
[node name="BallSpawn" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
[node name="SpawnsTeam0" type="Node3D" parent="."]
[node name="Spawn1" type="Marker3D" parent="SpawnsTeam0"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.8, 13.5)
[node name="SpawnsTeam1" type="Node3D" parent="."]
[node name="Spawn1" type="Marker3D" parent="SpawnsTeam1"]
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 2.8, -13.5)
[node name="Decoration" type="Node3D" parent="."]
+1 -3
View File
@@ -1,12 +1,10 @@
[gd_scene load_steps=4 format=3]
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://scripts/free_play.gd" id="1_fp"]
[ext_resource type="PackedScene" path="res://scenes/arena_01.tscn" id="2_fp"]
[ext_resource type="PackedScene" uid="uid://c8kak2l3m4n5" path="res://scenes/HUD.tscn" id="3_fp"]
[node name="FreePlay" type="Node3D"]
script = ExtResource("1_fp")
[node name="Arena" parent="." instance=ExtResource("2_fp")]
[node name="HUD" parent="." instance=ExtResource("3_fp")]
+14
View File
@@ -53,6 +53,20 @@ theme_override_font_sizes/font_size = 13
text = "Solo practice — no timer, R resets the ball"
horizontal_alignment = 1
[node name="ArenaRow" type="HBoxContainer" parent="CenterContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ArenaLabel" type="Label" parent="CenterContainer/VBoxContainer/ArenaRow"]
layout_mode = 2
text = "Arena"
[node name="ArenaDropdown" type="OptionButton" parent="CenterContainer/VBoxContainer/ArenaRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
size_flags_horizontal = 3
[node name="MatchSeparator" type="HSeparator" parent="CenterContainer/VBoxContainer"]
layout_mode = 2
+1 -3
View File
@@ -1,13 +1,11 @@
[gd_scene load_steps=4 format=3]
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://scripts/match_mode.gd" id="1_m"]
[ext_resource type="PackedScene" path="res://scenes/arena_01.tscn" id="2_m"]
[ext_resource type="PackedScene" uid="uid://c8kak2l3m4n5" path="res://scenes/HUD.tscn" id="3_m"]
[node name="Match" type="Node3D"]
script = ExtResource("1_m")
bot_model_path = "res://bots/promoted/easy.json"
[node name="Arena" parent="." instance=ExtResource("2_m")]
[node name="HUD" parent="." instance=ExtResource("3_m")]
+1 -3
View File
@@ -1,7 +1,6 @@
[gd_scene load_steps=4 format=3]
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://scripts/spectate_mode.gd" id="1_s"]
[ext_resource type="PackedScene" path="res://scenes/arena_01.tscn" id="2_s"]
[ext_resource type="PackedScene" uid="uid://c8kak2l3m4n5" path="res://scenes/HUD.tscn" id="3_s"]
[node name="Spectate" type="Node3D"]
@@ -9,6 +8,5 @@ script = ExtResource("1_s")
bot_a_model_path = "res://bots/promoted/easy.json"
bot_b_model_path = "res://bots/promoted/easy.json"
[node name="Arena" parent="." instance=ExtResource("2_s")]
[node name="HUD" parent="." instance=ExtResource("3_s")]