Add sun and starfield config to level config schema

- Add StarfieldConfig interface (count, radius, brightness, pointSize)
- Add scale property to SunConfig for independent x/y/z scaling
- Update levelDeserializer to apply sun scale and expose starfield config
- Update level1 to pass starfield config to BackgroundStars
- Create SunConfigEditor.svelte for editing sun properties
- Create StarfieldConfigEditor.svelte for editing starfield properties
- Add Sun and Stars tabs to LevelConfigEditor

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-12-02 15:41:20 -06:00
parent 29db6ec4b7
commit 71bb2b25da
6 changed files with 249 additions and 9 deletions

View File

@ -8,6 +8,8 @@
import InfoBox from '../shared/InfoBox.svelte'; import InfoBox from '../shared/InfoBox.svelte';
import ShipConfigEditor from './ShipConfigEditor.svelte'; import ShipConfigEditor from './ShipConfigEditor.svelte';
import BaseConfigEditor from './BaseConfigEditor.svelte'; import BaseConfigEditor from './BaseConfigEditor.svelte';
import SunConfigEditor from './SunConfigEditor.svelte';
import StarfieldConfigEditor from './StarfieldConfigEditor.svelte';
import AsteroidListEditor from './AsteroidListEditor.svelte'; import AsteroidListEditor from './AsteroidListEditor.svelte';
import PlanetListEditor from './PlanetListEditor.svelte'; import PlanetListEditor from './PlanetListEditor.svelte';
@ -29,6 +31,8 @@
const tabs = [ const tabs = [
{ id: 'ship', label: '🚀 Ship' }, { id: 'ship', label: '🚀 Ship' },
{ id: 'base', label: '🛬 Base' }, { id: 'base', label: '🛬 Base' },
{ id: 'sun', label: '☀️ Sun' },
{ id: 'starfield', label: '✨ Stars' },
{ id: 'asteroids', label: '☄️ Asteroids' }, { id: 'asteroids', label: '☄️ Asteroids' },
{ id: 'planets', label: '🪐 Planets' } { id: 'planets', label: '🪐 Planets' }
]; ];
@ -111,6 +115,15 @@
config.startBase = undefined; config.startBase = undefined;
} }
} }
function handleStarfieldToggle(enabled: boolean) {
if (!config) return;
if (enabled && !config.starfield) {
config.starfield = {};
} else if (!enabled) {
config.starfield = undefined;
}
}
</script> </script>
<div class="editor-container"> <div class="editor-container">
@ -153,6 +166,10 @@
<ShipConfigEditor bind:config={config.ship} /> <ShipConfigEditor bind:config={config.ship} />
{:else if activeTab === 'base'} {:else if activeTab === 'base'}
<BaseConfigEditor config={config.startBase} onToggle={handleBaseToggle} /> <BaseConfigEditor config={config.startBase} onToggle={handleBaseToggle} />
{:else if activeTab === 'sun'}
<SunConfigEditor bind:config={config.sun} />
{:else if activeTab === 'starfield'}
<StarfieldConfigEditor config={config.starfield} onToggle={handleStarfieldToggle} />
{:else if activeTab === 'asteroids'} {:else if activeTab === 'asteroids'}
<AsteroidListEditor bind:asteroids={config.asteroids} /> <AsteroidListEditor bind:asteroids={config.asteroids} />
{:else if activeTab === 'planets'} {:else if activeTab === 'planets'}

View File

@ -0,0 +1,127 @@
<script lang="ts">
import type { StarfieldConfig } from '../../levels/config/levelConfig';
import Section from '../shared/Section.svelte';
export let config: StarfieldConfig | undefined;
export let onToggle: (enabled: boolean) => void;
let isEnabled = !!config;
// Default values matching BackgroundStars.DEFAULT_CONFIG
const defaults: Required<StarfieldConfig> = {
count: 4500,
radius: 50000,
minBrightness: 0.1,
maxBrightness: 1.0,
pointSize: 0.1
};
function handleToggle() {
isEnabled = !isEnabled;
onToggle(isEnabled);
}
// Initialize with defaults when enabled
$: if (config) {
if (config.count === undefined) config.count = defaults.count;
if (config.radius === undefined) config.radius = defaults.radius;
if (config.minBrightness === undefined) config.minBrightness = defaults.minBrightness;
if (config.maxBrightness === undefined) config.maxBrightness = defaults.maxBrightness;
if (config.pointSize === undefined) config.pointSize = defaults.pointSize;
}
</script>
<Section title="Starfield Configuration">
<div class="toggle-field">
<label>
<input type="checkbox" checked={isEnabled} on:change={handleToggle} />
Custom Starfield Settings
</label>
<p class="hint">When disabled, uses default starfield settings.</p>
</div>
{#if isEnabled && config}
<div class="field">
<label for="count">Star Count</label>
<input id="count" type="number" bind:value={config.count} step={100} min={100} max={10000} class="settings-input" />
<span class="field-hint">Number of stars (100-10000)</span>
</div>
<div class="field">
<label for="radius">Radius</label>
<input id="radius" type="number" bind:value={config.radius} step={1000} min={1000} class="settings-input" />
<span class="field-hint">Sphere radius containing stars</span>
</div>
<div class="field">
<label for="minBrightness">Min Brightness</label>
<input id="minBrightness" type="range" bind:value={config.minBrightness} step={0.01} min={0} max={1} class="slider" />
<span class="field-value">{config.minBrightness?.toFixed(2)}</span>
</div>
<div class="field">
<label for="maxBrightness">Max Brightness</label>
<input id="maxBrightness" type="range" bind:value={config.maxBrightness} step={0.01} min={0} max={1} class="slider" />
<span class="field-value">{config.maxBrightness?.toFixed(2)}</span>
</div>
<div class="field">
<label for="pointSize">Point Size</label>
<input id="pointSize" type="number" bind:value={config.pointSize} step={0.1} min={0.1} max={5} class="settings-input" />
<span class="field-hint">Size of star points (0.1-5)</span>
</div>
{/if}
</Section>
<style>
.field {
margin-bottom: 1rem;
}
.field label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--color-text-primary, #fff);
}
.field input.settings-input {
width: 100%;
}
.field-hint {
display: block;
font-size: 0.8rem;
color: var(--color-text-secondary, #888);
margin-top: 0.25rem;
}
.field-value {
margin-left: 0.5rem;
color: var(--color-primary, #4a9eff);
}
.slider {
width: 100%;
cursor: pointer;
}
.toggle-field {
margin-bottom: 1.5rem;
}
.toggle-field label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
color: var(--color-text-primary, #fff);
font-weight: 500;
}
.hint {
font-size: 0.8rem;
color: var(--color-text-secondary, #888);
margin: 0.5rem 0 0 1.5rem;
}
</style>

View File

@ -0,0 +1,75 @@
<script lang="ts">
import type { SunConfig } from '../../levels/config/levelConfig';
import Vector3Input from './Vector3Input.svelte';
import Section from '../shared/Section.svelte';
export let config: SunConfig;
let hasScale = !!config.scale;
// Ensure arrays exist with defaults
$: if (!config.position) config.position = [0, 0, 0];
function toggleScale() {
hasScale = !hasScale;
if (hasScale && !config.scale) {
config.scale = [1, 1, 1];
} else if (!hasScale) {
config.scale = undefined;
}
}
</script>
<Section title="Sun Configuration">
<Vector3Input label="Position" bind:value={config.position} step={100} />
<div class="field">
<label for="diameter">Diameter</label>
<input id="diameter" type="number" bind:value={config.diameter} step={10} class="settings-input" />
</div>
<div class="field">
<label for="intensity">Intensity (optional)</label>
<input id="intensity" type="number" bind:value={config.intensity} step={0.1} class="settings-input" />
</div>
<div class="toggle-field">
<label>
<input type="checkbox" checked={hasScale} on:change={toggleScale} />
Custom Scale
</label>
</div>
{#if hasScale && config.scale}
<Vector3Input label="Scale" bind:value={config.scale} step={0.1} />
{/if}
</Section>
<style>
.field {
margin-bottom: 1rem;
}
.field label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--color-text-primary, #fff);
}
.field input {
width: 100%;
}
.toggle-field {
margin: 1rem 0;
}
.toggle-field label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
color: var(--color-text-primary, #fff);
}
</style>

View File

@ -82,10 +82,22 @@ interface StartBaseConfig {
/** /**
* Sun configuration * Sun configuration
*/ */
interface SunConfig { export interface SunConfig {
position: Vector3Array; position: Vector3Array;
diameter: number; diameter: number;
intensity?: number; // Light intensity intensity?: number; // Light intensity
scale?: Vector3Array; // Independent x/y/z scaling
}
/**
* Starfield configuration
*/
export interface StarfieldConfig {
count?: number; // Number of stars (default: 4500)
radius?: number; // Sphere radius (default: 50000)
minBrightness?: number; // Min brightness 0-1 (default: 0.1)
maxBrightness?: number; // Max brightness 0-1 (default: 1.0)
pointSize?: number; // Star point size (default: 0.1)
} }
/** /**
@ -142,6 +154,7 @@ export interface LevelConfig {
ship: ShipConfig; ship: ShipConfig;
startBase?: StartBaseConfig; startBase?: StartBaseConfig;
sun: SunConfig; sun: SunConfig;
starfield?: StarfieldConfig;
planets: PlanetConfig[]; planets: PlanetConfig[];
asteroids: AsteroidConfig[]; asteroids: AsteroidConfig[];

View File

@ -16,6 +16,7 @@ import { ScoreEvent } from "../../ui/hud/scoreboard";
import { import {
LevelConfig, LevelConfig,
ShipConfig, ShipConfig,
StarfieldConfig,
Vector3Array, Vector3Array,
validateLevelConfig validateLevelConfig
} from "./levelConfig"; } from "./levelConfig";
@ -156,9 +157,21 @@ export class LevelDeserializer {
sun.material = material; sun.material = material;
sun.renderingGroupId = 2; sun.renderingGroupId = 2;
// Apply scale if specified
if (config.scale) {
sun.scaling = this.arrayToVector3(config.scale);
}
return sun; return sun;
} }
/**
* Get starfield configuration for BackgroundStars
*/
public getStarfieldConfig(): StarfieldConfig | undefined {
return this.config.starfield;
}
/** /**
* Create planets from config * Create planets from config
*/ */

View File

@ -408,14 +408,9 @@ export class Level1 implements Level {
// Initialize scoreboard with asteroid count // Initialize scoreboard with asteroid count
this._ship.scoreboard.setRemainingCount(this._asteroidCount); this._ship.scoreboard.setRemainingCount(this._asteroidCount);
// Create background starfield // Create background starfield (use config if available, otherwise defaults)
this._backgroundStars = new BackgroundStars(DefaultScene.MainScene, { const starfieldConfig = this._deserializer.getStarfieldConfig();
count: 5000, this._backgroundStars = new BackgroundStars(DefaultScene.MainScene, starfieldConfig);
radius: 3000,
minBrightness: 0.1,
maxBrightness: 1.0,
pointSize: 1
});
// Set up render loop updates // Set up render loop updates
DefaultScene.MainScene.onBeforeRenderObservable.add(() => { DefaultScene.MainScene.onBeforeRenderObservable.add(() => {