mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-13 22:12:03 +00:00
refactor(team-colors): collapse disagreeing team palettes into one source of truth
ship.gd, HUDController.gd, and goal.gd each declared their own TEAM_COLORS, arena_boundary.gd/arena_deck.gdshader had a third pair, and HUD.tscn baked in a fourth (hardcoded "BLUE"/"ORANGE" labels) — nose, goal rim, end zone, and scoreboard all rendered different blues. New scripts/team_colors.gd (class_name TeamColors) is now the single source every one of those reads from, and team identity moves to purple/green.
This commit is contained in:
@@ -71,15 +71,12 @@ theme_override_constants/separation = 16
|
|||||||
[node name="Team0Name" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
[node name="Team0Name" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 4
|
size_flags_vertical = 4
|
||||||
theme_override_colors/font_color = Color(0.25, 0.55, 1, 1)
|
|
||||||
theme_override_font_sizes/font_size = 14
|
theme_override_font_sizes/font_size = 14
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = "BLUE"
|
|
||||||
|
|
||||||
[node name="Team0Score" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
[node name="Team0Score" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 4
|
size_flags_vertical = 4
|
||||||
theme_override_colors/font_color = Color(0.25, 0.55, 1, 1)
|
|
||||||
theme_override_font_sizes/font_size = 32
|
theme_override_font_sizes/font_size = 32
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = "0"
|
text = "0"
|
||||||
@@ -93,7 +90,6 @@ horizontal_alignment = 1
|
|||||||
[node name="Team1Score" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
[node name="Team1Score" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 4
|
size_flags_vertical = 4
|
||||||
theme_override_colors/font_color = Color(1, 0.5, 0.15, 1)
|
|
||||||
theme_override_font_sizes/font_size = 32
|
theme_override_font_sizes/font_size = 32
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = "0"
|
text = "0"
|
||||||
@@ -101,10 +97,8 @@ text = "0"
|
|||||||
[node name="Team1Name" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
[node name="Team1Name" type="Label" parent="Control/ScoreboardPanel/ScoreRow"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 4
|
size_flags_vertical = 4
|
||||||
theme_override_colors/font_color = Color(1, 0.5, 0.15, 1)
|
|
||||||
theme_override_font_sizes/font_size = 14
|
theme_override_font_sizes/font_size = 14
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = "ORANGE"
|
|
||||||
|
|
||||||
[node name="KickoffLabel" type="Label" parent="Control"]
|
[node name="KickoffLabel" type="Label" parent="Control"]
|
||||||
visible = false
|
visible = false
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ class_name HUDController
|
|||||||
@onready var thrust_bar: HudGauge = get_node_or_null("Control/Instruments/Cluster/ThrustBar")
|
@onready var thrust_bar: HudGauge = get_node_or_null("Control/Instruments/Cluster/ThrustBar")
|
||||||
@onready var camera_mode_label = get_node_or_null("Control/Instruments/Cluster/CameraModeLabel")
|
@onready var camera_mode_label = get_node_or_null("Control/Instruments/Cluster/CameraModeLabel")
|
||||||
|
|
||||||
# Team identity, mirroring TEAM_COLORS in ship.gd
|
|
||||||
const TEAM_NAMES := {0: "Blue", 1: "Orange"}
|
|
||||||
const TEAM_COLORS := {0: Color(0.25, 0.55, 1.0), 1: Color(1.0, 0.5, 0.15)}
|
|
||||||
|
|
||||||
var ship: Node
|
var ship: Node
|
||||||
var _last_score := {0: 0, 1: 0}
|
var _last_score := {0: 0, 1: 0}
|
||||||
|
|
||||||
@@ -66,6 +62,20 @@ func _initialize_hud():
|
|||||||
if timer_label and is_instance_valid(timer_label):
|
if timer_label and is_instance_valid(timer_label):
|
||||||
timer_label.visible = has_timer
|
timer_label.visible = has_timer
|
||||||
|
|
||||||
|
# Team identity (name + color) is fixed regardless of whether this
|
||||||
|
# mode tracks score, so set it here rather than baking it into the
|
||||||
|
# scene where it can't track TeamColors.
|
||||||
|
if team0_name_label and is_instance_valid(team0_name_label):
|
||||||
|
team0_name_label.text = TeamColors.TEAM_NAMES[0]
|
||||||
|
team0_name_label.add_theme_color_override("font_color", TeamColors.TEAM_COLORS[0])
|
||||||
|
if team0_score_label and is_instance_valid(team0_score_label):
|
||||||
|
team0_score_label.add_theme_color_override("font_color", TeamColors.TEAM_COLORS[0])
|
||||||
|
if team1_score_label and is_instance_valid(team1_score_label):
|
||||||
|
team1_score_label.add_theme_color_override("font_color", TeamColors.TEAM_COLORS[1])
|
||||||
|
if team1_name_label and is_instance_valid(team1_name_label):
|
||||||
|
team1_name_label.text = TeamColors.TEAM_NAMES[1]
|
||||||
|
team1_name_label.add_theme_color_override("font_color", TeamColors.TEAM_COLORS[1])
|
||||||
|
|
||||||
# Score display follows the same pattern: modes without scoring
|
# Score display follows the same pattern: modes without scoring
|
||||||
# (e.g. free play) just don't show it
|
# (e.g. free play) just don't show it
|
||||||
var has_score = game_manager and game_manager.has_signal("score_changed")
|
var has_score = game_manager and game_manager.has_signal("score_changed")
|
||||||
@@ -177,9 +187,9 @@ func _on_match_ended(winning_team: int, score: Dictionary):
|
|||||||
result_label.remove_theme_color_override("font_color")
|
result_label.remove_theme_color_override("font_color")
|
||||||
_set_result_panel_accent(Color(1, 1, 1, 0.2))
|
_set_result_panel_accent(Color(1, 1, 1, 0.2))
|
||||||
else:
|
else:
|
||||||
result_label.text = "%s team wins!" % TEAM_NAMES[winning_team]
|
result_label.text = "%s team wins!" % TeamColors.TEAM_NAMES[winning_team]
|
||||||
result_label.add_theme_color_override("font_color", TEAM_COLORS[winning_team])
|
result_label.add_theme_color_override("font_color", TeamColors.TEAM_COLORS[winning_team])
|
||||||
_set_result_panel_accent(TEAM_COLORS[winning_team])
|
_set_result_panel_accent(TeamColors.TEAM_COLORS[winning_team])
|
||||||
final_score_label.text = "%d - %d" % [score.get(0, 0), score.get(1, 0)]
|
final_score_label.text = "%d - %d" % [score.get(0, 0), score.get(1, 0)]
|
||||||
result_overlay.visible = true
|
result_overlay.visible = true
|
||||||
_animate_result_panel_in()
|
_animate_result_panel_in()
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ const DECK_SHADER_PATH := "res://shaders/arena_deck.gdshader"
|
|||||||
|
|
||||||
@export var field_tint := Color(0.45, 0.65, 1.0)
|
@export var field_tint := Color(0.45, 0.65, 1.0)
|
||||||
@export var field_intensity := 0.09
|
@export var field_intensity := 0.09
|
||||||
@export var team0_tint := Color(0.15, 0.45, 1.0)
|
@export var team0_tint := TeamColors.TEAM_COLORS[0]
|
||||||
@export var team1_tint := Color(1.0, 0.35, 0.25)
|
@export var team1_tint := TeamColors.TEAM_COLORS[1]
|
||||||
|
|
||||||
# The single merged surface shell and the material whose camera-side fade
|
# The single merged surface shell and the material whose camera-side fade
|
||||||
# _process() drives. Both stay null in headless runs, which never render.
|
# _process() drives. Both stay null in headless runs, which never render.
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ extends Area3D
|
|||||||
|
|
||||||
signal goal_scored(team: int)
|
signal goal_scored(team: int)
|
||||||
|
|
||||||
# Frame tints, indexed by team.
|
|
||||||
const TEAM_COLORS := [Color(0.2, 0.55, 1.0), Color(1.0, 0.4, 0.28)]
|
|
||||||
|
|
||||||
# The pocket is sunk into the end wall, so it can be at most as deep as that
|
# The pocket is sunk into the end wall, so it can be at most as deep as that
|
||||||
# wall is thick or it pokes out the back of the arena. ArenaBoundary cuts the
|
# wall is thick or it pokes out the back of the arena. ArenaBoundary cuts the
|
||||||
# matching aperture in the hull (see its GOAL_APERTURE_* constants).
|
# matching aperture in the hull (see its GOAL_APERTURE_* constants).
|
||||||
@@ -52,7 +49,7 @@ func _on_body_entered(body):
|
|||||||
func _build_visuals() -> void:
|
func _build_visuals() -> void:
|
||||||
var mouth := ($CollisionShape3D.shape as BoxShape3D).size
|
var mouth := ($CollisionShape3D.shape as BoxShape3D).size
|
||||||
var half := Vector2(mouth.x, mouth.y) / 2.0
|
var half := Vector2(mouth.x, mouth.y) / 2.0
|
||||||
var tint: Color = TEAM_COLORS[team % TEAM_COLORS.size()]
|
var tint: Color = TeamColors.TEAM_COLORS[team % TeamColors.TEAM_COLORS.size()]
|
||||||
|
|
||||||
# One ArrayMesh, four surfaces (pocket, net, bezel, rim). They stay separate
|
# One ArrayMesh, four surfaces (pocket, net, bezel, rim). They stay separate
|
||||||
# surfaces rather than sharing materials because they aren't visually
|
# surfaces rather than sharing materials because they aren't visually
|
||||||
|
|||||||
@@ -25,13 +25,6 @@ extends RigidBody3D
|
|||||||
@export var ceiling_pull_strength = 11.5 # Ceiling grav-plating strength; nets above gravity so a ship can hold a ceiling
|
@export var ceiling_pull_strength = 11.5 # Ceiling grav-plating strength; nets above gravity so a ship can hold a ceiling
|
||||||
@export var ceiling_pull_range = 3.0 # Metres from the ceiling where pull begins
|
@export var ceiling_pull_range = 3.0 # Metres from the ceiling where pull begins
|
||||||
|
|
||||||
# Accent colours per team, applied to the nose and tail fin meshes so the
|
|
||||||
# two sides are tellable apart at a glance.
|
|
||||||
const TEAM_COLORS := {
|
|
||||||
0: Color(0.25, 0.55, 1.0),
|
|
||||||
1: Color(1.0, 0.5, 0.15),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Non-tinted hull meshes, runtime-merged into one ArrayMesh by
|
# Non-tinted hull meshes, runtime-merged into one ArrayMesh by
|
||||||
# _build_merged_hull() (Nose/TailFin stay separate MeshInstance3Ds since
|
# _build_merged_hull() (Nose/TailFin stay separate MeshInstance3Ds since
|
||||||
# _apply_team_color() retints them per-team and must keep addressing them by
|
# _apply_team_color() retints them per-team and must keep addressing them by
|
||||||
@@ -73,7 +66,7 @@ static var _team_materials: Dictionary = {} # team:int -> StandardMaterial3D
|
|||||||
static func _get_team_material(team: int) -> StandardMaterial3D:
|
static func _get_team_material(team: int) -> StandardMaterial3D:
|
||||||
if _team_materials.has(team):
|
if _team_materials.has(team):
|
||||||
return _team_materials[team]
|
return _team_materials[team]
|
||||||
var color: Color = TEAM_COLORS.get(team, TEAM_COLORS[0])
|
var color: Color = TeamColors.TEAM_COLORS.get(team, TeamColors.TEAM_COLORS[0])
|
||||||
var accent := StandardMaterial3D.new()
|
var accent := StandardMaterial3D.new()
|
||||||
accent.albedo_color = color
|
accent.albedo_color = color
|
||||||
accent.metallic = 0.3
|
accent.metallic = 0.3
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
class_name TeamColors
|
||||||
|
|
||||||
|
# Single source of truth for team identity. Ship nose/tailfin, HUD result
|
||||||
|
# panel, goal rim/net, and the arena end-zone floor shader all read from
|
||||||
|
# here — do not restate these values locally (see TODO.md history).
|
||||||
|
const TEAM_NAMES := {0: "Purple", 1: "Green"}
|
||||||
|
const TEAM_COLORS := {
|
||||||
|
0: Color(0.48, 0.18, 0.88),
|
||||||
|
1: Color(0.24, 0.86, 0.42),
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c6r8q1dpd8klq
|
||||||
@@ -21,8 +21,12 @@ uniform vec3 panel_color : source_color = vec3(0.104, 0.116, 0.146);
|
|||||||
uniform float hull_fill : hint_range(0.0, 2.0) = 0.55;
|
uniform float hull_fill : hint_range(0.0, 2.0) = 0.55;
|
||||||
uniform float panel_variation : hint_range(0.0, 1.0) = 0.4;
|
uniform float panel_variation : hint_range(0.0, 1.0) = 0.4;
|
||||||
uniform vec3 line_color : source_color = vec3(0.62, 0.78, 1.0);
|
uniform vec3 line_color : source_color = vec3(0.62, 0.78, 1.0);
|
||||||
uniform vec3 team0_color : source_color = vec3(0.15, 0.45, 1.0);
|
// Must track TeamColors.TEAM_COLORS in scripts/team_colors.gd — shaders can't
|
||||||
uniform vec3 team1_color : source_color = vec3(1.0, 0.35, 0.25);
|
// import GDScript consts, and arena_boundary.gd always overrides these at
|
||||||
|
// runtime, but keep the literal defaults in sync for anyone inspecting the
|
||||||
|
// shader/material directly.
|
||||||
|
uniform vec3 team0_color : source_color = vec3(0.48, 0.18, 0.88);
|
||||||
|
uniform vec3 team1_color : source_color = vec3(0.24, 0.86, 0.42);
|
||||||
uniform vec3 seam_color : source_color = vec3(0.35, 0.75, 1.0);
|
uniform vec3 seam_color : source_color = vec3(0.35, 0.75, 1.0);
|
||||||
|
|
||||||
// Play-volume dimensions, from ArenaBoundary's constants.
|
// Play-volume dimensions, from ArenaBoundary's constants.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ The training pipeline is built — see `TRAINING.md` (self-play PPO via the vend
|
|||||||
Bugs found in an adversarial review. None are gameplay- or physics-affecting, so all are safe to land against the current `Game/bots/` checkpoints.
|
Bugs found in an adversarial review. None are gameplay- or physics-affecting, so all are safe to land against the current `Game/bots/` checkpoints.
|
||||||
|
|
||||||
- [x] `VideoSettings.apply_to_environment()` (`scripts/video_settings.gd`) compounds on every arena load: `env.glow_intensity *= glow_scale` mutates an `Environment` that is a `[sub_resource]` of the arena scene, and Godot shares sub-resources across instantiations of a cached `PackedScene`. Glow at 50% becomes 25% then 12.5% across repeat entries. Fix by duplicating the Environment in `Arena._ready()` (`scripts/arena.gd`). Regression test: set glow to 50%, enter/leave Free Play three times, confirm it's still 50%.
|
- [x] `VideoSettings.apply_to_environment()` (`scripts/video_settings.gd`) compounds on every arena load: `env.glow_intensity *= glow_scale` mutates an `Environment` that is a `[sub_resource]` of the arena scene, and Godot shares sub-resources across instantiations of a cached `PackedScene`. Glow at 50% becomes 25% then 12.5% across repeat entries. Fix by duplicating the Environment in `Arena._ready()` (`scripts/arena.gd`). Regression test: set glow to 50%, enter/leave Free Play three times, confirm it's still 50%.
|
||||||
- [ ] Collapse the four disagreeing team palettes into one source of truth — `ship.gd`, `HUDController.gd` and `goal.gd` each declare `TEAM_COLORS`, `arena_boundary.gd` exports `team0_tint`/`team1_tint`, and `arena_deck.gdshader` defaults to a fifth pair. Three of them disagree, so nose, goal rim, end zone and scoreboard are all different blues.
|
- [x] Collapse the four disagreeing team palettes into one source of truth — `ship.gd`, `HUDController.gd` and `goal.gd` each declare `TEAM_COLORS`, `arena_boundary.gd` exports `team0_tint`/`team1_tint`, and `arena_deck.gdshader` defaults to a fifth pair. Three of them disagree, so nose, goal rim, end zone and scoreboard are all different blues.
|
||||||
- [ ] Goal scoring volume (3.5 x 1.5, `objects/goal.tscn`) is smaller than the drawn mouth (3.7 x 1.65, `ArenaBoundary.GOAL_APERTURE_*`) — a ball crossing the visible edge doesn't score. Derive the aperture constants from the goal's collision shape, the way `goal.gd:53` already measures its own visuals.
|
- [ ] Goal scoring volume (3.5 x 1.5, `objects/goal.tscn`) is smaller than the drawn mouth (3.7 x 1.65, `ArenaBoundary.GOAL_APERTURE_*`) — a ball crossing the visible edge doesn't score. Derive the aperture constants from the goal's collision shape, the way `goal.gd:53` already measures its own visuals.
|
||||||
- [ ] `match_mode.gd`: full time can fire mid-kickoff-countdown, and the stalled coroutine resumes into the dying scene (can re-emit `kickoff_countdown` / unfreeze bodies for a frame). Guard `_run_kickoff_countdown` with a match-over flag.
|
- [ ] `match_mode.gd`: full time can fire mid-kickoff-countdown, and the stalled coroutine resumes into the dying scene (can re-emit `kickoff_countdown` / unfreeze bodies for a frame). Guard `_run_kickoff_countdown` with a match-over flag.
|
||||||
- [ ] `match_mode.gd` emits `timer_updated` every frame for a value that changes once a second; the HUD re-formats and re-shapes the label each time. Emit only on change, matching `ship.gd`'s threshold-gated telemetry discipline.
|
- [ ] `match_mode.gd` emits `timer_updated` every frame for a value that changes once a second; the HUD re-formats and re-shapes the label each time. Emit only on change, matching `ship.gd`'s threshold-gated telemetry discipline.
|
||||||
|
|||||||
Reference in New Issue
Block a user