mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 15:05:05 +00:00
Add Graphics Settings modal with live name-label tuning (#4065)
## Description:
- Add a user-facing **Graphics Settings** modal accessible from the
in-game Settings menu, with live preview as sliders change.
- First two knobs: **Name Scale** and **Minimum Name Size** (the
name-cull threshold).
- Overrides stored as a single JSON blob in `localStorage` under
`settings.graphics`, validated by a Zod schema
(`GraphicsOverridesSchema`). Future graphics knobs just extend the
schema + slider list.
## How it fits together
- `generateRenderSettings(overrides)` (`RenderSettings.ts`) — pure
function: clones `render-settings.json` defaults, layers overrides on
top, returns a fresh `RenderSettings`.
- `UserSettings.graphicsOverrides()` / `setGraphicsOverrides()` —
read/write the blob; falls back to `{}` on a missing/corrupt entry.
- `ClientGameRunner` listens for
`USER_SETTINGS_CHANGED_EVENT:settings.graphics`, regenerates, and
`Object.assign`s each category into the live `view.getSettings()` slice
so passes pick up the new values on the next frame (no renderer
reconstruction).
- Modal reads defaults straight from `render-settings.json` so there's
no duplication.
<img width="599" height="515" alt="Screenshot 2026-05-28 at 11 18 43 AM"
src="https://github.com/user-attachments/assets/263d7d91-10d8-4a66-a069-10015c735d60"
/>
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
This commit is contained in:
@@ -31,6 +31,7 @@ import { GameView, PlayerView } from "../core/game/GameView";
|
||||
import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader";
|
||||
import {
|
||||
DARK_MODE_KEY,
|
||||
GRAPHICS_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
UserSettings,
|
||||
} from "../core/game/UserSettings";
|
||||
@@ -67,7 +68,11 @@ import {
|
||||
import { createCanvas } from "./Utils";
|
||||
import { WebGLFrameBuilder } from "./WebGLFrameBuilder";
|
||||
import { createRenderer, GameRenderer } from "./hud/GameRenderer";
|
||||
import { createDebugGui, GameView as WebGLGameView } from "./render/gl";
|
||||
import {
|
||||
createDebugGui,
|
||||
generateRenderSettings,
|
||||
GameView as WebGLGameView,
|
||||
} from "./render/gl";
|
||||
import { ALL_UNIT_TYPES, UnitState } from "./render/types";
|
||||
import { SoundManager } from "./sound/SoundManager";
|
||||
|
||||
@@ -479,6 +484,21 @@ async function createClientGame(
|
||||
(e) => view.setShowPatterns((e as CustomEvent<string>).detail === "true"),
|
||||
);
|
||||
|
||||
const graphicsListenerAbort = new AbortController();
|
||||
const applyGraphicsOverrides = (): void => {
|
||||
const generated = generateRenderSettings(
|
||||
userSettings.graphicsOverrides(),
|
||||
);
|
||||
const live = view.getSettings();
|
||||
Object.assign(live.name, generated.name);
|
||||
};
|
||||
applyGraphicsOverrides();
|
||||
globalThis.addEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${GRAPHICS_KEY}`,
|
||||
applyGraphicsOverrides,
|
||||
{ signal: graphicsListenerAbort.signal },
|
||||
);
|
||||
|
||||
let debugGui: ReturnType<typeof createDebugGui> | null = null;
|
||||
eventBus.on(ToggleRenderDebugGuiEvent, () => {
|
||||
if (debugGui === null) {
|
||||
@@ -524,6 +544,7 @@ async function createClientGame(
|
||||
soundManager,
|
||||
userSettings,
|
||||
webglBuilder,
|
||||
graphicsListenerAbort,
|
||||
);
|
||||
} catch (err) {
|
||||
soundManager.dispose();
|
||||
@@ -557,6 +578,7 @@ export class ClientGameRunner {
|
||||
private soundManager: SoundManager,
|
||||
private userSettings: UserSettings,
|
||||
private webglBuilder: WebGLFrameBuilder | null = null,
|
||||
private graphicsListenerAbort: AbortController | null = null,
|
||||
) {
|
||||
this.lastMessageTime = Date.now();
|
||||
}
|
||||
@@ -813,6 +835,7 @@ export class ClientGameRunner {
|
||||
|
||||
public stop() {
|
||||
this.soundManager.dispose();
|
||||
this.graphicsListenerAbort?.abort();
|
||||
if (!this.isActive) return;
|
||||
|
||||
this.isActive = false;
|
||||
|
||||
Reference in New Issue
Block a user