mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-12 16:13:44 +00:00
refactor(*): DRY up arena scenes, game modes, and HUD instruments
Arenas inherit from a new arena_base.tscn instead of restating ~40 shared lines each; only sky/ambient/glow/tint/decoration vary, exposed via new Arena exports since nested Environment properties aren't overridable through scene inheritance. Bot construction and score-keeping move onto GameMode, shared by match and spectate modes while preserving their differing GameSettings-override behavior and the HUD's score-row duck-typing. HUD instruments share a HudInstrument base for the smoothing-weight calc and angle-lerp helper. Also dedupes MAIN_MENU_SCENE_PATH into ScenePaths and documents why DIFFICULTIES tiers share one checkpoint.
This commit is contained in:
@@ -11,13 +11,18 @@ extends Node3D
|
||||
@export var ball_scene: PackedScene = preload("res://objects/ball.tscn")
|
||||
|
||||
const CAMERA_RIG_SCENE = preload("res://scenes/ship_camera_rig.tscn")
|
||||
const MAIN_MENU_SCENE_PATH = "res://scenes/main_menu.tscn"
|
||||
|
||||
var arena: Arena
|
||||
var ball: RigidBody3D
|
||||
var ships: Array[Ship] = []
|
||||
var _ship_spawn_transforms := {}
|
||||
|
||||
# Shared by modes that keep score (Match, Spectate); Free Play never
|
||||
# references this or emits a score_changed signal, and HUDController relies
|
||||
# on has_signal("score_changed") to decide whether to show a score row — so
|
||||
# that signal stays declared per-subclass, not here.
|
||||
var score := {0: 0, 1: 0}
|
||||
|
||||
|
||||
func _ready():
|
||||
# Group lets the HUD discover the game mode for timer/score signals
|
||||
@@ -97,6 +102,28 @@ func spawn_camera_rig(target: Ship) -> ShipCameraRig:
|
||||
return rig
|
||||
|
||||
|
||||
# Given an already-resolved (path, reaction_ticks, action_noise) — callers
|
||||
# apply their own GameSettings-override logic first, which differs between
|
||||
# modes (Match lets GameSettings override all three fields, Spectate only
|
||||
# the path) — builds a trained-policy controller, or an inert placeholder if
|
||||
# the path is empty or missing.
|
||||
func _build_opponent(model_path: String, reaction_ticks: int, action_noise: float, label: String = "GameMode") -> ShipController:
|
||||
if not model_path.is_empty() and FileAccess.file_exists(model_path):
|
||||
var bot := AIShipController.new()
|
||||
bot.model_path = model_path
|
||||
bot.reaction_ticks = reaction_ticks
|
||||
bot.action_noise = action_noise
|
||||
return bot
|
||||
if not model_path.is_empty():
|
||||
push_warning("%s: bot model not found at %s, spawning inert opponent" % [label, model_path])
|
||||
return ShipController.new() # inert placeholder
|
||||
|
||||
|
||||
func _record_goal(scoring_team: int) -> void:
|
||||
score[scoring_team] += 1
|
||||
print("Goal for team %d! Score: %d - %d" % [scoring_team, score[0], score[1]])
|
||||
|
||||
|
||||
# Tiny per-reset randomization, well below anything a player would notice as
|
||||
# "not a real kickoff" — just enough that two ships running the identical
|
||||
# deterministic AI policy (action_noise = 0) don't start every kickoff from a
|
||||
@@ -141,4 +168,4 @@ func _reset_body(body: RigidBody3D, to: Transform3D) -> void:
|
||||
|
||||
func _unhandled_input(event):
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
get_tree().change_scene_to_file(MAIN_MENU_SCENE_PATH)
|
||||
get_tree().change_scene_to_file(ScenePaths.MAIN_MENU)
|
||||
|
||||
Reference in New Issue
Block a user