├── .babelrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── favicon.ico ├── package-lock.json ├── package.json ├── src ├── audio │ ├── audioCompleteLevel.mp3 │ ├── audioDescend.mp3 │ ├── audioDie.mp3 │ ├── audioFireFlowerShot.mp3 │ ├── audioFireworkBurst.mp3 │ ├── audioFireworkWhistle.mp3 │ ├── audioGameOver.mp3 │ ├── audioGoombaSquash.mp3 │ ├── audioJump.mp3 │ ├── audioLosePowerUp.mp3 │ ├── audioMusicLevel1.mp3 │ └── audioWinLevel.mp3 ├── img │ ├── background.png │ ├── block.png │ ├── blockTri.png │ ├── flagPole.png │ ├── hills.png │ ├── level2 │ │ ├── background.png │ │ ├── lgPlatform.png │ │ ├── mdPlatform.png │ │ ├── mountain.png │ │ ├── mountains.png │ │ └── sun.png │ ├── lgPlatform.png │ ├── mario │ │ ├── spriteFireFlowerShootLeft.png │ │ └── spriteFireFlowerShootRight.png │ ├── mdPlatform.png │ ├── platform.png │ ├── platformSmallTall.png │ ├── spriteFireFlower.png │ ├── spriteFireFlowerJumpLeft.png │ ├── spriteFireFlowerJumpRight.png │ ├── spriteFireFlowerRunLeft.png │ ├── spriteFireFlowerRunRight.png │ ├── spriteFireFlowerStandLeft.png │ ├── spriteFireFlowerStandRight.png │ ├── spriteGoomba.png │ ├── spriteMarioJumpLeft.png │ ├── spriteMarioJumpRight.png │ ├── spriteMarioRunLeft.png │ ├── spriteMarioRunRight.png │ ├── spriteMarioStandLeft.png │ ├── spriteMarioStandRight.png │ ├── spriteRunLeft.png │ ├── spriteRunRight.png │ ├── spriteStandLeft.png │ ├── spriteStandRight.png │ ├── tPlatform.png │ └── xtPlatform.png ├── index.html └── js │ ├── audio.js │ ├── canvas.js │ ├── images.js │ └── utils.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [ 4 | ["@babel/plugin-transform-runtime"] 5 | ] 6 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | extends: 'eslint:recommended', 7 | parserOptions: { 8 | sourceType: 'module' 9 | }, 10 | rules: { 11 | indent: ['error', 4], 12 | 'linebreak-style': ['error', 'unix'], 13 | quotes: ['error', 'single'], 14 | semi: ['error', 'never'] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .ds_store 3 | dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Canvas Boilerplate is the go-to solution for quickly creating modern canvas pieces using ES6 and webpack. 2 | 3 | ## Getting Started 4 | 5 | 1. Clone the repo: 6 | 7 | git clone https://github.com/christopher4lis/canvas-boilerplate.git 8 | 9 | 2. Install dependencies: 10 | 11 | yarn 12 | 13 | or 14 | 15 | npm install 16 | 17 | 3. Run webpack: 18 | 19 | npm start 20 | 21 | Your canvas piece should open up automatically at http://localhost:3000 and you should see 'HTML CANVAS BOILERPLATE' on hover. 22 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/favicon.ico -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canvas-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "dev": "webpack --config webpack.config.js", 8 | "start": "webpack --config webpack.config.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@babel/runtime": "^7.16.7", 16 | "gsap": "^3.9.1", 17 | "howler": "^2.2.3" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.9.0", 21 | "@babel/plugin-transform-runtime": "^7.16.7", 22 | "@babel/preset-env": "^7.9.0", 23 | "babel-loader": "^8.1.0", 24 | "browser-sync": "^2.26.7", 25 | "browser-sync-webpack-plugin": "^2.2.2", 26 | "file-loader": "^6.2.0", 27 | "html-webpack-plugin": "^4.0.3", 28 | "prettier": "^2.0.2", 29 | "webpack": "^4.42.1", 30 | "webpack-cli": "^3.3.11" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/audio/audioCompleteLevel.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioCompleteLevel.mp3 -------------------------------------------------------------------------------- /src/audio/audioDescend.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioDescend.mp3 -------------------------------------------------------------------------------- /src/audio/audioDie.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioDie.mp3 -------------------------------------------------------------------------------- /src/audio/audioFireFlowerShot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioFireFlowerShot.mp3 -------------------------------------------------------------------------------- /src/audio/audioFireworkBurst.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioFireworkBurst.mp3 -------------------------------------------------------------------------------- /src/audio/audioFireworkWhistle.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioFireworkWhistle.mp3 -------------------------------------------------------------------------------- /src/audio/audioGameOver.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioGameOver.mp3 -------------------------------------------------------------------------------- /src/audio/audioGoombaSquash.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioGoombaSquash.mp3 -------------------------------------------------------------------------------- /src/audio/audioJump.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioJump.mp3 -------------------------------------------------------------------------------- /src/audio/audioLosePowerUp.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioLosePowerUp.mp3 -------------------------------------------------------------------------------- /src/audio/audioMusicLevel1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioMusicLevel1.mp3 -------------------------------------------------------------------------------- /src/audio/audioWinLevel.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/audio/audioWinLevel.mp3 -------------------------------------------------------------------------------- /src/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/background.png -------------------------------------------------------------------------------- /src/img/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/block.png -------------------------------------------------------------------------------- /src/img/blockTri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/blockTri.png -------------------------------------------------------------------------------- /src/img/flagPole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/flagPole.png -------------------------------------------------------------------------------- /src/img/hills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/hills.png -------------------------------------------------------------------------------- /src/img/level2/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/level2/background.png -------------------------------------------------------------------------------- /src/img/level2/lgPlatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/level2/lgPlatform.png -------------------------------------------------------------------------------- /src/img/level2/mdPlatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/level2/mdPlatform.png -------------------------------------------------------------------------------- /src/img/level2/mountain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/level2/mountain.png -------------------------------------------------------------------------------- /src/img/level2/mountains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/level2/mountains.png -------------------------------------------------------------------------------- /src/img/level2/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/level2/sun.png -------------------------------------------------------------------------------- /src/img/lgPlatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/lgPlatform.png -------------------------------------------------------------------------------- /src/img/mario/spriteFireFlowerShootLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/mario/spriteFireFlowerShootLeft.png -------------------------------------------------------------------------------- /src/img/mario/spriteFireFlowerShootRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/mario/spriteFireFlowerShootRight.png -------------------------------------------------------------------------------- /src/img/mdPlatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/mdPlatform.png -------------------------------------------------------------------------------- /src/img/platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/platform.png -------------------------------------------------------------------------------- /src/img/platformSmallTall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/platformSmallTall.png -------------------------------------------------------------------------------- /src/img/spriteFireFlower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteFireFlower.png -------------------------------------------------------------------------------- /src/img/spriteFireFlowerJumpLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteFireFlowerJumpLeft.png -------------------------------------------------------------------------------- /src/img/spriteFireFlowerJumpRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteFireFlowerJumpRight.png -------------------------------------------------------------------------------- /src/img/spriteFireFlowerRunLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteFireFlowerRunLeft.png -------------------------------------------------------------------------------- /src/img/spriteFireFlowerRunRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteFireFlowerRunRight.png -------------------------------------------------------------------------------- /src/img/spriteFireFlowerStandLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteFireFlowerStandLeft.png -------------------------------------------------------------------------------- /src/img/spriteFireFlowerStandRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteFireFlowerStandRight.png -------------------------------------------------------------------------------- /src/img/spriteGoomba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteGoomba.png -------------------------------------------------------------------------------- /src/img/spriteMarioJumpLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteMarioJumpLeft.png -------------------------------------------------------------------------------- /src/img/spriteMarioJumpRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteMarioJumpRight.png -------------------------------------------------------------------------------- /src/img/spriteMarioRunLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteMarioRunLeft.png -------------------------------------------------------------------------------- /src/img/spriteMarioRunRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteMarioRunRight.png -------------------------------------------------------------------------------- /src/img/spriteMarioStandLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteMarioStandLeft.png -------------------------------------------------------------------------------- /src/img/spriteMarioStandRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteMarioStandRight.png -------------------------------------------------------------------------------- /src/img/spriteRunLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteRunLeft.png -------------------------------------------------------------------------------- /src/img/spriteRunRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteRunRight.png -------------------------------------------------------------------------------- /src/img/spriteStandLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteStandLeft.png -------------------------------------------------------------------------------- /src/img/spriteStandRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/spriteStandRight.png -------------------------------------------------------------------------------- /src/img/tPlatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/tPlatform.png -------------------------------------------------------------------------------- /src/img/xtPlatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriscourses/mario-game/cf452b2a295a5f8b226e7847c60dc1179b68a31b/src/img/xtPlatform.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Canvas Boilerplate | Chris Courses 6 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/js/audio.js: -------------------------------------------------------------------------------- 1 | import { Howl } from 'howler' 2 | import audioCompleteLevel from '../audio/audioCompleteLevel.mp3' 3 | import audioDescend from '../audio/audioDescend.mp3' 4 | import audioDie from '../audio/audioDie.mp3' 5 | import audioFireFlowerShot from '../audio/audioFireFlowerShot.mp3' 6 | import audioFireworkBurst from '../audio/audioFireworkBurst.mp3' 7 | import audioFireworkWhistle from '../audio/audioFireworkWhistle.mp3' 8 | import audioGameOver from '../audio/audioGameOver.mp3' 9 | import audioJump from '../audio/audioJump.mp3' 10 | import audioLosePowerUp from '../audio/audioLosePowerUp.mp3' 11 | import audioMusicLevel1 from '../audio/audioMusicLevel1.mp3' 12 | import audioObtainPowerUp from '../audio/audioWinLevel.mp3' 13 | import audioGoombaSquash from '../audio/audioGoombaSquash.mp3' 14 | 15 | export const audio = { 16 | completeLevel: new Howl({ 17 | src: [audioCompleteLevel], 18 | volume: 0.1 19 | }), 20 | descend: new Howl({ 21 | src: [audioDescend], 22 | volume: 0.1 23 | }), 24 | die: new Howl({ 25 | src: [audioDie], 26 | volume: 0.1 27 | }), 28 | fireFlowerShot: new Howl({ 29 | src: [audioFireFlowerShot], 30 | volume: 0.1 31 | }), 32 | fireworkBurst: new Howl({ 33 | src: [audioFireworkBurst], 34 | volume: 0.1 35 | }), 36 | 37 | fireworkWhistle: new Howl({ 38 | src: [audioFireworkWhistle], 39 | volume: 0.1 40 | }), 41 | gameOver: new Howl({ 42 | src: [audioGameOver], 43 | volume: 0.1 44 | }), 45 | jump: new Howl({ 46 | src: [audioJump], 47 | volume: 0.1 48 | }), 49 | losePowerUp: new Howl({ 50 | src: [audioLosePowerUp], 51 | volume: 0.1 52 | }), 53 | musicLevel1: new Howl({ 54 | src: [audioMusicLevel1], 55 | volume: 0.1, 56 | loop: true, 57 | autoplay: true 58 | }), 59 | obtainPowerUp: new Howl({ 60 | src: [audioObtainPowerUp], 61 | volume: 0.1 62 | }), 63 | goombaSquash: new Howl({ 64 | src: [audioGoombaSquash], 65 | volume: 0.1 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /src/js/canvas.js: -------------------------------------------------------------------------------- 1 | import gsap from 'gsap' 2 | import { 3 | createImage, 4 | createImageAsync, 5 | isOnTopOfPlatform, 6 | collisionTop, 7 | isOnTopOfPlatformCircle, 8 | hitBottomOfPlatform, 9 | hitSideOfPlatform, 10 | objectsTouch 11 | } from './utils.js' 12 | 13 | import platform from '../img/platform.png' 14 | import hills from '../img/hills.png' 15 | import background from '../img/background.png' 16 | import platformSmallTall from '../img/platformSmallTall.png' 17 | import block from '../img/block.png' 18 | import blockTri from '../img/blockTri.png' 19 | import mdPlatform from '../img/mdPlatform.png' 20 | import lgPlatform from '../img/lgPlatform.png' 21 | import tPlatform from '../img/tPlatform.png' 22 | import xtPlatform from '../img/xtPlatform.png' 23 | import flagPoleSprite from '../img/flagPole.png' 24 | 25 | import spriteRunLeft from '../img/spriteRunLeft.png' 26 | import spriteRunRight from '../img/spriteRunRight.png' 27 | import spriteStandLeft from '../img/spriteStandLeft.png' 28 | import spriteStandRight from '../img/spriteStandRight.png' 29 | 30 | import spriteMarioRunLeft from '../img/spriteMarioRunLeft.png' 31 | import spriteMarioRunRight from '../img/spriteMarioRunRight.png' 32 | import spriteMarioStandLeft from '../img/spriteMarioStandLeft.png' 33 | import spriteMarioStandRight from '../img/spriteMarioStandRight.png' 34 | import spriteMarioJumpRight from '../img/spriteMarioJumpRight.png' 35 | import spriteMarioJumpLeft from '../img/spriteMarioJumpLeft.png' 36 | 37 | import spriteFireFlowerRunRight from '../img/spriteFireFlowerRunRight.png' 38 | import spriteFireFlowerRunLeft from '../img/spriteFireFlowerRunLeft.png' 39 | import spriteFireFlowerStandRight from '../img/spriteFireFlowerStandRight.png' 40 | import spriteFireFlowerStandLeft from '../img/spriteFireFlowerStandLeft.png' 41 | import spriteFireFlowerJumpRight from '../img/spriteFireFlowerJumpRight.png' 42 | import spriteFireFlowerJumpLeft from '../img/spriteFireFlowerJumpLeft.png' 43 | 44 | import spriteFireFlower from '../img/spriteFireFlower.png' 45 | 46 | import spriteGoomba from '../img/spriteGoomba.png' 47 | import { audio } from './audio.js' 48 | import { images } from './images.js' 49 | 50 | const canvas = document.querySelector('canvas') 51 | const c = canvas.getContext('2d') 52 | 53 | canvas.width = 1024 54 | canvas.height = 576 55 | 56 | let gravity = 1.5 57 | 58 | class Player { 59 | constructor() { 60 | this.shooting = false 61 | this.speed = 10 62 | this.position = { 63 | x: 100, 64 | y: 100 65 | } 66 | this.velocity = { 67 | x: 0, 68 | y: 0 69 | } 70 | 71 | this.scale = 0.3 72 | this.width = 398 * this.scale 73 | this.height = 353 * this.scale 74 | 75 | this.image = createImage(spriteStandRight) 76 | this.frames = 0 77 | this.sprites = { 78 | stand: { 79 | right: createImage(spriteMarioStandRight), 80 | left: createImage(spriteMarioStandLeft), 81 | fireFlower: { 82 | right: createImage(spriteFireFlowerStandRight), 83 | left: createImage(spriteFireFlowerStandLeft) 84 | } 85 | }, 86 | run: { 87 | right: createImage(spriteMarioRunRight), 88 | left: createImage(spriteMarioRunLeft), 89 | fireFlower: { 90 | right: createImage(spriteFireFlowerRunRight), 91 | left: createImage(spriteFireFlowerRunLeft) 92 | } 93 | }, 94 | jump: { 95 | right: createImage(spriteMarioJumpRight), 96 | left: createImage(spriteMarioJumpLeft), 97 | fireFlower: { 98 | right: createImage(spriteFireFlowerJumpRight), 99 | left: createImage(spriteFireFlowerJumpLeft) 100 | } 101 | }, 102 | shoot: { 103 | fireFlower: { 104 | right: createImage(images.mario.shoot.fireFlower.right), 105 | left: createImage(images.mario.shoot.fireFlower.left) 106 | } 107 | } 108 | } 109 | 110 | this.currentSprite = this.sprites.stand.right 111 | this.currentCropWidth = 398 112 | this.powerUps = { 113 | fireFlower: false 114 | } 115 | this.invincible = false 116 | this.opacity = 1 117 | } 118 | 119 | draw() { 120 | c.save() 121 | c.globalAlpha = this.opacity 122 | c.fillStyle = 'rgba(255, 0, 0, .2)' 123 | c.fillRect(this.position.x, this.position.y, this.width, this.height) 124 | c.drawImage( 125 | this.currentSprite, 126 | this.currentCropWidth * this.frames, 127 | 0, 128 | this.currentCropWidth, 129 | 353, 130 | this.position.x, 131 | this.position.y, 132 | this.width, 133 | this.height 134 | ) 135 | c.restore() 136 | } 137 | 138 | update() { 139 | this.frames++ 140 | const { currentSprite, sprites } = this 141 | 142 | if ( 143 | this.frames > 58 && 144 | (currentSprite === sprites.stand.right || 145 | currentSprite === sprites.stand.left || 146 | currentSprite === sprites.stand.fireFlower.left || 147 | currentSprite === sprites.stand.fireFlower.right) 148 | ) 149 | this.frames = 0 150 | else if ( 151 | this.frames > 28 && 152 | (currentSprite === sprites.run.right || 153 | currentSprite === sprites.run.left || 154 | currentSprite === sprites.run.fireFlower.right || 155 | currentSprite === sprites.run.fireFlower.left) 156 | ) 157 | this.frames = 0 158 | else if ( 159 | currentSprite === sprites.jump.right || 160 | currentSprite === sprites.jump.left || 161 | currentSprite === sprites.jump.fireFlower.right || 162 | currentSprite === sprites.jump.fireFlower.left || 163 | currentSprite === sprites.shoot.fireFlower.left || 164 | currentSprite === sprites.shoot.fireFlower.right 165 | ) 166 | this.frames = 0 167 | 168 | this.draw() 169 | this.position.x += this.velocity.x 170 | this.position.y += this.velocity.y 171 | 172 | if (this.position.y + this.height + this.velocity.y <= canvas.height) 173 | this.velocity.y += gravity 174 | 175 | if (this.invincible) { 176 | if (this.opacity === 1) this.opacity = 0 177 | else this.opacity = 1 178 | } else this.opacity = 1 179 | } 180 | } 181 | 182 | class Platform { 183 | constructor({ x, y, image, block, text }) { 184 | this.position = { 185 | x, 186 | y 187 | } 188 | 189 | this.velocity = { 190 | x: 0 191 | } 192 | 193 | this.image = image 194 | this.width = image.width 195 | this.height = image.height 196 | this.block = block 197 | this.text = text 198 | } 199 | 200 | draw() { 201 | c.drawImage(this.image, this.position.x, this.position.y) 202 | 203 | if (this.text) { 204 | c.font = '20px Arial' 205 | c.fillStyle = 'red' 206 | c.fillText(this.text, this.position.x, this.position.y) 207 | } 208 | } 209 | 210 | update() { 211 | this.draw() 212 | this.position.x += this.velocity.x 213 | } 214 | } 215 | 216 | class GenericObject { 217 | constructor({ x, y, image }) { 218 | this.position = { 219 | x, 220 | y 221 | } 222 | 223 | this.velocity = { 224 | x: 0 225 | } 226 | 227 | this.image = image 228 | this.width = image.width 229 | this.height = image.height 230 | } 231 | 232 | draw() { 233 | c.drawImage(this.image, this.position.x, this.position.y) 234 | } 235 | 236 | update() { 237 | this.draw() 238 | this.position.x += this.velocity.x 239 | } 240 | } 241 | 242 | class Goomba { 243 | constructor({ 244 | position, 245 | velocity, 246 | distance = { 247 | limit: 50, 248 | traveled: 0 249 | } 250 | }) { 251 | this.position = { 252 | x: position.x, 253 | y: position.y 254 | } 255 | 256 | this.velocity = { 257 | x: velocity.x, 258 | y: velocity.y 259 | } 260 | 261 | this.width = 43.33 262 | this.height = 50 263 | 264 | this.image = createImage(spriteGoomba) 265 | this.frames = 0 266 | 267 | this.distance = distance 268 | } 269 | 270 | draw() { 271 | // c.fillStyle = 'red' 272 | // c.fillRect(this.position.x, this.position.y, this.width, this.height) 273 | 274 | c.drawImage( 275 | this.image, 276 | 130 * this.frames, 277 | 0, 278 | 130, 279 | 150, 280 | this.position.x, 281 | this.position.y, 282 | this.width, 283 | this.height 284 | ) 285 | } 286 | 287 | update() { 288 | this.frames++ 289 | if (this.frames >= 58) this.frames = 0 290 | this.draw() 291 | this.position.x += this.velocity.x 292 | this.position.y += this.velocity.y 293 | 294 | if (this.position.y + this.height + this.velocity.y <= canvas.height) 295 | this.velocity.y += gravity 296 | 297 | // walk the goomba back and forth 298 | this.distance.traveled += Math.abs(this.velocity.x) 299 | 300 | if (this.distance.traveled > this.distance.limit) { 301 | this.distance.traveled = 0 302 | this.velocity.x = -this.velocity.x 303 | } 304 | } 305 | } 306 | 307 | class FireFlower { 308 | constructor({ position, velocity }) { 309 | this.position = { 310 | x: position.x, 311 | y: position.y 312 | } 313 | 314 | this.velocity = { 315 | x: velocity.x, 316 | y: velocity.y 317 | } 318 | 319 | this.width = 56 320 | this.height = 60 321 | 322 | this.image = createImage(spriteFireFlower) 323 | this.frames = 0 324 | } 325 | 326 | draw() { 327 | // c.fillStyle = 'red' 328 | // c.fillRect(this.position.x, this.position.y, this.width, this.height) 329 | 330 | c.drawImage( 331 | this.image, 332 | 56 * this.frames, 333 | 0, 334 | 56, 335 | 60, 336 | this.position.x, 337 | this.position.y, 338 | this.width, 339 | this.height 340 | ) 341 | } 342 | 343 | update() { 344 | this.frames++ 345 | if (this.frames >= 75) this.frames = 0 346 | this.draw() 347 | this.position.x += this.velocity.x 348 | this.position.y += this.velocity.y 349 | 350 | if (this.position.y + this.height + this.velocity.y <= canvas.height) 351 | this.velocity.y += gravity 352 | } 353 | } 354 | 355 | class Particle { 356 | constructor({ 357 | position, 358 | velocity, 359 | radius, 360 | color = '#654428', 361 | fireball = false, 362 | fades = false 363 | }) { 364 | this.position = { 365 | x: position.x, 366 | y: position.y 367 | } 368 | 369 | this.velocity = { 370 | x: velocity.x, 371 | y: velocity.y 372 | } 373 | 374 | this.radius = radius 375 | this.ttl = 300 376 | this.color = color 377 | this.fireball = fireball 378 | this.opacity = 1 379 | this.fades = fades 380 | } 381 | 382 | draw() { 383 | c.save() 384 | c.globalAlpha = this.opacity 385 | c.beginPath() 386 | c.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2, false) 387 | c.fillStyle = this.color 388 | c.fill() 389 | c.closePath() 390 | c.restore() 391 | } 392 | 393 | update() { 394 | this.ttl-- 395 | this.draw() 396 | this.position.x += this.velocity.x 397 | this.position.y += this.velocity.y 398 | 399 | if (this.position.y + this.radius + this.velocity.y <= canvas.height) 400 | this.velocity.y += gravity * 0.4 401 | 402 | if (this.fades && this.opacity > 0) { 403 | this.opacity -= 0.01 404 | } 405 | 406 | if (this.opacity < 0) this.opacity = 0 407 | } 408 | } 409 | 410 | let platformImage 411 | let platformSmallTallImage 412 | let blockTriImage 413 | let lgPlatformImage 414 | let tPlatformImage 415 | let xtPlatformImage 416 | let blockImage 417 | 418 | let player = new Player() 419 | let platforms = [] 420 | let genericObjects = [] 421 | let goombas = [] 422 | let particles = [] 423 | let fireFlowers = [] 424 | 425 | let lastKey 426 | let keys 427 | 428 | let scrollOffset 429 | let flagPole 430 | let flagPoleImage 431 | let game 432 | let currentLevel = 1 433 | 434 | function selectLevel(currentLevel) { 435 | if (!audio.musicLevel1.playing()) audio.musicLevel1.play() 436 | switch (currentLevel) { 437 | case 1: 438 | init() 439 | break 440 | case 2: 441 | initLevel2() 442 | break 443 | } 444 | } 445 | 446 | async function init() { 447 | player = new Player() 448 | keys = { 449 | right: { 450 | pressed: false 451 | }, 452 | left: { 453 | pressed: false 454 | } 455 | } 456 | scrollOffset = 0 457 | 458 | game = { 459 | disableUserInput: false 460 | } 461 | 462 | platformImage = await createImageAsync(platform) 463 | platformSmallTallImage = await createImageAsync(platformSmallTall) 464 | blockTriImage = await createImageAsync(blockTri) 465 | blockImage = await createImageAsync(block) 466 | lgPlatformImage = await createImageAsync(lgPlatform) 467 | tPlatformImage = await createImageAsync(tPlatform) 468 | xtPlatformImage = await createImageAsync(xtPlatform) 469 | flagPoleImage = await createImageAsync(flagPoleSprite) 470 | 471 | flagPole = new GenericObject({ 472 | x: 6968 + 600, 473 | // x: 500, 474 | y: canvas.height - lgPlatformImage.height - flagPoleImage.height, 475 | image: flagPoleImage 476 | }) 477 | 478 | fireFlowers = [ 479 | new FireFlower({ 480 | position: { 481 | x: 400, 482 | y: 100 483 | }, 484 | velocity: { 485 | x: 0, 486 | y: 0 487 | } 488 | }) 489 | ] 490 | 491 | player = new Player() 492 | 493 | const goombaWidth = 43.33 494 | goombas = [ 495 | new Goomba({ 496 | position: { 497 | x: 908 + lgPlatformImage.width - goombaWidth, 498 | y: 100 499 | }, 500 | velocity: { 501 | x: -0.3, 502 | y: 0 503 | }, 504 | distance: { 505 | limit: 400, 506 | traveled: 0 507 | } 508 | }), 509 | new Goomba({ 510 | position: { 511 | x: 3249 + lgPlatformImage.width - goombaWidth, 512 | y: 100 513 | }, 514 | velocity: { 515 | x: -0.3, 516 | y: 0 517 | }, 518 | distance: { 519 | limit: 400, 520 | traveled: 0 521 | } 522 | }), 523 | new Goomba({ 524 | position: { 525 | x: 3249 + lgPlatformImage.width - goombaWidth - goombaWidth, 526 | y: 100 527 | }, 528 | velocity: { 529 | x: -0.3, 530 | y: 0 531 | }, 532 | distance: { 533 | limit: 400, 534 | traveled: 0 535 | } 536 | }), 537 | new Goomba({ 538 | position: { 539 | x: 540 | 3249 + 541 | lgPlatformImage.width - 542 | goombaWidth - 543 | goombaWidth - 544 | goombaWidth, 545 | y: 100 546 | }, 547 | velocity: { 548 | x: -0.3, 549 | y: 0 550 | }, 551 | distance: { 552 | limit: 400, 553 | traveled: 0 554 | } 555 | }), 556 | new Goomba({ 557 | position: { 558 | x: 559 | 3249 + 560 | lgPlatformImage.width - 561 | goombaWidth - 562 | goombaWidth - 563 | goombaWidth - 564 | goombaWidth, 565 | y: 100 566 | }, 567 | velocity: { 568 | x: -0.3, 569 | y: 0 570 | }, 571 | distance: { 572 | limit: 400, 573 | traveled: 0 574 | } 575 | }), 576 | new Goomba({ 577 | position: { 578 | x: 5135 + xtPlatformImage.width / 2 + goombaWidth, 579 | y: 100 580 | }, 581 | velocity: { 582 | x: -0.3, 583 | y: 0 584 | }, 585 | distance: { 586 | limit: 100, 587 | traveled: 0 588 | } 589 | }), 590 | new Goomba({ 591 | position: { 592 | x: 6968, 593 | y: 0 594 | }, 595 | velocity: { 596 | x: -0.3, 597 | y: 0 598 | }, 599 | distance: { 600 | limit: 100, 601 | traveled: 0 602 | } 603 | }) 604 | ] 605 | particles = [] 606 | platforms = [ 607 | new Platform({ 608 | x: 908 + 100, 609 | y: 300, 610 | image: blockTriImage, 611 | block: true 612 | }), 613 | new Platform({ 614 | x: 908 + 100 + blockImage.width, 615 | y: 100, 616 | image: blockImage, 617 | block: true 618 | }), 619 | new Platform({ 620 | x: 1991 + lgPlatformImage.width - tPlatformImage.width, 621 | y: canvas.height - lgPlatformImage.height - tPlatformImage.height, 622 | image: tPlatformImage, 623 | block: false 624 | }), 625 | new Platform({ 626 | x: 1991 + lgPlatformImage.width - tPlatformImage.width - 100, 627 | y: 628 | canvas.height - 629 | lgPlatformImage.height - 630 | tPlatformImage.height + 631 | blockImage.height, 632 | image: blockImage, 633 | block: true 634 | }), 635 | new Platform({ 636 | x: 5712 + xtPlatformImage.width + 175, 637 | y: canvas.height - xtPlatformImage.height, 638 | image: blockImage, 639 | block: true, 640 | text: 5712 + xtPlatformImage.width + 175 641 | }), 642 | new Platform({ 643 | x: 6116 + 175, 644 | y: canvas.height - xtPlatformImage.height, 645 | image: blockImage, 646 | block: true 647 | }), 648 | new Platform({ 649 | x: 6116 + 175 * 2, 650 | y: canvas.height - xtPlatformImage.height, 651 | image: blockImage, 652 | block: true 653 | }), 654 | new Platform({ 655 | x: 6116 + 175 * 3, 656 | y: canvas.height - xtPlatformImage.height - 100, 657 | image: blockImage, 658 | block: true 659 | }), 660 | new Platform({ 661 | x: 6116 + 175 * 4, 662 | y: canvas.height - xtPlatformImage.height - 200, 663 | image: blockTriImage, 664 | block: true 665 | }), 666 | new Platform({ 667 | x: 6116 + 175 * 4 + blockTriImage.width, 668 | y: canvas.height - xtPlatformImage.height - 200, 669 | image: blockTriImage, 670 | block: true, 671 | text: 6116 + 175 * 4 + blockTriImage.width 672 | }), 673 | new Platform({ 674 | x: 6968 + 300, 675 | y: canvas.height - lgPlatformImage.height, 676 | image: lgPlatformImage, 677 | block: true, 678 | text: 6968 + 300 679 | }) 680 | ] 681 | genericObjects = [ 682 | new GenericObject({ 683 | x: -1, 684 | y: -1, 685 | image: createImage(background) 686 | }), 687 | new GenericObject({ 688 | x: -1, 689 | y: -1, 690 | image: createImage(hills) 691 | }) 692 | ] 693 | 694 | scrollOffset = 0 695 | 696 | const platformsMap = [ 697 | 'lg', 698 | 'lg', 699 | 'gap', 700 | 'lg', 701 | 'gap', 702 | 'gap', 703 | 'lg', 704 | 'gap', 705 | 't', 706 | 'gap', 707 | 'xt', 708 | 'gap', 709 | 'xt', 710 | 'gap', 711 | 'gap', 712 | 'xt' 713 | ] 714 | 715 | let platformDistance = 0 716 | 717 | platformsMap.forEach((symbol) => { 718 | switch (symbol) { 719 | case 'lg': 720 | platforms.push( 721 | new Platform({ 722 | x: platformDistance, 723 | y: canvas.height - lgPlatformImage.height, 724 | image: lgPlatformImage, 725 | block: true, 726 | text: platformDistance 727 | }) 728 | ) 729 | 730 | platformDistance += lgPlatformImage.width - 2 731 | 732 | break 733 | 734 | case 'gap': 735 | platformDistance += 175 736 | 737 | break 738 | 739 | case 't': 740 | platforms.push( 741 | new Platform({ 742 | x: platformDistance, 743 | y: canvas.height - tPlatformImage.height, 744 | image: tPlatformImage, 745 | block: true 746 | }) 747 | ) 748 | 749 | platformDistance += tPlatformImage.width - 2 750 | 751 | break 752 | 753 | case 'xt': 754 | platforms.push( 755 | new Platform({ 756 | x: platformDistance, 757 | y: canvas.height - xtPlatformImage.height, 758 | image: xtPlatformImage, 759 | block: true, 760 | text: platformDistance 761 | }) 762 | ) 763 | 764 | platformDistance += xtPlatformImage.width - 2 765 | 766 | break 767 | } 768 | }) 769 | } 770 | 771 | async function initLevel2() { 772 | player = new Player() 773 | keys = { 774 | right: { 775 | pressed: false 776 | }, 777 | left: { 778 | pressed: false 779 | } 780 | } 781 | scrollOffset = 0 782 | 783 | game = { 784 | disableUserInput: false 785 | } 786 | 787 | blockTriImage = await createImageAsync(blockTri) 788 | blockImage = await createImageAsync(block) 789 | lgPlatformImage = await createImageAsync(images.levels[2].lgPlatform) 790 | tPlatformImage = await createImageAsync(tPlatform) 791 | xtPlatformImage = await createImageAsync(xtPlatform) 792 | flagPoleImage = await createImageAsync(flagPoleSprite) 793 | const mountains = await createImageAsync(images.levels[2].mountains) 794 | const mdPlatformImage = await createImageAsync(images.levels[2].mdPlatform) 795 | 796 | flagPole = new GenericObject({ 797 | x: 7680, 798 | // x: 500, 799 | y: canvas.height - lgPlatformImage.height - flagPoleImage.height, 800 | image: flagPoleImage 801 | }) 802 | 803 | fireFlowers = [ 804 | new FireFlower({ 805 | position: { 806 | x: 4734 - 28, 807 | y: 100 808 | }, 809 | velocity: { 810 | x: 0, 811 | y: 0 812 | } 813 | }) 814 | ] 815 | 816 | player = new Player() 817 | 818 | const goombaWidth = 43.33 819 | goombas = [ 820 | new Goomba({ 821 | // single block goomba 822 | position: { 823 | x: 903 + mdPlatformImage.width - goombaWidth, 824 | y: 100 825 | }, 826 | velocity: { 827 | x: -2, 828 | y: 0 829 | }, 830 | distance: { 831 | limit: 700, 832 | traveled: 0 833 | } 834 | }), 835 | new Goomba({ 836 | // single block goomba 837 | position: { 838 | x: 839 | 1878 + 840 | lgPlatformImage.width + 841 | 155 + 842 | 200 + 843 | 200 + 844 | 200 + 845 | blockImage.width / 2 - 846 | goombaWidth / 2, 847 | y: 100 848 | }, 849 | velocity: { 850 | x: 0, 851 | y: 0 852 | }, 853 | distance: { 854 | limit: 0, 855 | traveled: 0 856 | } 857 | }), 858 | new Goomba({ 859 | position: { 860 | x: 3831 + lgPlatformImage.width - goombaWidth, 861 | y: 100 862 | }, 863 | velocity: { 864 | x: -1, 865 | y: 0 866 | }, 867 | distance: { 868 | limit: lgPlatformImage.width - goombaWidth, 869 | traveled: 0 870 | } 871 | }), 872 | 873 | new Goomba({ 874 | position: { 875 | x: 4734, 876 | y: 100 877 | }, 878 | velocity: { 879 | x: 1, 880 | y: 0 881 | }, 882 | distance: { 883 | limit: lgPlatformImage.width - goombaWidth, 884 | traveled: 0 885 | } 886 | }) 887 | ] 888 | particles = [] 889 | platforms = [ 890 | new Platform({ 891 | x: 903 + mdPlatformImage.width + 115, 892 | y: 300, 893 | image: blockTriImage, 894 | block: true 895 | }), 896 | new Platform({ 897 | x: 903 + mdPlatformImage.width + 115 + blockTriImage.width, 898 | y: 300, 899 | image: blockTriImage, 900 | block: true 901 | }), 902 | new Platform({ 903 | x: 1878 + lgPlatformImage.width + 175, 904 | y: 360, 905 | image: blockImage, 906 | block: true 907 | }), 908 | new Platform({ 909 | x: 1878 + lgPlatformImage.width + 155 + 200, 910 | y: 300, 911 | image: blockImage, 912 | block: true 913 | }), 914 | new Platform({ 915 | x: 1878 + lgPlatformImage.width + 155 + 200 + 200, 916 | y: 330, 917 | image: blockImage, 918 | block: true 919 | }), 920 | new Platform({ 921 | x: 1878 + lgPlatformImage.width + 155 + 200 + 200 + 200, 922 | y: 240, 923 | image: blockImage, 924 | block: true 925 | }), 926 | new Platform({ 927 | x: 4734 - mdPlatformImage.width / 2, 928 | y: canvas.height - lgPlatformImage.height - mdPlatformImage.height, 929 | image: mdPlatformImage 930 | }), 931 | new Platform({ 932 | x: 5987, 933 | y: canvas.height - lgPlatformImage.height - mdPlatformImage.height, 934 | image: mdPlatformImage 935 | }), 936 | new Platform({ 937 | x: 5987, 938 | y: canvas.height - lgPlatformImage.height - mdPlatformImage.height * 2, 939 | image: mdPlatformImage 940 | }), 941 | new Platform({ 942 | x: 6787, 943 | y: canvas.height - lgPlatformImage.height - mdPlatformImage.height, 944 | image: mdPlatformImage 945 | }), 946 | new Platform({ 947 | x: 6787, 948 | y: canvas.height - lgPlatformImage.height - mdPlatformImage.height * 2, 949 | image: mdPlatformImage 950 | }), 951 | new Platform({ 952 | x: 6787, 953 | y: canvas.height - lgPlatformImage.height - mdPlatformImage.height * 3, 954 | image: mdPlatformImage 955 | }) 956 | ] 957 | genericObjects = [ 958 | new GenericObject({ 959 | x: -1, 960 | y: -1, 961 | image: createImage(images.levels[2].background) 962 | }), 963 | new GenericObject({ 964 | x: -1, 965 | y: canvas.height - mountains.height, 966 | image: mountains 967 | }) 968 | ] 969 | 970 | scrollOffset = 0 971 | 972 | const platformsMap = [ 973 | 'lg', 974 | 'md', 975 | 'gap', 976 | 'gap', 977 | 'gap', 978 | 'lg', 979 | 'gap', 980 | 'gap', 981 | 'gap', 982 | 'gap', 983 | 'gap', 984 | 'gap', 985 | 'lg', 986 | 'lg', 987 | 'gap', 988 | 'gap', 989 | 'md', 990 | 'gap', 991 | 'gap', 992 | 'md', 993 | 'gap', 994 | 'gap', 995 | 'lg' 996 | ] 997 | 998 | let platformDistance = 0 999 | 1000 | platformsMap.forEach((symbol) => { 1001 | switch (symbol) { 1002 | case 'md': 1003 | platforms.push( 1004 | new Platform({ 1005 | x: platformDistance, 1006 | y: canvas.height - mdPlatformImage.height, 1007 | image: mdPlatformImage, 1008 | block: true, 1009 | text: platformDistance 1010 | }) 1011 | ) 1012 | 1013 | platformDistance += mdPlatformImage.width - 3 1014 | 1015 | break 1016 | case 'lg': 1017 | platforms.push( 1018 | new Platform({ 1019 | x: platformDistance - 2, 1020 | y: canvas.height - lgPlatformImage.height, 1021 | image: lgPlatformImage, 1022 | block: true, 1023 | text: platformDistance 1024 | }) 1025 | ) 1026 | 1027 | platformDistance += lgPlatformImage.width - 3 1028 | 1029 | break 1030 | 1031 | case 'gap': 1032 | platformDistance += 175 1033 | 1034 | break 1035 | 1036 | case 't': 1037 | platforms.push( 1038 | new Platform({ 1039 | x: platformDistance, 1040 | y: canvas.height - tPlatformImage.height, 1041 | image: tPlatformImage, 1042 | block: true 1043 | }) 1044 | ) 1045 | 1046 | platformDistance += tPlatformImage.width - 2 1047 | 1048 | break 1049 | 1050 | case 'xt': 1051 | platforms.push( 1052 | new Platform({ 1053 | x: platformDistance, 1054 | y: canvas.height - xtPlatformImage.height, 1055 | image: xtPlatformImage, 1056 | block: true, 1057 | text: platformDistance 1058 | }) 1059 | ) 1060 | 1061 | platformDistance += xtPlatformImage.width - 2 1062 | 1063 | break 1064 | } 1065 | }) 1066 | } 1067 | 1068 | function animate() { 1069 | requestAnimationFrame(animate) 1070 | c.fillStyle = 'white' 1071 | c.fillRect(0, 0, canvas.width, canvas.height) 1072 | 1073 | genericObjects.forEach((genericObject) => { 1074 | genericObject.update() 1075 | genericObject.velocity.x = 0 1076 | }) 1077 | 1078 | particles.forEach((particle, i) => { 1079 | particle.update() 1080 | 1081 | if ( 1082 | particle.fireball && 1083 | (particle.position.x - particle.radius >= canvas.width || 1084 | particle.position.x + particle.radius <= 0) 1085 | ) 1086 | setTimeout(() => { 1087 | particles.splice(i, 1) 1088 | }, 0) 1089 | }) 1090 | 1091 | platforms.forEach((platform) => { 1092 | platform.update() 1093 | platform.velocity.x = 0 1094 | }) 1095 | 1096 | if (flagPole) { 1097 | flagPole.update() 1098 | flagPole.velocity.x = 0 1099 | 1100 | // mario touches flagpole 1101 | // win condition 1102 | // complete level 1103 | if ( 1104 | !game.disableUserInput && 1105 | objectsTouch({ 1106 | object1: player, 1107 | object2: flagPole 1108 | }) 1109 | ) { 1110 | audio.completeLevel.play() 1111 | audio.musicLevel1.stop() 1112 | game.disableUserInput = true 1113 | player.velocity.x = 0 1114 | player.velocity.y = 0 1115 | gravity = 0 1116 | 1117 | player.currentSprite = player.sprites.stand.right 1118 | 1119 | if (player.powerUps.fireFlower) 1120 | player.currentSprite = player.sprites.stand.fireFlower.right 1121 | 1122 | // flagpole slide 1123 | setTimeout(() => { 1124 | audio.descend.play() 1125 | }, 200) 1126 | gsap.to(player.position, { 1127 | y: canvas.height - lgPlatformImage.height - player.height, 1128 | duration: 1, 1129 | onComplete() { 1130 | player.currentSprite = player.sprites.run.right 1131 | 1132 | if (player.powerUps.fireFlower) 1133 | player.currentSprite = player.sprites.run.fireFlower.right 1134 | } 1135 | }) 1136 | 1137 | gsap.to(player.position, { 1138 | delay: 1, 1139 | x: canvas.width, 1140 | duration: 2, 1141 | ease: 'power1.in' 1142 | }) 1143 | 1144 | // fireworks 1145 | const particleCount = 300 1146 | const radians = (Math.PI * 2) / particleCount 1147 | const power = 8 1148 | let increment = 1 1149 | 1150 | const intervalId = setInterval(() => { 1151 | for (let i = 0; i < particleCount; i++) { 1152 | particles.push( 1153 | new Particle({ 1154 | position: { 1155 | x: (canvas.width / 4) * increment, 1156 | y: canvas.height / 2 1157 | }, 1158 | velocity: { 1159 | x: Math.cos(radians * i) * power * Math.random(), 1160 | y: Math.sin(radians * i) * power * Math.random() 1161 | }, 1162 | radius: 3 * Math.random(), 1163 | color: `hsl(${Math.random() * 200}, 50%, 50%)`, 1164 | fades: true 1165 | }) 1166 | ) 1167 | } 1168 | 1169 | audio.fireworkBurst.play() 1170 | audio.fireworkWhistle.play() 1171 | 1172 | if (increment === 3) clearInterval(intervalId) 1173 | 1174 | increment++ 1175 | }, 1000) 1176 | 1177 | // switch to the next level 1178 | setTimeout(() => { 1179 | currentLevel++ 1180 | gravity = 1.5 1181 | selectLevel(currentLevel) 1182 | }, 8000) 1183 | } 1184 | } 1185 | 1186 | // mario obtains powerup 1187 | fireFlowers.forEach((fireFlower, i) => { 1188 | if ( 1189 | objectsTouch({ 1190 | object1: player, 1191 | object2: fireFlower 1192 | }) 1193 | ) { 1194 | audio.obtainPowerUp.play() 1195 | player.powerUps.fireFlower = true 1196 | setTimeout(() => { 1197 | fireFlowers.splice(i, 1) 1198 | }, 0) 1199 | } else fireFlower.update() 1200 | }) 1201 | 1202 | goombas.forEach((goomba, index) => { 1203 | goomba.update() 1204 | 1205 | // remove goomba on fireball hit 1206 | particles.forEach((particle, particleIndex) => { 1207 | if ( 1208 | particle.fireball && 1209 | particle.position.x + particle.radius >= goomba.position.x && 1210 | particle.position.y + particle.radius >= goomba.position.y && 1211 | particle.position.x - particle.radius <= 1212 | goomba.position.x + goomba.width && 1213 | particle.position.y - particle.radius <= 1214 | goomba.position.y + goomba.height 1215 | ) { 1216 | for (let i = 0; i < 50; i++) { 1217 | particles.push( 1218 | new Particle({ 1219 | position: { 1220 | x: goomba.position.x + goomba.width / 2, 1221 | y: goomba.position.y + goomba.height / 2 1222 | }, 1223 | velocity: { 1224 | x: (Math.random() - 0.5) * 7, 1225 | y: (Math.random() - 0.5) * 15 1226 | }, 1227 | radius: Math.random() * 3 1228 | }) 1229 | ) 1230 | } 1231 | setTimeout(() => { 1232 | goombas.splice(index, 1) 1233 | particles.splice(particleIndex, 1) 1234 | }, 0) 1235 | } 1236 | }) 1237 | 1238 | // goomba stomp squish / squash 1239 | if ( 1240 | collisionTop({ 1241 | object1: player, 1242 | object2: goomba 1243 | }) 1244 | ) { 1245 | audio.goombaSquash.play() 1246 | 1247 | for (let i = 0; i < 50; i++) { 1248 | particles.push( 1249 | new Particle({ 1250 | position: { 1251 | x: goomba.position.x + goomba.width / 2, 1252 | y: goomba.position.y + goomba.height / 2 1253 | }, 1254 | velocity: { 1255 | x: (Math.random() - 0.5) * 7, 1256 | y: (Math.random() - 0.5) * 15 1257 | }, 1258 | radius: Math.random() * 3 1259 | }) 1260 | ) 1261 | } 1262 | player.velocity.y -= 40 1263 | setTimeout(() => { 1264 | goombas.splice(index, 1) 1265 | }, 0) 1266 | } else if ( 1267 | player.position.x + player.width >= goomba.position.x && 1268 | player.position.y + player.height >= goomba.position.y && 1269 | player.position.x <= goomba.position.x + goomba.width 1270 | ) { 1271 | // player hits goomba 1272 | // lose fireflower / lose powerup 1273 | if (player.powerUps.fireFlower) { 1274 | player.invincible = true 1275 | player.powerUps.fireFlower = false 1276 | audio.losePowerUp.play() 1277 | 1278 | setTimeout(() => { 1279 | player.invincible = false 1280 | }, 1000) 1281 | } else if (!player.invincible) { 1282 | audio.die.play() 1283 | selectLevel(currentLevel) 1284 | } 1285 | } 1286 | }) 1287 | 1288 | player.update() 1289 | 1290 | if (game.disableUserInput) return 1291 | 1292 | // scrolling code starts 1293 | let hitSide = false 1294 | if (keys.right.pressed && player.position.x < 400) { 1295 | player.velocity.x = player.speed 1296 | } else if ( 1297 | (keys.left.pressed && player.position.x > 100) || 1298 | (keys.left.pressed && scrollOffset === 0 && player.position.x > 0) 1299 | ) { 1300 | player.velocity.x = -player.speed 1301 | } else { 1302 | player.velocity.x = 0 1303 | 1304 | // scrolling code 1305 | if (keys.right.pressed) { 1306 | for (let i = 0; i < platforms.length; i++) { 1307 | const platform = platforms[i] 1308 | platform.velocity.x = -player.speed 1309 | 1310 | if ( 1311 | platform.block && 1312 | hitSideOfPlatform({ 1313 | object: player, 1314 | platform 1315 | }) 1316 | ) { 1317 | platforms.forEach((platform) => { 1318 | platform.velocity.x = 0 1319 | }) 1320 | 1321 | hitSide = true 1322 | break 1323 | } 1324 | } 1325 | 1326 | if (!hitSide) { 1327 | scrollOffset += player.speed 1328 | 1329 | flagPole.velocity.x = -player.speed 1330 | 1331 | genericObjects.forEach((genericObject) => { 1332 | genericObject.velocity.x = -player.speed * 0.66 1333 | }) 1334 | 1335 | goombas.forEach((goomba) => { 1336 | goomba.position.x -= player.speed 1337 | }) 1338 | 1339 | fireFlowers.forEach((fireFlower) => { 1340 | fireFlower.position.x -= player.speed 1341 | }) 1342 | 1343 | particles.forEach((particle) => { 1344 | particle.position.x -= player.speed 1345 | }) 1346 | } 1347 | } else if (keys.left.pressed && scrollOffset > 0) { 1348 | for (let i = 0; i < platforms.length; i++) { 1349 | const platform = platforms[i] 1350 | platform.velocity.x = player.speed 1351 | 1352 | if ( 1353 | platform.block && 1354 | hitSideOfPlatform({ 1355 | object: player, 1356 | platform 1357 | }) 1358 | ) { 1359 | platforms.forEach((platform) => { 1360 | platform.velocity.x = 0 1361 | }) 1362 | 1363 | hitSide = true 1364 | break 1365 | } 1366 | } 1367 | 1368 | if (!hitSide) { 1369 | scrollOffset -= player.speed 1370 | 1371 | flagPole.velocity.x = player.speed 1372 | 1373 | genericObjects.forEach((genericObject) => { 1374 | genericObject.velocity.x = player.speed * 0.66 1375 | }) 1376 | 1377 | goombas.forEach((goomba) => { 1378 | goomba.position.x += player.speed 1379 | }) 1380 | 1381 | fireFlowers.forEach((fireFlower) => { 1382 | fireFlower.position.x += player.speed 1383 | }) 1384 | 1385 | particles.forEach((particle) => { 1386 | particle.position.x += player.speed 1387 | }) 1388 | } 1389 | } 1390 | } 1391 | 1392 | // platform collision detection 1393 | platforms.forEach((platform) => { 1394 | if ( 1395 | isOnTopOfPlatform({ 1396 | object: player, 1397 | platform 1398 | }) 1399 | ) { 1400 | player.velocity.y = 0 1401 | } 1402 | 1403 | if ( 1404 | platform.block && 1405 | hitBottomOfPlatform({ 1406 | object: player, 1407 | platform 1408 | }) 1409 | ) { 1410 | player.velocity.y = -player.velocity.y 1411 | } 1412 | 1413 | if ( 1414 | platform.block && 1415 | hitSideOfPlatform({ 1416 | object: player, 1417 | platform 1418 | }) 1419 | ) { 1420 | player.velocity.x = 0 1421 | } 1422 | 1423 | // particles bounce 1424 | particles.forEach((particle, index) => { 1425 | if ( 1426 | isOnTopOfPlatformCircle({ 1427 | object: particle, 1428 | platform 1429 | }) 1430 | ) { 1431 | const bounce = 0.9 1432 | particle.velocity.y = -particle.velocity.y * 0.99 1433 | 1434 | if (particle.radius - 0.4 < 0) particles.splice(index, 1) 1435 | else particle.radius -= 0.4 1436 | } 1437 | 1438 | if (particle.ttl < 0) particles.splice(index, 1) 1439 | }) 1440 | 1441 | goombas.forEach((goomba) => { 1442 | if ( 1443 | isOnTopOfPlatform({ 1444 | object: goomba, 1445 | platform 1446 | }) 1447 | ) 1448 | goomba.velocity.y = 0 1449 | }) 1450 | 1451 | fireFlowers.forEach((fireFlower) => { 1452 | if ( 1453 | isOnTopOfPlatform({ 1454 | object: fireFlower, 1455 | platform 1456 | }) 1457 | ) 1458 | fireFlower.velocity.y = 0 1459 | }) 1460 | }) 1461 | 1462 | // lose condition 1463 | if (player.position.y > canvas.height) { 1464 | audio.die.play() 1465 | selectLevel(currentLevel) 1466 | } 1467 | 1468 | // sprite switching 1469 | 1470 | if (player.shooting) { 1471 | player.currentSprite = player.sprites.shoot.fireFlower.right 1472 | 1473 | if (lastKey === 'left') 1474 | player.currentSprite = player.sprites.shoot.fireFlower.left 1475 | 1476 | return 1477 | } 1478 | 1479 | // sprite jump 1480 | if (player.velocity.y !== 0) return 1481 | 1482 | if ( 1483 | keys.right.pressed && 1484 | lastKey === 'right' && 1485 | player.currentSprite !== player.sprites.run.right 1486 | ) { 1487 | player.currentSprite = player.sprites.run.right 1488 | } else if ( 1489 | keys.left.pressed && 1490 | lastKey === 'left' && 1491 | player.currentSprite !== player.sprites.run.left 1492 | ) { 1493 | player.currentSprite = player.sprites.run.left 1494 | } else if ( 1495 | !keys.left.pressed && 1496 | lastKey === 'left' && 1497 | player.currentSprite !== player.sprites.stand.left 1498 | ) { 1499 | player.currentSprite = player.sprites.stand.left 1500 | } else if ( 1501 | !keys.right.pressed && 1502 | lastKey === 'right' && 1503 | player.currentSprite !== player.sprites.stand.right 1504 | ) { 1505 | player.currentSprite = player.sprites.stand.right 1506 | } 1507 | 1508 | // fireflower sprites 1509 | if (!player.powerUps.fireFlower) return 1510 | 1511 | if ( 1512 | keys.right.pressed && 1513 | lastKey === 'right' && 1514 | player.currentSprite !== player.sprites.run.fireFlower.right 1515 | ) { 1516 | player.currentSprite = player.sprites.run.fireFlower.right 1517 | } else if ( 1518 | keys.left.pressed && 1519 | lastKey === 'left' && 1520 | player.currentSprite !== player.sprites.run.fireFlower.left 1521 | ) { 1522 | player.currentSprite = player.sprites.run.fireFlower.left 1523 | } else if ( 1524 | !keys.left.pressed && 1525 | lastKey === 'left' && 1526 | player.currentSprite !== player.sprites.stand.fireFlower.left 1527 | ) { 1528 | player.currentSprite = player.sprites.stand.fireFlower.left 1529 | } else if ( 1530 | !keys.right.pressed && 1531 | lastKey === 'right' && 1532 | player.currentSprite !== player.sprites.stand.fireFlower.right 1533 | ) { 1534 | player.currentSprite = player.sprites.stand.fireFlower.right 1535 | } 1536 | } // animation loop ends 1537 | 1538 | selectLevel(1) 1539 | // init() 1540 | // initLevel2() 1541 | animate() 1542 | 1543 | addEventListener('keydown', ({ keyCode }) => { 1544 | if (game.disableUserInput) return 1545 | 1546 | switch (keyCode) { 1547 | case 65: 1548 | console.log('left') 1549 | keys.left.pressed = true 1550 | lastKey = 'left' 1551 | 1552 | break 1553 | 1554 | case 83: 1555 | console.log('down') 1556 | break 1557 | 1558 | case 68: 1559 | console.log('right') 1560 | keys.right.pressed = true 1561 | lastKey = 'right' 1562 | 1563 | break 1564 | 1565 | case 87: 1566 | console.log('up') 1567 | player.velocity.y -= 25 1568 | 1569 | audio.jump.play() 1570 | 1571 | if (lastKey === 'right') player.currentSprite = player.sprites.jump.right 1572 | else player.currentSprite = player.sprites.jump.left 1573 | 1574 | if (!player.powerUps.fireFlower) break 1575 | 1576 | if (lastKey === 'right') 1577 | player.currentSprite = player.sprites.jump.fireFlower.right 1578 | else player.currentSprite = player.sprites.jump.fireFlower.left 1579 | 1580 | break 1581 | 1582 | case 32: 1583 | console.log('space') 1584 | 1585 | if (!player.powerUps.fireFlower) return 1586 | 1587 | player.shooting = true 1588 | 1589 | setTimeout(() => { 1590 | player.shooting = false 1591 | }, 100) 1592 | 1593 | audio.fireFlowerShot.play() 1594 | 1595 | let velocity = 15 1596 | if (lastKey === 'left') velocity = -velocity 1597 | 1598 | particles.push( 1599 | new Particle({ 1600 | position: { 1601 | x: player.position.x + player.width / 2, 1602 | y: player.position.y + player.height / 2 1603 | }, 1604 | velocity: { 1605 | x: velocity, 1606 | y: 0 1607 | }, 1608 | radius: 5, 1609 | color: 'red', 1610 | fireball: true 1611 | }) 1612 | ) 1613 | break 1614 | } 1615 | }) 1616 | 1617 | addEventListener('keyup', ({ keyCode }) => { 1618 | if (game.disableUserInput) return 1619 | 1620 | switch (keyCode) { 1621 | case 65: 1622 | console.log('left') 1623 | keys.left.pressed = false 1624 | break 1625 | 1626 | case 83: 1627 | console.log('down') 1628 | break 1629 | 1630 | case 68: 1631 | console.log('right') 1632 | keys.right.pressed = false 1633 | 1634 | break 1635 | 1636 | case 87: 1637 | console.log('up') 1638 | break 1639 | } 1640 | }) 1641 | -------------------------------------------------------------------------------- /src/js/images.js: -------------------------------------------------------------------------------- 1 | import backgroundLevel2 from '../img/level2/background.png' 2 | import mountains from '../img/level2/mountains.png' 3 | import lgPlatformLevel2 from '../img/level2/lgPlatform.png' 4 | import mdPlatformLevel2 from '../img/level2/mdPlatform.png' 5 | import spriteFireFlowerShootLeft from '../img/mario/spriteFireFlowerShootLeft.png' 6 | import spriteFireFlowerShootRight from '../img/mario/spriteFireFlowerShootRight.png' 7 | 8 | export const images = { 9 | mario: { 10 | shoot: { 11 | fireFlower: { 12 | right: spriteFireFlowerShootRight, 13 | left: spriteFireFlowerShootLeft 14 | } 15 | } 16 | }, 17 | levels: { 18 | 1: { 19 | background: '' 20 | }, 21 | 2: { 22 | background: backgroundLevel2, 23 | mountains, 24 | lgPlatform: lgPlatformLevel2, 25 | mdPlatform: mdPlatformLevel2 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/js/utils.js: -------------------------------------------------------------------------------- 1 | function randomIntFromRange(min, max) { 2 | return Math.floor(Math.random() * (max - min + 1) + min) 3 | } 4 | 5 | function randomColor(colors) { 6 | return colors[Math.floor(Math.random() * colors.length)] 7 | } 8 | 9 | function distance(x1, y1, x2, y2) { 10 | const xDist = x2 - x1 11 | const yDist = y2 - y1 12 | 13 | return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)) 14 | } 15 | 16 | export function createImage(imageSrc) { 17 | const image = new Image() 18 | image.src = imageSrc 19 | return image 20 | } 21 | 22 | export function createImageAsync(imageSrc) { 23 | return new Promise((resolve) => { 24 | const image = new Image() 25 | image.onload = () => { 26 | resolve(image) 27 | } 28 | image.src = imageSrc 29 | }) 30 | } 31 | 32 | export function isOnTopOfPlatform({ object, platform }) { 33 | return ( 34 | object.position.y + object.height <= platform.position.y && 35 | object.position.y + object.height + object.velocity.y >= 36 | platform.position.y && 37 | object.position.x + object.width >= platform.position.x && 38 | object.position.x <= platform.position.x + platform.width 39 | ) 40 | } 41 | 42 | export function collisionTop({ object1, object2 }) { 43 | return ( 44 | object1.position.y + object1.height <= object2.position.y && 45 | object1.position.y + object1.height + object1.velocity.y >= 46 | object2.position.y && 47 | object1.position.x + object1.width >= object2.position.x && 48 | object1.position.x <= object2.position.x + object2.width 49 | ) 50 | } 51 | 52 | export function isOnTopOfPlatformCircle({ object, platform }) { 53 | return ( 54 | object.position.y + object.radius <= platform.position.y && 55 | object.position.y + object.radius + object.velocity.y >= 56 | platform.position.y && 57 | object.position.x + object.radius >= platform.position.x && 58 | object.position.x <= platform.position.x + platform.width 59 | ) 60 | } 61 | 62 | export function hitBottomOfPlatform({ object, platform }) { 63 | return ( 64 | object.position.y <= platform.position.y + platform.height && 65 | object.position.y - object.velocity.y >= 66 | platform.position.y + platform.height && 67 | object.position.x + object.width >= platform.position.x && 68 | object.position.x <= platform.position.x + platform.width 69 | ) 70 | } 71 | 72 | export function hitSideOfPlatform({ object, platform }) { 73 | return ( 74 | object.position.x + 75 | object.width + 76 | object.velocity.x - 77 | platform.velocity.x >= 78 | platform.position.x && 79 | object.position.x + object.velocity.x <= 80 | platform.position.x + platform.width && 81 | object.position.y <= platform.position.y + platform.height && 82 | object.position.y + object.height >= platform.position.y 83 | ) 84 | } 85 | 86 | // two rectangles collide 87 | export function objectsTouch({ object1, object2 }) { 88 | return ( 89 | object1.position.x + object1.width >= object2.position.x && 90 | object1.position.x <= object2.position.x + object2.width && 91 | object1.position.y + object1.height >= object2.position.y && 92 | object1.position.y <= object2.position.y + object2.height 93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const BrowserSyncPlugin = require('browser-sync-webpack-plugin') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | module.exports = { 5 | mode: 'development', 6 | entry: './src/js/canvas.js', 7 | output: { 8 | path: __dirname + '/dist/', 9 | filename: './js/canvas.bundle.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.m?js$/, 15 | exclude: /(node_modules|bower_components)/, 16 | use: { 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['@babel/preset-env'] 20 | } 21 | } 22 | }, 23 | { 24 | test: /\.(png|jpe?g|gif)$/i, 25 | use: [ 26 | { 27 | loader: 'file-loader' 28 | } 29 | ] 30 | }, 31 | { 32 | test: /\.(mp3|wav)$/i, 33 | use: [ 34 | { 35 | loader: 'file-loader', 36 | options: { 37 | outputPath: 'audio' 38 | } 39 | } 40 | ] 41 | } 42 | ] 43 | }, 44 | plugins: [ 45 | new BrowserSyncPlugin({ 46 | host: 'localhost', 47 | port: 3000, 48 | server: { baseDir: ['dist'] }, 49 | files: ['./dist/*'], 50 | notify: false 51 | }), 52 | new HtmlWebpackPlugin({ 53 | filename: 'index.html', 54 | favicon: 'favicon.ico', 55 | template: 'src/index.html' 56 | }) 57 | ], 58 | watch: true, 59 | devtool: 'source-map' 60 | } 61 | --------------------------------------------------------------------------------