Skip to content

Commit 267a248

Browse files
author
DavidQ
committed
feat: add Skin Editor and wire game skin loading/overrides for Breakout, Pong, SolarSystem, and Bouncing Ball
1 parent 862ed89 commit 267a248

30 files changed

Lines changed: 1711 additions & 122 deletions
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"sampleId": "game-solarsystem-skin",
3+
"title": "Solar System Skin",
4+
"description": "Default editable skin payload for Solar System.",
5+
"toolHints": [
6+
"skin-editor"
7+
],
8+
"payload": {
9+
"gameId": "SolarSystem",
10+
"skinPath": "/games/SolarSystem/assets/skins/default.json",
11+
"skin": {
12+
"documentKind": "game-skin",
13+
"schema": "games.solar-system.skin/1",
14+
"version": 1,
15+
"gameId": "SolarSystem",
16+
"name": "Solar System Classic Skin",
17+
"colors": {
18+
"background": "#030712",
19+
"frame": "#dbeafe",
20+
"orbit": "#334155",
21+
"text": "#dbeafe",
22+
"muted": "#94a3b8",
23+
"panel": "#07101d"
24+
},
25+
"entities": {
26+
"sun": {
27+
"color": "#fbbf24",
28+
"radius": 30
29+
},
30+
"planets": {
31+
"mercury": { "color": "#9ca3af" },
32+
"venus": { "color": "#fde68a" },
33+
"earth": { "color": "#38bdf8" },
34+
"mars": { "color": "#fb7185" },
35+
"jupiter": { "color": "#f59e0b" },
36+
"saturn": { "color": "#eab308" },
37+
"uranus": { "color": "#67e8f9" },
38+
"neptune": { "color": "#60a5fa" }
39+
},
40+
"moons": {
41+
"moon": { "color": "#e5e7eb" },
42+
"io": { "color": "#fde68a" },
43+
"europa": { "color": "#dbeafe" },
44+
"ganymede": { "color": "#cbd5e1" },
45+
"titan": { "color": "#fef3c7" }
46+
},
47+
"rings": {
48+
"jupiter": "rgba(245, 158, 11, 0.22)",
49+
"saturn": "rgba(253, 230, 138, 0.55)",
50+
"uranus": "rgba(103, 232, 249, 0.28)",
51+
"neptune": "rgba(96, 165, 250, 0.24)"
52+
}
53+
}
54+
}
55+
}
56+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"documentKind": "game-skin",
3+
"schema": "games.solar-system.skin/1",
4+
"version": 1,
5+
"gameId": "SolarSystem",
6+
"name": "Solar System Classic Skin",
7+
"colors": {
8+
"background": "#030712",
9+
"frame": "#dbeafe",
10+
"orbit": "#334155",
11+
"text": "#dbeafe",
12+
"muted": "#94a3b8",
13+
"panel": "#07101d"
14+
},
15+
"entities": {
16+
"sun": {
17+
"color": "#fbbf24",
18+
"radius": 30
19+
},
20+
"planets": {
21+
"mercury": { "color": "#9ca3af" },
22+
"venus": { "color": "#fde68a" },
23+
"earth": { "color": "#38bdf8" },
24+
"mars": { "color": "#fb7185" },
25+
"jupiter": { "color": "#f59e0b" },
26+
"saturn": { "color": "#eab308" },
27+
"uranus": { "color": "#67e8f9" },
28+
"neptune": { "color": "#60a5fa" }
29+
},
30+
"moons": {
31+
"moon": { "color": "#e5e7eb" },
32+
"io": { "color": "#fde68a" },
33+
"europa": { "color": "#dbeafe" },
34+
"ganymede": { "color": "#cbd5e1" },
35+
"titan": { "color": "#fef3c7" }
36+
},
37+
"rings": {
38+
"jupiter": "rgba(245, 158, 11, 0.22)",
39+
"saturn": "rgba(253, 230, 138, 0.55)",
40+
"uranus": "rgba(103, 232, 249, 0.28)",
41+
"neptune": "rgba(96, 165, 250, 0.24)"
42+
}
43+
}
44+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"schema": "html-js-gaming.game-asset-catalog",
3+
"version": 1,
4+
"gameId": "solarsystem",
5+
"assets": {
6+
"skin.main": {
7+
"path": "/games/SolarSystem/assets/skins/default.json",
8+
"kind": "skin",
9+
"source": "workspace-manager"
10+
}
11+
}
12+
}

games/SolarSystem/game/SolarSystemScene.js

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import SolarSystemWorld from './SolarSystemWorld.js';
99

1010
const VIEW = { width: 960, height: 720 };
1111

12-
const COLORS = {
12+
const DEFAULT_COLORS = {
1313
background: '#030712',
1414
frame: '#dbeafe',
1515
orbit: '#334155',
@@ -18,16 +18,38 @@ const COLORS = {
1818
panel: '#07101d',
1919
};
2020

21+
function toObject(value) {
22+
return value && typeof value === 'object' ? value : {};
23+
}
24+
25+
function sanitizeSolarSceneColors(skin) {
26+
const colors = toObject(skin?.colors);
27+
return {
28+
background: typeof colors.background === 'string' && colors.background.trim() ? colors.background.trim() : DEFAULT_COLORS.background,
29+
frame: typeof colors.frame === 'string' && colors.frame.trim() ? colors.frame.trim() : DEFAULT_COLORS.frame,
30+
orbit: typeof colors.orbit === 'string' && colors.orbit.trim() ? colors.orbit.trim() : DEFAULT_COLORS.orbit,
31+
text: typeof colors.text === 'string' && colors.text.trim() ? colors.text.trim() : DEFAULT_COLORS.text,
32+
muted: typeof colors.muted === 'string' && colors.muted.trim() ? colors.muted.trim() : DEFAULT_COLORS.muted,
33+
panel: typeof colors.panel === 'string' && colors.panel.trim() ? colors.panel.trim() : DEFAULT_COLORS.panel
34+
};
35+
}
36+
2137
export default class SolarSystemScene extends Scene {
22-
constructor() {
38+
constructor(options = {}) {
2339
super();
24-
this.world = new SolarSystemWorld(VIEW);
40+
this.world = new SolarSystemWorld({ ...VIEW, skin: options.skin || null });
41+
this.colors = sanitizeSolarSceneColors(options.skin);
2542
this.isPaused = false;
2643
this.lastPausePressed = false;
2744
this.lastLabelsPressed = false;
2845
this.lastResetPressed = false;
2946
}
3047

48+
applySkin(nextSkin) {
49+
this.colors = sanitizeSolarSceneColors(nextSkin);
50+
this.world.applySkin(nextSkin);
51+
}
52+
3153
update(dt, engine) {
3254
const pausePressed = Boolean(engine.input?.isDown?.('KeyP'));
3355
const labelsPressed = Boolean(engine.input?.isDown?.('KeyL'));
@@ -62,7 +84,7 @@ export default class SolarSystemScene extends Scene {
6284
}
6385

6486
render(renderer) {
65-
renderer.clear(COLORS.background);
87+
renderer.clear(this.colors.background);
6688
this.drawFrame(renderer);
6789
this.drawOrbits(renderer);
6890
this.drawBodies(renderer);
@@ -71,7 +93,7 @@ export default class SolarSystemScene extends Scene {
7193
if (this.isPaused) {
7294
renderer.drawRect(0, 0, VIEW.width, VIEW.height, 'rgba(0, 0, 0, 0.42)');
7395
renderer.drawText('PAUSED', VIEW.width / 2, 324, {
74-
color: COLORS.text,
96+
color: this.colors.text,
7597
font: 'bold 30px monospace',
7698
textAlign: 'center',
7799
});
@@ -85,11 +107,11 @@ export default class SolarSystemScene extends Scene {
85107

86108
drawFrame(renderer) {
87109
const { bounds } = this.world;
88-
renderer.drawRect(0, 0, VIEW.width, 18, COLORS.frame);
89-
renderer.drawRect(0, VIEW.height - 18, VIEW.width, 18, COLORS.frame);
90-
renderer.drawRect(0, 0, 18, VIEW.height, COLORS.frame);
91-
renderer.drawRect(VIEW.width - 18, 0, 18, VIEW.height, COLORS.frame);
92-
renderer.strokeRect(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, COLORS.frame, 2);
110+
renderer.drawRect(0, 0, VIEW.width, 18, this.colors.frame);
111+
renderer.drawRect(0, VIEW.height - 18, VIEW.width, 18, this.colors.frame);
112+
renderer.drawRect(0, 0, 18, VIEW.height, this.colors.frame);
113+
renderer.drawRect(VIEW.width - 18, 0, 18, VIEW.height, this.colors.frame);
114+
renderer.strokeRect(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, this.colors.frame, 2);
93115
}
94116

95117
drawOrbits(renderer) {
@@ -98,7 +120,7 @@ export default class SolarSystemScene extends Scene {
98120
});
99121
}
100122

101-
drawOrbitRing(renderer, centerX, centerY, radiusX, radiusY, color = COLORS.orbit, lineWidth = 1) {
123+
drawOrbitRing(renderer, centerX, centerY, radiusX, radiusY, color = this.colors.orbit, lineWidth = 1) {
102124
const segments = 48;
103125
let previous = null;
104126

@@ -165,7 +187,7 @@ export default class SolarSystemScene extends Scene {
165187

166188
drawLabel(renderer, text, x, y, font = '14px monospace') {
167189
renderer.drawText(text, x, y, {
168-
color: COLORS.text,
190+
color: this.colors.text,
169191
font,
170192
textBaseline: 'top',
171193
});
@@ -174,54 +196,54 @@ export default class SolarSystemScene extends Scene {
174196
drawHud(renderer) {
175197
const { bounds } = this.world;
176198
renderer.drawText('SOLAR SYSTEM', 28, 24, {
177-
color: COLORS.text,
199+
color: this.colors.text,
178200
font: 'bold 24px monospace',
179201
textBaseline: 'top',
180202
});
181203

182-
renderer.drawRect(642, 128, 264, 168, COLORS.panel);
183-
renderer.strokeRect(642, 128, 264, 168, COLORS.frame, 2);
204+
renderer.drawRect(642, 128, 264, 168, this.colors.panel);
205+
renderer.strokeRect(642, 128, 264, 168, this.colors.frame, 2);
184206
renderer.drawText('System View', 660, 148, {
185-
color: COLORS.text,
207+
color: this.colors.text,
186208
font: 'bold 18px monospace',
187209
textBaseline: 'top',
188210
});
189211
renderer.drawText(`Time: ${this.world.elapsedDays.toFixed(1)} days`, 660, 182, {
190-
color: COLORS.text,
212+
color: this.colors.text,
191213
font: '16px monospace',
192214
textBaseline: 'top',
193215
});
194216
renderer.drawText(`Rate: ${this.world.getTimeScale().label}`, 660, 208, {
195-
color: COLORS.text,
217+
color: this.colors.text,
196218
font: '16px monospace',
197219
textBaseline: 'top',
198220
});
199221
renderer.drawText(`Labels: ${this.world.labelsVisible ? 'ON' : 'OFF'}`, 660, 234, {
200-
color: COLORS.text,
222+
color: this.colors.text,
201223
font: '16px monospace',
202224
textBaseline: 'top',
203225
});
204226
renderer.drawText(`Bodies: ${1 + this.world.planets.length + this.world.moons.length}`, 660, 260, {
205-
color: COLORS.text,
227+
color: this.colors.text,
206228
font: '16px monospace',
207229
textBaseline: 'top',
208230
});
209231

210-
renderer.drawRect(642, 314, 264, 192, COLORS.panel);
211-
renderer.strokeRect(642, 314, 264, 192, COLORS.frame, 2);
232+
renderer.drawRect(642, 314, 264, 192, this.colors.panel);
233+
renderer.strokeRect(642, 314, 264, 192, this.colors.frame, 2);
212234
renderer.drawText('Controls', 660, 334, {
213-
color: COLORS.text,
235+
color: this.colors.text,
214236
font: 'bold 18px monospace',
215237
textBaseline: 'top',
216238
});
217-
renderer.drawText('P pause / resume', 660, 368, { color: COLORS.muted, font: '16px monospace', textBaseline: 'top' });
218-
renderer.drawText('L toggle labels', 660, 394, { color: COLORS.muted, font: '16px monospace', textBaseline: 'top' });
219-
renderer.drawText('R reset clock', 660, 420, { color: COLORS.muted, font: '16px monospace', textBaseline: 'top' });
220-
renderer.drawText('1 x1 2 x2', 660, 446, { color: COLORS.muted, font: '16px monospace', textBaseline: 'top' });
221-
renderer.drawText('3 x3 4 x4', 660, 472, { color: COLORS.muted, font: '16px monospace', textBaseline: 'top' });
239+
renderer.drawText('P pause / resume', 660, 368, { color: this.colors.muted, font: '16px monospace', textBaseline: 'top' });
240+
renderer.drawText('L toggle labels', 660, 394, { color: this.colors.muted, font: '16px monospace', textBaseline: 'top' });
241+
renderer.drawText('R reset clock', 660, 420, { color: this.colors.muted, font: '16px monospace', textBaseline: 'top' });
242+
renderer.drawText('1 x1 2 x2', 660, 446, { color: this.colors.muted, font: '16px monospace', textBaseline: 'top' });
243+
renderer.drawText('3 x3 4 x4', 660, 472, { color: this.colors.muted, font: '16px monospace', textBaseline: 'top' });
222244

223245
renderer.drawText('Readable orbital motion, not exact scale.', bounds.left, 686, {
224-
color: COLORS.muted,
246+
color: this.colors.muted,
225247
font: '16px monospace',
226248
textBaseline: 'top',
227249
});

0 commit comments

Comments
 (0)