Add per-campaign theming system for in-level visuals
- Create `src/data/themes.js` with theme definitions (background color, parallax layers) and a `getTheme()` resolver that falls back to campaign 1's default look - Update `PlayScene` to load the active campaign's theme instead of hardcoding colors/texture keys - Register campaign 2's `_2` background assets in the asset manifest with placeholder fallbacks - Add updated background art for campaign 2 (`bg_far_2.png`)
This commit is contained in:
parent
a728a51cd2
commit
c2abb5c4ea
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 2.4 MiB |
|
|
@ -11,9 +11,10 @@ import level10 from './level10.js';
|
|||
import level11 from './level11.js';
|
||||
|
||||
// Campaigns group levels into themed "maps" for the level select screen.
|
||||
// Each campaign gets its own background art (bgKey) and theme, so adding a
|
||||
// new campaign is: create its levels, add a bg image + manifest entry, add a
|
||||
// row here. Node positions are in 960x540 design units (NOT multiplied by
|
||||
// Each campaign gets its own background art (bgKey), menu theme music
|
||||
// (musicKey), and in-level look (data/themes.js), so adding a new campaign
|
||||
// is: create its levels, add a bg image + manifest entry, add an entry to
|
||||
// THEMES, add a row here. Node positions are in 960x540 design units (NOT multiplied by
|
||||
// WORLD_SCALE - the scene scales them), and the path is rendered as a line
|
||||
// through them in order. `positions` may be omitted to auto-place the
|
||||
// campaign's levels: a 2-lane circuit (top lane right->left, bottom lane
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// Per-campaign themes for the IN-LEVEL (play) presentation - the parallax
|
||||
// background stack and base camera color shown while actually driving a
|
||||
// level. This is distinct from a campaign's *map* art (bgKey) and menu
|
||||
// music (musicKey), which live on the campaign object in data/levels/index.js.
|
||||
//
|
||||
// Adding a new campaign: add one entry below keyed by the campaign's `id`
|
||||
// (see data/levels/index.js). You may set the whole object or only the bits
|
||||
// you want to change; any field you leave out (or the entire entry) falls
|
||||
// back to DEFAULT_THEME - the campaign 1 "Evergreen Lake" look. So a brand
|
||||
// new campaign is already themed by default and only needs an entry for
|
||||
// whatever it wants to customize (swap the art, tweak the base color, add a
|
||||
// new theme axis for this campaign only, etc.).
|
||||
//
|
||||
// Every texture key referenced here must exist in util/assetManifest.js so
|
||||
// PreloadScene loads it (and a placeholder is generated if the file is
|
||||
// missing).
|
||||
|
||||
// The default / campaign 1 look. Other campaigns inherit from this for any
|
||||
// field they don't override, so this stays the single source for "the
|
||||
// baseline art".
|
||||
export const DEFAULT_THEME = {
|
||||
// Camera background color, drawn behind the parallax stack and only
|
||||
// visible through gaps. A hex string, the same format the scenes pass to
|
||||
// Phaser's setBackgroundColor().
|
||||
bgColor: '#8fc7e8',
|
||||
// The three parallax layers, back to front. `far` is the full-viewport
|
||||
// sky; `mid`/`near` are bottom-anchored ground tiles (see PlayScene's
|
||||
// _buildParallax). Keys are texture keys, not file paths.
|
||||
parallax: {
|
||||
far: 'bg_far',
|
||||
mid: 'bg_mid',
|
||||
near: 'bg_near',
|
||||
},
|
||||
};
|
||||
|
||||
export const THEMES = {
|
||||
// Campaign 1 uses the baseline art as-is.
|
||||
campaign01: DEFAULT_THEME,
|
||||
|
||||
// Campaign 2 ("Underprivileged Community") - the urban _2 background set,
|
||||
// with a dusk base color matched to bg_far_2's sky.
|
||||
campaign02: {
|
||||
bgColor: '#92656d',
|
||||
parallax: {
|
||||
far: 'bg_far_2',
|
||||
mid: 'bg_mid_2',
|
||||
near: 'bg_near_2',
|
||||
},
|
||||
},
|
||||
// Add campaign03: { ... } here when it's ready.
|
||||
};
|
||||
|
||||
// Resolves the theme for a campaign id, always returning a complete theme
|
||||
// (falls back to the default for unknown/absent campaigns).
|
||||
export function getTheme(campaignId) {
|
||||
return THEMES[campaignId] || DEFAULT_THEME;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import Phaser from 'phaser';
|
||||
import { GAME_WIDTH, GAME_HEIGHT, DEBUG, WORLD_SCALE, COMPARTMENT_DEBUG } from '../config.js';
|
||||
import { getLevelById } from '../data/levels/index.js';
|
||||
import { getLevelById, getCampaignOfLevel } from '../data/levels/index.js';
|
||||
import { getTheme } from '../data/themes.js';
|
||||
import Bus from '../entities/Bus.js';
|
||||
import Terrain from '../entities/Terrain.js';
|
||||
import FinishLine from '../entities/FinishLine.js';
|
||||
|
|
@ -33,7 +34,13 @@ export default class PlayScene extends Phaser.Scene {
|
|||
create() {
|
||||
stopMenuMusic(this);
|
||||
|
||||
this.cameras.main.setBackgroundColor('#8fc7e8');
|
||||
// Per-campaign theme (data/themes.js) - drives the base color and the
|
||||
// parallax art below. Campaign 1 is the default look; each new campaign
|
||||
// can override any of it.
|
||||
const campaign = getCampaignOfLevel(this.levelId);
|
||||
this.theme = getTheme(campaign ? campaign.id : null);
|
||||
|
||||
this.cameras.main.setBackgroundColor(this.theme.bgColor);
|
||||
|
||||
this._buildParallax();
|
||||
|
||||
|
|
@ -111,9 +118,9 @@ export default class PlayScene extends Phaser.Scene {
|
|||
// manually scrubs its tilePositionX each frame based on progress from
|
||||
// start to goal, so the single image pans start-to-end exactly once
|
||||
// over the course of the level.
|
||||
const farNative = this._nativeTextureSize('bg_far');
|
||||
const farNative = this._nativeTextureSize(this.theme.parallax.far);
|
||||
const farScale = GAME_HEIGHT / farNative.height;
|
||||
this.bgFar = this.add.tileSprite(0, 0, GAME_WIDTH, GAME_HEIGHT, 'bg_far').setOrigin(0, 0);
|
||||
this.bgFar = this.add.tileSprite(0, 0, GAME_WIDTH, GAME_HEIGHT, this.theme.parallax.far).setOrigin(0, 0);
|
||||
this.bgFar.setScrollFactor(0, 0);
|
||||
this.bgFar.tileScaleX = farScale;
|
||||
this.bgFar.tileScaleY = farScale;
|
||||
|
|
@ -125,8 +132,8 @@ export default class PlayScene extends Phaser.Scene {
|
|||
// count isn't pinned to an exact number - it falls out of each layer's
|
||||
// target height, and near's is shorter than mid's, so it naturally
|
||||
// repeats more often across the same span.
|
||||
this._addGroundLayer('bg_mid', bounds.x, width, GAME_HEIGHT * 0.6, 0.45);
|
||||
this._addGroundLayer('bg_near', bounds.x, width, GAME_HEIGHT * 0.35, 0.75);
|
||||
this._addGroundLayer(this.theme.parallax.mid, bounds.x, width, GAME_HEIGHT * 0.6, 0.45);
|
||||
this._addGroundLayer(this.theme.parallax.near, bounds.x, width, GAME_HEIGHT * 0.35, 0.75);
|
||||
}
|
||||
|
||||
_nativeTextureSize(key) {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,15 @@ export const ASSET_MANIFEST = [
|
|||
// 9 frames of 64x64 bonus items (CTC box + 2 firework frames, guitar + 2,
|
||||
// cash + 2 - see BONUS_ITEM in config.js and BonusItemManager.js).
|
||||
{ key: 'bonus_items', path: 'assets/sprites/bonus-items.png', width: 64 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0xe0a040, frameWidth: 64, frameHeight: 64 },
|
||||
// Campaign 1 ("Evergreen Lake") parallax set - see DEFAULT_THEME in
|
||||
// data/themes.js. The _2 set is campaign 2's; both are 3136x672 natively
|
||||
// (width/height below only feed the placeholder fallback, never display).
|
||||
{ key: 'bg_far', path: 'assets/backgrounds/bg_far.png', width: 256 * WORLD_SCALE, height: 540 * WORLD_SCALE, kind: 'rect', color: 0x8fc7e8, tileable: true },
|
||||
{ key: 'bg_mid', path: 'assets/backgrounds/bg_mid.png', width: 256 * WORLD_SCALE, height: 540 * WORLD_SCALE, kind: 'rect', color: 0x6fae7d, tileable: true },
|
||||
{ key: 'bg_near', path: 'assets/backgrounds/bg_near.png', width: 256 * WORLD_SCALE, height: 540 * WORLD_SCALE, kind: 'rect', color: 0x4c8a5c, tileable: true },
|
||||
{ key: 'bg_far_2', path: 'assets/backgrounds/bg_far_2.png', width: 256 * WORLD_SCALE, height: 540 * WORLD_SCALE, kind: 'rect', color: 0x92656d, tileable: true },
|
||||
{ key: 'bg_mid_2', path: 'assets/backgrounds/bg_mid_2.png', width: 256 * WORLD_SCALE, height: 540 * WORLD_SCALE, kind: 'rect', color: 0x644c44, tileable: true },
|
||||
{ key: 'bg_near_2', path: 'assets/backgrounds/bg_near_2.png', width: 256 * WORLD_SCALE, height: 540 * WORLD_SCALE, kind: 'rect', color: 0x554239, tileable: true },
|
||||
// Intro screen's poster art (960x540 native design aspect) and the
|
||||
// standalone wordmark graphic IntroScene layers on top of it, animated -
|
||||
// see IntroScene._buildLogo.
|
||||
|
|
|
|||
Loading…
Reference in New Issue