feat(ui): add persistent bilingual settings and combat HUD

This commit is contained in:
2026-11-12 18:01:54 +08:00
parent 0857e291d5
commit 97fbf187e3
54 changed files with 3046 additions and 52 deletions
+53 -11
View File
@@ -11,10 +11,11 @@ namespace Ironspore
VisualElement root;
Label mission, prompt, status, notice, telemetry;
ProgressBar armor, energy, cargo;
VisualElement cross;
VisualElement cross, controlsHint;
CombatInstruments instruments;
enum View
{
Hangar, Field, Cargo, Part, Routes, Pause
Hangar, Field, Cargo, Part, Routes, Pause, Settings
}
View view;
int partSlot, partIndex;
@@ -24,6 +25,11 @@ namespace Ironspore
int lastLanguageActivationFrame = -1;
int lastLanguageNavigationFrame = -1;
Label hangarNotice;
SettingsScreen settingsScreen;
int menuInputFrame = -1;
public bool SettingsOpen => view == View.Settings;
public bool MenuInputConsumedThisFrame => menuInputFrame == Time.frameCount;
string K(string action) => game.Settings.Bindings.Display(action);
static string L(string key, params object[] args) => GameLocalization.Text(key, args);
public void Initialize(GameSession session)
{
@@ -39,6 +45,7 @@ namespace Ironspore
root.style.unityFontDefinition = FontDefinition.FromSDFFont(game.localization.uiFont);
root.RegisterCallback<KeyDownEvent>(HandleLanguageKey, TrickleDown.TrickleDown);
GameLocalization.Changed += RefreshLocale;
settingsScreen = new SettingsScreen(game.Settings, () => game.Action("Pause").WasPressedThisFrame());
}
void HandleLanguageKey(KeyDownEvent evt)
{
@@ -106,10 +113,12 @@ namespace Ironspore
}
void Clear()
{
settingsScreen?.Close();
root.Clear();
mission = prompt = status = notice = telemetry = null;
armor = energy = cargo = null;
cross = null;
cross = controlsHint = null;
instruments = null;
hangarNotice = null;
languageControl = languageMenu = languageDismiss = null;
languageButton = null;
@@ -137,6 +146,9 @@ namespace Ironspore
case View.Pause:
ShowPause();
break;
case View.Settings:
settingsScreen.RefreshLocale();
break;
}
}
void AddLanguageControl(VisualElement page)
@@ -242,10 +254,11 @@ namespace Ironspore
if (!game.StartingPartsUnlocked())
Text(content, L("ui.home.locked"), "warning");
Button(controls, L("ui.home.range"), game.StartTrial);
Button(controls, L("settings.title"), ShowSettings).name = "home-settings";
if (game.Store.Data.run.active)
Button(controls, L("ui.home.resume"), game.Resume, "primary");
Text(content, L("ui.home.moveHint"), "hint");
Text(content, L("ui.home.fireHint"), "hint");
Text(content, L("ui.home.moveHint", K("Move"), K("View"), K("Boost"), K("Jump")), "hint");
Text(content, L("ui.home.fireHint", K("Fire"), K("Aim"), K("Secondary"), K("Guard"), K("Interact")), "hint");
var save = game.Store.Data;
Text(content, L("ui.home.inventory"), "section");
Text(content, L("ui.home.stock", save.alloy, save.cells, save.blueprints.Count, save.extractions, save.completed), "budget");
@@ -273,7 +286,7 @@ namespace Ironspore
var goal = Element(field, "mission");
Text(goal, L("ui.hud.operation"), "section");
mission = Text(goal, "", "mission-text");
telemetry = Text(field, "", "telemetry");
telemetry = Text(goal, "", "combat-extraction");
cross = Element(field, "crosshair");
cross.style.position = Position.Absolute;
cross.style.left = Length.Percent(50);
@@ -294,16 +307,17 @@ namespace Ironspore
prompt = Text(field, "", "interact");
notice = Text(field, "", "notice");
var bottom = Element(field, "bottom");
instruments = new CombatInstruments(field, bottom, game);
armor = new ProgressBar { title = L("ui.hud.armor", 0), lowValue = 0, highValue = 100 };
armor.AddToClassList("armor");
bottom.Add(armor);
energy = new ProgressBar { title = L("ui.hud.energy", 0), lowValue = 0, highValue = 100 };
energy.AddToClassList("energy");
bottom.Add(energy);
cargo = new ProgressBar { title = L("ui.hud.podReady"), lowValue = 0, highValue = 3 };
cargo = new ProgressBar { title = L("ui.hud.podReady", K("Inventory")), lowValue = 0, highValue = 3 };
cargo.AddToClassList("cargo-progress");
bottom.Add(cargo);
Text(bottom, L("ui.hud.hint"), "hint");
controlsHint = Text(bottom, L("ui.hud.hint", K("View"), K("Boost"), K("Jump"), K("Secondary"), K("Guard"), K("Interact"), K("Inventory"), K("Cargo"), K("Pause")), "hint");
}
VisualElement Overlay(string heading, string body)
{
@@ -316,7 +330,7 @@ namespace Ironspore
}
public void ShowCargo()
{
var panel = Overlay(L("ui.cargo.title"), L(game.Run.podUsed ? "ui.cargo.used" : "ui.cargo.help"));
var panel = Overlay(L("ui.cargo.title"), L(game.Run.podUsed ? "ui.cargo.used" : "ui.cargo.help", K("Cargo")));
view = View.Cargo;
foreach (var stack in game.Run.cargo)
{
@@ -356,12 +370,33 @@ namespace Ironspore
var panel = Overlay(L("ui.pause.title"), L("ui.pause.help"));
view = View.Pause;
Button(panel, L("ui.pause.resume"), game.CloseMenu, "primary");
Button(panel, L("settings.title"), ShowSettings).name = "pause-settings";
if (game.Trial)
Button(panel, L("ui.pause.range"), () => { game.CloseMenu(); game.EndSortie(true); });
Button(panel, L("ui.pause.quit"), () => Application.Quit());
}
public void ShowSettings()
{
bool returnToPause = view == View.Pause;
Clear();
view = View.Settings;
menuInputFrame = Time.frameCount;
settingsScreen.Open(root, () =>
{
if (returnToPause) ShowPause();
else ShowHangar();
root.Q<Button>(returnToPause ? "pause-settings" : "home-settings")?.Focus();
});
}
public void CancelSettingsInput() => settingsScreen.CancelInput();
void Update()
{
if (SettingsOpen)
{
menuInputFrame = Time.frameCount;
settingsScreen.Tick();
return;
}
if (view == View.Hangar)
{
RefreshHangarNotice();
@@ -399,7 +434,7 @@ namespace Ironspore
if (cargo != null)
{
cargo.value = game.CargoProgress;
cargo.title = L(run.podUsed ? "ui.hud.podUsed" : game.CargoProgress > 0 ? "ui.hud.podPreparing" : "ui.hud.podReady");
cargo.title = L(run.podUsed ? "ui.hud.podUsed" : game.CargoProgress > 0 ? "ui.hud.podPreparing" : "ui.hud.podReady", K("Inventory"));
}
if (prompt != null)
{
@@ -409,12 +444,19 @@ namespace Ironspore
if (notice != null)
notice.text = game.Notice;
if (telemetry != null)
telemetry.text = L("ui.hud.telemetry", game.chassis[run.chassis].LocalizedName, L(p.FirstPerson ? "ui.hud.cockpit" : "ui.hud.shoulder"), game.Part(0, run.equipment[0]).LocalizedName, run.cargo.Count, game.World.ExtractionRemaining >= 0 ? game.World.TransportReady ? L("ui.hud.transportReady") : L("ui.hud.transportEta", Mathf.CeilToInt(game.World.ExtractionRemaining)) : L("ui.hud.extractionAvailable"));
telemetry.text = game.World.ExtractionRemaining < 0 ? L("ui.hud.extractionAvailable") :
game.World.TransportReady ? L("ui.hud.transportReady") : L("ui.hud.transportEta", Mathf.CeilToInt(game.World.ExtractionRemaining));
if (instruments != null)
{
instruments.Refresh();
controlsHint.style.display = game.Action("Aim").IsPressed() ? DisplayStyle.None : DisplayStyle.Flex;
}
if (cross != null)
cross.style.opacity = p.RecentHit ? 1 : .65f;
}
void OnDestroy()
{
settingsScreen?.Close();
GameLocalization.Changed -= RefreshLocale;
if (panelSettings)
Destroy(panelSettings);