'use strict'; import Key from "./Key.js"; import MrCroc from "./MrCroc.js"; import RetroArchitecture from "./retro/RetroArchitecture.js"; import Camera from "./Camera.js"; import Gisela from "./Gisela.js"; import Setting from "./Setting.js"; import FrameRateMeasurer from "./FrameRateMeasurer.js"; import GraphicSet from "./GraphicSet.js"; import ImageLoader from "./ImageLoader.js"; import Level from "./Level.js"; import InterfaceEvent from "./events/InterfaceEvent.js"; import UrlParam from "./UrlParam.js"; import UserInterface from "./ui/UserInterface.js"; import TextMessageGisela from "./ui/TextMessageGisela.js"; import TextMessageMrCroc from "./ui/TextMessageMrCroc.js"; import {LoadLevelDialog} from "./ui/LoadLevelDialog.js"; class Game { static GAME_SPEED = 1 constructor(level) { this.level = level; this.fps = 0; this.frameDuration = 0; this.lastRendered = undefined; this.lastTimestamp = undefined; this.canvas = document.getElementById('canvas'); this.context = this.canvas.getContext('2d'); this.mrCroc = new MrCroc(); this.gisela = new Gisela(); this.architecture = RetroArchitecture.createFromData(this.level); this.camera = new Camera(); this.gameFinished = false; this.hasPlayerLeftArchitecture = false; this.textBoxGameStart = new TextMessageMrCroc('Mr. Croc: "Where is Gisela? I have to find her!"', this.context); this.textBoxGameFinished = new TextMessageGisela( 'Gisela: "Thanks for showing up, Mr. Croc, but I\'m not in danger."', this.context ); this.userInterface = new UserInterface(); this.isPaused = false; this.KeyLeft = new Key('ArrowLeft'); this.KeyRight = new Key('ArrowRight'); this.KeyJump = new Key('Space'); this.KeyLoad = new Key('KeyL'); this.loader = new ImageLoader(); this.loader.addImage(Setting.GRAPHICS_LOCATION + 'mr-croc-walk-right.png'); this.loader.addImage(Setting.GRAPHICS_LOCATION + 'mr-croc-walk-left.png'); this.loader.addImage(Setting.TILESET_LOCATION + GraphicSet[this.level.getTilesetId()].tileset); this.loader.addImage(Setting.GRAPHICS_LOCATION + 'gisela-right.png'); this.loader.addImage(Setting.GRAPHICS_LOCATION + 'gisela-left.png'); new FrameRateMeasurer(); window.addEventListener( 'imagesloaded', () => { this.init(); } ); } render(timestamp) { if (timestamp - this.lastRendered < this.frameDuration) { return; } if (this.gisela.currentAnimation !== 'LOOK_LEFT' && this.mrCroc.position.x < this.gisela.position.x) { this.gisela.currentAnimation = 'LOOK_LEFT'; } else if (this.gisela.currentAnimation !== 'LOOK_RIGHT' && this.mrCroc.position.x >= this.gisela.position.x) { this.gisela.currentAnimation = 'LOOK_RIGHT'; } this.context.clearRect(0, 0, window.innerWidth, window.innerHeight); this.architecture.draw(this.context, this.camera); this.mrCroc.draw(this.context, this.camera); this.gisela.draw(this.context, this.camera); this.userInterface.draw(this.context); this.lastRendered = timestamp; } canBeFinished() { return ( !this.gameFinished && this.mrCroc.isJumping === false && this.architecture.isMovableInsideTargetPosition(this.mrCroc) ); } handlePhysics(delta, timestamp) { let ceilingHeight = Math.max( this.architecture.getCeilingHeight(this.mrCroc.getPositionHeadLeft()), this.architecture.getCeilingHeight(this.mrCroc.getPositionHeadRight()), ); let groundHeight = Math.min( this.architecture.getGroundHeight(this.mrCroc.getPositionFootLeft()), this.architecture.getGroundHeight(this.mrCroc.getPositionFootRight()) ); /* Handle falling */ this.mrCroc.position.y += this.mrCroc.fallSpeed; this.mrCroc.fallSpeed += this.level.gravity * delta; /* Handle ground collision */ if (this.mrCroc.position.y > groundHeight && this.mrCroc.fallSpeed > 0) { this.mrCroc.position.y = groundHeight; this.mrCroc.fallSpeed = 0; } /* Handle ceiling collision */ if (this.mrCroc.position.y - this.mrCroc.getHeight() <= ceilingHeight) { this.mrCroc.fallSpeed = 0; this.mrCroc.position.y = ceilingHeight + this.mrCroc.getHeight() + 1; } this.handlePlayerMovement(delta, timestamp, groundHeight); } updateCamera() { this.camera.focusPosition( this.mrCroc.position.x - this.mrCroc.getWidth() * 0.5, this.mrCroc.position.y - this.mrCroc.getHeight() * 0.5, 20 ); this.camera.lockCameraIntoBorders(); } handlePlayerMovement(delta, timestamp, groundHeight) { /* Jumping */ if (!this.mrCroc.isJumping && this.mrCroc.fallSpeed === 0 && this.mrCroc.position.y === groundHeight && this.KeyJump.isPressed()) { this.mrCroc.jump(); } else if (!this.KeyJump.isPressed()) { this.mrCroc.isJumping = false; } /* Movement left and right */ if (!this.hasPlayerLeftArchitecture && this.KeyLeft.isPressed()) { let lastWallLeft = Math.min( this.architecture.getWallLeft(this.mrCroc.getPositionHeadLeft()), this.architecture.getWallLeft(this.mrCroc.getPositionFootLeft()) ); this.mrCroc.moveLeft(timestamp, delta); if (this.mrCroc.position.x <= lastWallLeft + this.mrCroc.getWidth() * 0.5) { this.mrCroc.position.x = lastWallLeft + this.mrCroc.getWidth() * 0.5 + 1; } } else if (!this.hasPlayerLeftArchitecture && this.KeyRight.isPressed()) { let lastWallRight = Math.max( this.architecture.getWallRight(this.mrCroc.getPositionHeadRight()), this.architecture.getWallRight(this.mrCroc.getPositionFootRight()) ); this.mrCroc.moveRight(timestamp, delta); if (this.mrCroc.position.x >= lastWallRight - this.mrCroc.getWidth() * 0.5) { this.mrCroc.position.x = lastWallRight - this.mrCroc.getWidth() * 0.5 - 1; } } if (!this.hasPlayerLeftArchitecture && !this.architecture.isInsideArchitecture(this.mrCroc.position)) { this.hasPlayerLeftArchitecture = true; setTimeout( () => { this.architecture.setMovableToStartPosition(this.mrCroc); this.hasPlayerLeftArchitecture = false; }, 2000 ); } } finish() { this.gameFinished = true; this.KeyLeft.pressed = false; this.KeyRight.pressed = false; this.KeyJump.pressed = false; this.lastTimestamp = undefined; this.lastRendered = undefined; this.textBoxGameFinished.updateLines(window.innerWidth - 40, this.context); this.textBoxGameFinished.animate(75); this.userInterface.addTextBox(this.textBoxGameFinished); } init(loopFunction) { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; this.canvas.style.backgroundAttachment = 'fixed'; this.canvas.style.backgroundSize = 'cover'; this.canvas.style.backgroundPosition = 'center center'; if (GraphicSet[this.level.getTilesetId()].backgroundImage !== null) { this.canvas.style.backgroundImage = 'url("' + Setting.GRAPHICS_LOCATION + GraphicSet[this.level.getTilesetId()].backgroundImage +'")'; } this.canvas.style.backgroundColor = this.level.getBackgroundColor(); window.addEventListener( 'resize', function () { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; } ); this.textBoxGameStart.animate(75, 1000); this.textBoxGameStart.show(1000); this.textBoxGameStart.hide(10000); this.userInterface.addTextBox(this.textBoxGameStart); this.camera.borderRight = this.architecture.columns * this.architecture.tileWidth; this.camera.borderBottom = this.architecture.rows * this.architecture.tileHeight; this.architecture.setMovableToStartPosition(this.mrCroc); this.architecture.setMovableToTargetPosition(this.gisela); window.addEventListener( InterfaceEvent.FRAME_RATE_MEASURED, (event) => { this.fps = event.frameRate; this.frameDuration = 1000.0 / this.fps; window.requestAnimationFrame(loopFunction); } ); } } function mainLoop(timestamp) { if (game.isPaused) { return; } if (game.lastRendered === undefined && game.lastTimestamp === undefined) { game.lastRendered = timestamp; game.lastTimestamp = timestamp; } let delta = (timestamp - game.lastTimestamp) / (10 / Game.GAME_SPEED); game.handlePhysics(delta, timestamp); game.updateCamera(); game.render(timestamp); game.lastTimestamp = timestamp; if (game.canBeFinished()) { game.finish(); } if (game.KeyLoad.isPressed()) { const dialog = new LoadLevelDialog(); dialog.onClose = () => { dialog.close(); game.isPaused = false; window.requestAnimationFrame(mainLoop); } dialog.onLoad = (data) => { game = new Game(Level.createFromJson(data)); game.init(); } game.isPaused = true; } window.requestAnimationFrame(mainLoop); } const LEVEL = [ 'level01.json', 'moon.json', 'moonbase.json', 'terrain8.json', ]; const urlGetter = new UrlParam(); const levelIndex = urlGetter.getInt('level'); const level = Level.createFromFile( Setting.LEVELS_LOCATION + LEVEL[levelIndex < 0 || levelIndex >= LEVEL.length ? 0 : levelIndex] ); let game = new Game(level); game.init(mainLoop);