diff --git a/assets/backgrounds/bg_far_2.png b/assets/backgrounds/bg_far_2.png index 72dee1c..ac7ffeb 100644 Binary files a/assets/backgrounds/bg_far_2.png and b/assets/backgrounds/bg_far_2.png differ diff --git a/src/data/levels/index.js b/src/data/levels/index.js index fac7e18..ee9aefd 100644 --- a/src/data/levels/index.js +++ b/src/data/levels/index.js @@ -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 diff --git a/src/data/themes.js b/src/data/themes.js new file mode 100644 index 0000000..029537a --- /dev/null +++ b/src/data/themes.js @@ -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; +} diff --git a/src/scenes/PlayScene.js b/src/scenes/PlayScene.js index d3f6007..924de9e 100644 --- a/src/scenes/PlayScene.js +++ b/src/scenes/PlayScene.js @@ -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) { diff --git a/src/util/assetManifest.js b/src/util/assetManifest.js index b67caa7..d4cf77b 100644 --- a/src/util/assetManifest.js +++ b/src/util/assetManifest.js @@ -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.