mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
feat(arena): add elevated-goal arena variants
Each of the three arenas gains an ELEVATED sibling scene that inherits the base arena and overrides the boundary's goal_mode, the two goal transforms and the ship spawns, so the goal sits at mid-wall height instead of flush with the deck. Registered in ArenaRegistry alongside the floor-level arenas, plus a training_elevated scene for self-play on them. TrainingMode reads the arena's goal_mode once in _start() and widens the ball-placement height range to match the goal's real position; on FLOOR arenas the bound is a no-op, so floor-level training is unchanged. It is read in _start() rather than _ready() because TrainingMode has no _ready() override and GameMode._ready() is what discovers the arena first. Policies trained against floor-level goals are not expected to score on an elevated one, so the two are kept as separate arenas rather than a variant of the same entry.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/arena_01.tscn" id="1_base"]
|
||||
|
||||
[node name="Arena" instance=ExtResource("1_base")]
|
||||
|
||||
[node name="Boundary" parent="." index="0"]
|
||||
goal_mode = 1
|
||||
|
||||
[node name="GoalTeam0" parent="." index="5"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.0, 18)
|
||||
|
||||
[node name="GoalTeam1" parent="." index="6"]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 6.0, -18)
|
||||
|
||||
[node name="Spawn1" parent="SpawnsTeam0" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.0, 2.8, 13.5)
|
||||
|
||||
[node name="Spawn1" parent="SpawnsTeam1" index="0"]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -4.0, 2.8, -13.5)
|
||||
@@ -0,0 +1,20 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/arena_02.tscn" id="1_base"]
|
||||
|
||||
[node name="Arena" instance=ExtResource("1_base")]
|
||||
|
||||
[node name="Boundary" parent="." index="0"]
|
||||
goal_mode = 1
|
||||
|
||||
[node name="GoalTeam0" parent="." index="5"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.0, 18)
|
||||
|
||||
[node name="GoalTeam1" parent="." index="6"]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 6.0, -18)
|
||||
|
||||
[node name="Spawn1" parent="SpawnsTeam0" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.0, 2.8, 13.5)
|
||||
|
||||
[node name="Spawn1" parent="SpawnsTeam1" index="0"]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -4.0, 2.8, -13.5)
|
||||
@@ -0,0 +1,20 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/arena_03.tscn" id="1_base"]
|
||||
|
||||
[node name="Arena" instance=ExtResource("1_base")]
|
||||
|
||||
[node name="Boundary" parent="." index="0"]
|
||||
goal_mode = 1
|
||||
|
||||
[node name="GoalTeam0" parent="." index="5"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.0, 18)
|
||||
|
||||
[node name="GoalTeam1" parent="." index="6"]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 6.0, -18)
|
||||
|
||||
[node name="Spawn1" parent="SpawnsTeam0" index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.0, 2.8, 13.5)
|
||||
|
||||
[node name="Spawn1" parent="SpawnsTeam1" index="0"]
|
||||
transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -4.0, 2.8, -13.5)
|
||||
@@ -0,0 +1,15 @@
|
||||
[gd_scene load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/training_mode.gd" id="1_tr"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/arena_01_elevated.tscn" id="2_tr"]
|
||||
[ext_resource type="Script" path="res://addons/godot_rl_agents/sync.gd" id="3_tr"]
|
||||
|
||||
[node name="Training" type="Node3D"]
|
||||
script = ExtResource("1_tr")
|
||||
|
||||
[node name="Arena" parent="." instance=ExtResource("2_tr")]
|
||||
|
||||
[node name="Sync" type="Node" parent="."]
|
||||
script = ExtResource("3_tr")
|
||||
action_repeat = 8
|
||||
speed_up = 8.0
|
||||
@@ -1,14 +1,26 @@
|
||||
class_name ArenaRegistry
|
||||
|
||||
# Single source of truth for available arenas: the Free Play menu dropdown
|
||||
# lists these, and Match/Spectate pick one at random each session. Training
|
||||
# is exempt — training.tscn keeps its own fixed arena_01 child.
|
||||
# lists all of these, and Match/Spectate pick at random each session from
|
||||
# those with "random" true. Training is exempt — training.tscn/
|
||||
# training_elevated.tscn each keep their own fixed Arena child.
|
||||
#
|
||||
# Elevated-goal variants are Free-Play-only ("random": false) until a
|
||||
# checkpoint trained on training_elevated.tscn is promoted — the current
|
||||
# promoted bots (see main_menu.gd's DIFFICULTIES) were trained exclusively
|
||||
# on floor-level goals and can't be expected to score on an elevated one.
|
||||
# Flip a variant's "random" flag to true once that training/promotion has
|
||||
# happened.
|
||||
const ARENAS := [
|
||||
{"name": "Starfield", "path": "res://scenes/arena_01.tscn"},
|
||||
{"name": "Nebula", "path": "res://scenes/arena_02.tscn"},
|
||||
{"name": "Asteroid Field", "path": "res://scenes/arena_03.tscn"},
|
||||
{"name": "Starfield (Floor Goals)", "path": "res://scenes/arena_01.tscn", "random": true},
|
||||
{"name": "Starfield (Elevated Goals)", "path": "res://scenes/arena_01_elevated.tscn", "random": false},
|
||||
{"name": "Nebula (Floor Goals)", "path": "res://scenes/arena_02.tscn", "random": true},
|
||||
{"name": "Nebula (Elevated Goals)", "path": "res://scenes/arena_02_elevated.tscn", "random": false},
|
||||
{"name": "Asteroid Field (Floor Goals)", "path": "res://scenes/arena_03.tscn", "random": true},
|
||||
{"name": "Asteroid Field (Elevated Goals)", "path": "res://scenes/arena_03_elevated.tscn", "random": false},
|
||||
]
|
||||
|
||||
|
||||
static func random_path() -> String:
|
||||
return ARENAS[randi() % ARENAS.size()]["path"]
|
||||
var candidates := ARENAS.filter(func(arena): return arena["random"])
|
||||
return candidates[randi() % candidates.size()]["path"]
|
||||
|
||||
@@ -120,8 +120,16 @@ var _ai_overrides := {}
|
||||
# arena spawn instead of drifting into the play area as a stray obstacle.
|
||||
var _inert_ships: Array[Ship] = []
|
||||
|
||||
# Whether this run's arena is an ELEVATED-goal variant (see
|
||||
# ArenaBoundary.GoalMode) — read once _start() runs so _place_ball_near_goal
|
||||
# can sample a height range matching the goal's real position. Read here
|
||||
# rather than _ready(): TrainingMode has no _ready() override, and
|
||||
# GameMode._ready() is what discovers `arena` before calling _start().
|
||||
var _elevated := false
|
||||
|
||||
|
||||
func _start() -> void:
|
||||
_elevated = (arena.get_node("Boundary") as ArenaBoundary).goal_mode == ArenaBoundary.GoalMode.ELEVATED
|
||||
_parse_eval_args()
|
||||
_parse_curriculum_args()
|
||||
spawn_ball()
|
||||
@@ -371,9 +379,12 @@ func _place_ball_random() -> void:
|
||||
func _place_ball_near_goal() -> void:
|
||||
var goal := _goal_for_team(0) if randf() < attack_goal_bias else _goal_for_team(1)
|
||||
var toward_centre := -signf(goal.global_position.z)
|
||||
# Upper Y bound is a no-op on FLOOR arenas (goal.global_position.y ~0.79,
|
||||
# so maxf(4.0, ...) stays 4.0); on ELEVATED arenas it widens to sample
|
||||
# near the goal's real height instead of always landing near the floor.
|
||||
var position := Vector3(
|
||||
randf_range(-4.0, 4.0),
|
||||
randf_range(FIELD_MIN_Y, 4.0),
|
||||
randf_range(FIELD_MIN_Y, maxf(4.0, goal.global_position.y + 2.0)),
|
||||
goal.global_position.z + toward_centre * randf_range(3.0, 6.0)
|
||||
)
|
||||
var to_goal := (goal.global_position - position).normalized()
|
||||
|
||||
Reference in New Issue
Block a user