diff --git a/public/js/scenes/CompositionScene.js b/public/js/CompositionScene.js similarity index 51% rename from public/js/scenes/CompositionScene.js rename to public/js/CompositionScene.js index 47d28b7c..6d239d78 100644 --- a/public/js/scenes/CompositionScene.js +++ b/public/js/CompositionScene.js @@ -1,18 +1,12 @@ -import Scene from "../Scene.js"; +import Scene from './Scene.js'; export default class CompositionScene extends Scene { constructor() { super(); - this.layers = []; this.countDown = 2; } update(gameContext) { - const videoContext = gameContext.videoContext; - videoContext.fillRect(0, 0, videoContext.canvas.width, videoContext.canvas.height); - for (const layer of this.layers) { - layer(videoContext); - } this.countDown -= gameContext.deltaTime; if (this.countDown <= 0) { this.events.emit(Scene.EVENT_COMPLETE); diff --git a/public/js/Level.js b/public/js/Level.js index fa7b073e..0c0ad59f 100644 --- a/public/js/Level.js +++ b/public/js/Level.js @@ -3,31 +3,31 @@ import Compositor from './Compositor.js'; import EventEmitter from './EventEmitter.js'; import MusicController from './MusicController.js'; import EntityCollider from './EntityCollider.js'; +import Scene from './Scene.js'; import TileCollider from './TileCollider.js'; import { findPlayers } from './player.js'; function focusPlayer(level) { for (const player of findPlayers(level)) { level.camera.pos.x = Math.max(0, player.pos.x - 100); - return; } } -export default class Level { - static EVENT_GOTO_SCENE = Symbol('go to scene event'); +export default class Level extends Scene { + static EVENT_TRIGGER = Symbol('trigger'); constructor() { + super(); + this.name = ""; this.gravity = 1500; this.totalTime = 0; this.camera = new Camera(); - this.events = new EventEmitter(); this.music = new MusicController(); - this.comp = new Compositor(); this.entities = new Set(); this.entityCollider = new EntityCollider(this.entities); @@ -55,4 +55,8 @@ export default class Level { this.totalTime += gameContext.deltaTime; } + + pause() { + this.music.pause(); + } } diff --git a/public/js/MusicController.js b/public/js/MusicController.js index 3ee86e25..8b415601 100644 --- a/public/js/MusicController.js +++ b/public/js/MusicController.js @@ -20,7 +20,7 @@ export default class MusicController { }, {once: true}); } - stop() { - this.player.pause(); + pause() { + this.player.pauseAll(); } } diff --git a/public/js/MusicPlayer.js b/public/js/MusicPlayer.js index 0d593c3e..0d417128 100644 --- a/public/js/MusicPlayer.js +++ b/public/js/MusicPlayer.js @@ -11,13 +11,13 @@ export default class MusicPlayer { } playTrack(name) { - this.pause(); + this.pauseAll(); const audio = this.tracks.get(name); audio.play(); return audio; } - pause() { + pauseAll() { for (const audio of this.tracks.values()) { audio.pause(); } diff --git a/public/js/Scene.js b/public/js/Scene.js index 74a7279f..34c84143 100644 --- a/public/js/Scene.js +++ b/public/js/Scene.js @@ -1,12 +1,22 @@ -import EventEmitter from "./EventEmitter.js"; +import Compositor from './Compositor.js'; +import EventEmitter from './EventEmitter.js'; export default class Scene { static EVENT_COMPLETE = Symbol('scene complete'); constructor() { this.events = new EventEmitter(); + this.comp = new Compositor(); } - end() { + draw(gameContext) { + this.comp.draw(gameContext.videoContext); + } + + update(gameContext) { + } + + pause() { + console.log("Pause", this); } } diff --git a/public/js/SceneRunner.js b/public/js/SceneRunner.js index 8e30d6ec..d4a13b0f 100644 --- a/public/js/SceneRunner.js +++ b/public/js/SceneRunner.js @@ -1,4 +1,4 @@ -import Scene from "./Scene.js"; +import Scene from './Scene.js'; export default class SceneRunner { constructor() { @@ -16,16 +16,16 @@ export default class SceneRunner { runNext() { const currentScene = this.scenes[this.sceneIndex]; if (currentScene) { - currentScene.end(); + currentScene.pause(); } this.sceneIndex++; } update(gameContext) { const currentScene = this.scenes[this.sceneIndex]; - if (!currentScene) { - return; + if (currentScene) { + currentScene.update(gameContext); + currentScene.draw(gameContext); } - currentScene.update(gameContext); } } diff --git a/public/js/input.js b/public/js/input.js index d7caa9b1..d4856378 100644 --- a/public/js/input.js +++ b/public/js/input.js @@ -6,6 +6,7 @@ export function setupKeyboard(window) { const router = new InputRouter(); input.listenTo(window); + input.addMapping('KeyP', keyState => { if (keyState) { router.route(entity => entity.jump.start()); diff --git a/public/js/layers/color.js b/public/js/layers/color.js new file mode 100644 index 00000000..c3921207 --- /dev/null +++ b/public/js/layers/color.js @@ -0,0 +1,6 @@ +export function createColorLayer(color) { + return function drawColor(context) { + context.fillStyle = color; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + }; +} diff --git a/public/js/layers/dashboard.js b/public/js/layers/dashboard.js index 79b29668..b6675d21 100644 --- a/public/js/layers/dashboard.js +++ b/public/js/layers/dashboard.js @@ -14,25 +14,24 @@ function getTimerTrait(level) { } } -export function createDashboardRenderer(font) { +export function createDashboardLayer(font, level) { const LINE1 = font.size; const LINE2 = font.size * 2; - return function createDashboardLayer(level) { + const timerTrait = getTimerTrait(level); + + return function drawDashboard(context) { const playerTrait = getPlayerTrait(level); - const timerTrait = getTimerTrait(level); - return function drawDashboard(context) { - font.print(playerTrait.name, context, 16, LINE1); - font.print(playerTrait.score.toString().padStart(6, '0'), context, 16, LINE2); + font.print(playerTrait.name, context, 16, LINE1); + font.print(playerTrait.score.toString().padStart(6, '0'), context, 16, LINE2); - font.print('@x' + playerTrait.coins.toString().padStart(2, '0'), context, 96, LINE2); + font.print('@x' + playerTrait.coins.toString().padStart(2, '0'), context, 96, LINE2); - font.print('WORLD', context, 152, LINE1); - font.print(level.name.slice(0, 4), context, 160, LINE2); + font.print('WORLD', context, 152, LINE1); + font.print(level.name, context, 160, LINE2); - font.print('TIME', context, 208, LINE1); - font.print(timerTrait.currentTime.toFixed().toString().padStart(3, '0'), context, 216, LINE2); - }; + font.print('TIME', context, 208, LINE1); + font.print(timerTrait.currentTime.toFixed().toString().padStart(3, '0'), context, 216, LINE2); }; -}; +} diff --git a/public/js/layers/player-progress.js b/public/js/layers/player-progress.js new file mode 100644 index 00000000..1ea8b4a4 --- /dev/null +++ b/public/js/layers/player-progress.js @@ -0,0 +1,29 @@ +import {findPlayers} from "../player.js"; + +function getPlayer(level) { + for (const entity of findPlayers(level)) { + return entity; + } +} + +export function createPlayerProgressLayer(font, level) { + const size = font.size; + + const spriteBuffer = document.createElement('canvas'); + spriteBuffer.width = 32; + spriteBuffer.height = 32; + const spriteBufferContext = spriteBuffer.getContext('2d'); + + return function drawPlayerProgress(context) { + const entity = getPlayer(level); + font.print('WORLD ' + level.name, context, size * 12, size * 12); + + font.print('x ' + entity.player.lives.toString().padStart(3, ' '), + context, size * 16, size * 16); + + spriteBufferContext.clearRect(0, 0, + spriteBuffer.width, spriteBuffer.height); + entity.draw(spriteBufferContext); + context.drawImage(spriteBuffer, size * 12, size * 15); + }; +} diff --git a/public/js/layers/wait-screen.js b/public/js/layers/wait-screen.js deleted file mode 100644 index d49cfb84..00000000 --- a/public/js/layers/wait-screen.js +++ /dev/null @@ -1,27 +0,0 @@ -import {findPlayers} from "../player.js"; - -function getPlayer(level) { - for (const entity of findPlayers(level)) { - return entity; - } -} - -export function createWaitScreenRenderer(font) { - const avatarBuffer = document.createElement('canvas'); - avatarBuffer.width = 32; - avatarBuffer.height = 32; - - const avatarContext = avatarBuffer.getContext('2d'); - - return function createWaitScreenLayer(level) { - const entity = getPlayer(level); - avatarContext.clearRect(0, 0, avatarBuffer.height, avatarBuffer.width); - entity.draw(avatarContext); - - return function drawWaitScreen(context) { - font.print('WORLD ' + level.name, context, 8 * 12, 8 * 12); - font.print('x ' + entity.player.lives.toString().padStart(3, ' '), context, 8 * 16, 8 * 16); - context.drawImage(avatarBuffer, 8 * 12, 8 * 15); - }; - }; -}; diff --git a/public/js/loaders/level.js b/public/js/loaders/level.js index 5b8e0750..12c437e1 100644 --- a/public/js/loaders/level.js +++ b/public/js/loaders/level.js @@ -15,19 +15,9 @@ function createTimer() { return timer; } -function createGotoTrigger(name) { +function createTrigger() { const entity = new Entity(); - entity.size.set(24, 24); - const trigger = new Trigger(); - trigger.conditions.push((touches, _, level) => { - for (const entity of touches) { - if (entity.player) { - level.events.emit(Level.EVENT_GOTO_SCENE, name); - return; - } - } - }); - entity.addTrait(trigger); + entity.addTrait(new Trigger()); return entity; } @@ -72,12 +62,16 @@ function setupTriggers(levelSpec, level) { if (!levelSpec.triggers) { return; } + for (const triggerSpec of levelSpec.triggers) { - if (triggerSpec.type === "goto") { - const trigger = createGotoTrigger(triggerSpec.pointer); - trigger.pos.set(...triggerSpec.pos); - level.entities.add(trigger); - } + const entity = createTrigger(); + entity.trigger.conditions.push((entity, touches, gc, level) => { + level.events.emit(Level.EVENT_TRIGGER, triggerSpec, entity, touches); + }); + console.log(entity); + entity.size.set(64, 64); + entity.pos.set(triggerSpec.pos[0], triggerSpec.pos[1]); + level.entities.add(entity); } } diff --git a/public/js/main.js b/public/js/main.js index 51068f3f..56646464 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,18 +1,16 @@ -import SceneRunner from './SceneRunner.js'; import Level from './Level.js'; -import CompositionScene from './scenes/CompositionScene.js'; -import LevelScene from './scenes/LevelScene.js'; import Timer from './Timer.js'; import {createLevelLoader} from './loaders/level.js'; import {loadFont} from './loaders/font.js'; import {loadEntities} from './entities.js'; import {createPlayer, createPlayerEnv} from './player.js'; import {setupKeyboard} from './input.js'; +import {createColorLayer} from './layers/color.js'; import {createCollisionLayer} from './layers/collision.js'; -import {createDashboardRenderer} from './layers/dashboard.js'; -import {createWaitScreenRenderer} from './layers/wait-screen.js'; -import Player from './traits/Player.js'; -import InputRouter from './InputRouter.js'; +import {createDashboardLayer} from './layers/dashboard.js'; +import SceneRunner from './SceneRunner.js'; +import { createPlayerProgressLayer } from './layers/player-progress.js'; +import CompositionScene from './CompositionScene.js'; async function main(canvas) { const videoContext = canvas.getContext('2d'); @@ -23,57 +21,53 @@ async function main(canvas) { loadFont(), ]); - const createDashboardLayer = createDashboardRenderer(font); - const createWaitScreenLayer = createWaitScreenRenderer(font); - const marioPlayer = new Player(); - marioPlayer.name = 'MARIO'; + const loadLevel = await createLevelLoader(entityFactory); - const mario = entityFactory.mario(); - mario.addTrait(marioPlayer); + const sceneRunner = new SceneRunner(); + const mario = createPlayer(entityFactory.mario()); + mario.player.name = "MARIO"; const inputRouter = setupKeyboard(window); inputRouter.addReceiver(mario); - const loadLevel = await createLevelLoader(entityFactory); + async function runLevel(name) { + const level = await loadLevel(name); - const sceneRunner = new SceneRunner(); + level.events.listen(Level.EVENT_TRIGGER, (spec, trigger, touches) => { + if (spec.type === "goto") { + for (const entity of touches) { + if (entity.player) { + runLevel(spec.name); + return; + } + } + } + }); - async function loadScene(name) { - const level = await loadLevel(name); + const playerProgressLayer = createPlayerProgressLayer(font, level); + const dashboardLayer = createDashboardLayer(font, level); mario.pos.set(0, 0); - mario.vel.set(0, 0); level.entities.add(mario); const playerEnv = createPlayerEnv(mario); level.entities.add(playerEnv); - const dashboardLayer = createDashboardLayer(level); - const waitScreenLayer = createWaitScreenLayer(level); + const waitScreen = new CompositionScene(); + waitScreen.countDown = 2; + waitScreen.comp.layers.push(createColorLayer('#000')); + waitScreen.comp.layers.push(dashboardLayer); + waitScreen.comp.layers.push(playerProgressLayer); + sceneRunner.addScene(waitScreen); level.comp.layers.push(createCollisionLayer(level)); level.comp.layers.push(dashboardLayer); - - level.events.listen(Level.EVENT_GOTO_SCENE, sceneName => { - runScene(sceneName); - }); - - const loadScreenScene = new CompositionScene(); - loadScreenScene.layers.push(dashboardLayer); - loadScreenScene.layers.push(waitScreenLayer); - sceneRunner.addScene(loadScreenScene); - - const levelScene = new LevelScene(level); - sceneRunner.addScene(levelScene); + sceneRunner.addScene(level); sceneRunner.runNext(); } - function runScene(name) { - loadScene(name); - } - const gameContext = { audioContext, videoContext, @@ -89,7 +83,7 @@ async function main(canvas) { timer.start(); - runScene('debug-progression') + runLevel('debug-progression'); } const canvas = document.getElementById('screen'); diff --git a/public/js/scenes/LevelScene.js b/public/js/scenes/LevelScene.js deleted file mode 100644 index b40bc3fd..00000000 --- a/public/js/scenes/LevelScene.js +++ /dev/null @@ -1,18 +0,0 @@ -import Scene from "../Scene.js"; - -export default class LevelScene extends Scene { - constructor(level) { - super(); - this.level = level; - } - - update(gameContext) { - const level = this.level; - level.update(gameContext); - level.draw(gameContext); - } - - end() { - this.level.music.stop(); - } -} diff --git a/public/js/traits/Trigger.js b/public/js/traits/Trigger.js index 52c89d2e..8911c3b8 100644 --- a/public/js/traits/Trigger.js +++ b/public/js/traits/Trigger.js @@ -11,10 +11,10 @@ export default class Trigger extends Trait { this.touches.add(them); } - update(_, gameContext, level) { + update(entity, gameContext, level) { if (this.touches.size > 0) { for (const condition of this.conditions) { - condition(this.touches, gameContext, level); + condition(entity, this.touches, gameContext, level); } this.touches.clear(); } diff --git a/public/levels/1-1.json b/public/levels/1-1.json index 10d0bc2d..b9f2130b 100644 --- a/public/levels/1-1.json +++ b/public/levels/1-1.json @@ -256,5 +256,13 @@ "name": "cannon", "pos": [96, 112] } + ], + + "triggers": [ + { + "type": "goto", + "name": "1-2", + "pos": [64, 64] + } ] } diff --git a/public/levels/1-2.json b/public/levels/1-2.json index 640a3b71..3a834882 100644 --- a/public/levels/1-2.json +++ b/public/levels/1-2.json @@ -29,5 +29,13 @@ } ], - "entities": [] + "entities": [], + + "triggers": [ + { + "type": "goto", + "name": "1-1", + "pos": [64, 64] + } + ] } diff --git a/public/levels/debug-progression.json b/public/levels/debug-progression.json index 53558638..77ba65f2 100644 --- a/public/levels/debug-progression.json +++ b/public/levels/debug-progression.json @@ -1,7 +1,6 @@ { "spriteSheet": "overworld", "musicSheet": "silent", - "patternSheet": "overworld-pattern", "layers": [ @@ -34,9 +33,9 @@ "triggers": [ { - "pos": [128, 128], "type": "goto", - "pointer": "1-2" + "name": "1-1", + "pos": [64, 64] } ] }