├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── index.js ├── package.json └── src ├── assets ├── assets │ └── platformer │ │ ├── animated_coin.gif │ │ ├── bitmap_font_sheet.png │ │ ├── blurry_hero.png │ │ ├── coin_scoreboard.png │ │ ├── coin_spritesheet.png │ │ ├── door_spawned.png │ │ ├── enemy_bounce.gif │ │ ├── enemy_dying.gif │ │ ├── game_loop.png │ │ ├── game_state.png │ │ ├── grass_1x1.png │ │ ├── grass_4x1.png │ │ ├── ground_falling.gif │ │ ├── hero_animation_glitch.gif │ │ ├── hero_animations.gif │ │ ├── hero_bad_position.png │ │ ├── hero_fall_bottom.png │ │ ├── hero_jump.gif │ │ ├── hero_spritesheet.png │ │ ├── hud_icon_only.png │ │ ├── invisible_walls.png │ │ ├── key_icon_empty.png │ │ ├── key_icon_filled.png │ │ ├── key_icon_spritesheet.png │ │ ├── key_spawned.png │ │ ├── key_tween.gif │ │ ├── level00_thumb.png │ │ ├── level_coin_scoreboard.png │ │ ├── platformer_screenshot.png │ │ ├── platforms_falling.gif │ │ ├── spider_disaster.gif │ │ ├── spider_spritesheet.png │ │ ├── spider_turning.gif │ │ ├── spider_vs_wall.png │ │ ├── start.zip │ │ ├── static_coins.png │ │ ├── step00_check.png │ │ ├── step01_check.png │ │ ├── step02_check.png │ │ ├── step03_check.png │ │ ├── step06_check.png │ │ ├── steps │ │ ├── step01.js │ │ ├── step02.js │ │ ├── step03.js │ │ ├── step04.js │ │ ├── step05.js │ │ ├── step06.js │ │ ├── step07.js │ │ ├── step08.js │ │ ├── step09.js │ │ ├── step10.js │ │ ├── step11.js │ │ ├── step12.js │ │ ├── step13.js │ │ ├── step14.js │ │ └── step15.js │ │ ├── walking_spider.gif │ │ └── win_condition.png ├── css │ └── styles.css ├── images │ ├── moz-favicon.svg │ └── moz-logo.svg ├── platformer │ ├── audio │ │ ├── bgm.mp3 │ │ ├── bgm.ogg │ │ ├── coin.wav │ │ ├── door.wav │ │ ├── jump.wav │ │ ├── key.wav │ │ └── stomp.wav │ ├── data │ │ ├── level00.json │ │ └── level01.json │ ├── images │ │ ├── background.png │ │ ├── coin_animated.png │ │ ├── coin_icon.png │ │ ├── decor.png │ │ ├── door.png │ │ ├── grass_1x1.png │ │ ├── grass_2x1.png │ │ ├── grass_4x1.png │ │ ├── grass_6x1.png │ │ ├── grass_8x1.png │ │ ├── ground.png │ │ ├── hero.png │ │ ├── hero_stopped.png │ │ ├── invisible_wall.png │ │ ├── key.png │ │ ├── key_icon.png │ │ ├── numbers.png │ │ └── spider.png │ ├── index.html │ └── js │ │ ├── main.js │ │ ├── phaser.map │ │ └── phaser.min.js └── vendor │ └── prism │ ├── prism.css │ └── prism.js ├── content ├── bonus │ └── resources_en.md ├── coach-guide │ └── index_en.md ├── index_en.md ├── platformer │ ├── index_en.md │ ├── step01_en.md │ ├── step02_en.md │ ├── step03_en.md │ ├── step04_en.md │ ├── step05_en.md │ ├── step06_en.md │ ├── step07_en.md │ ├── step08_en.md │ ├── step09_en.md │ ├── step10_en.md │ ├── step11_en.md │ ├── step12_en.md │ ├── step13_en.md │ ├── step14_en.md │ ├── step15_en.md │ └── step16_en.md └── setup │ ├── index_en.md │ └── step00_en.md └── layouts ├── default.pug ├── guide_index.pug └── guide_step.pug /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Project ignore 40 | .DS_Store 41 | publish.sh 42 | dist 43 | game-wip 44 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "browser": true, 4 | "node": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML5 games workshop 2 | 3 | A workshop that teaches how to develop HTML5 games with JavaScript and [Phaser](http://phaser.io). 4 | 5 | It is meant to last a full day, although it includes sufficient guiding for people to finish it at home if only a short session with a coach is possible. 6 | 7 | [**Access the workshop online here**](https://mozdevs.github.io/html5-games-workshop/). 8 | 9 | ![Game screenshot](https://github.com/mozdevs/html5-games-workshop/raw/master/src/assets/assets/platformer/platformer_screenshot.png) 10 | 11 | ## Sessions 12 | 13 | Upcoming sessions of this workshop: 14 | 15 | - Barcelona (Spain), 4 March 2017 at [AdaJS](http://ada.barcelonajs.org/). 16 | 17 | Would you like to host the workshop in your city? [Contact Belén](https://belenalbeza.com/speaking/#howtogetmeatyourevent). 18 | 19 | ## Development 20 | 21 | If you want to tinker around or have your own version… 22 | 23 | This project uses [Metalsmith](http://www.metalsmith.io/) to generate a static website out of Markdown files (among others). 24 | 25 | ### Build the project 26 | 27 | ```sh 28 | npm install 29 | node index.js 30 | ``` 31 | 32 | It will generate a `dist` folder with the website for the workshop generated. It's all static files. 33 | 34 | ### Watch 35 | 36 | There is a **watch** mode that will recompile the project whenever a markdown file has been changed: 37 | 38 | ```sh 39 | npm run watch 40 | ``` 41 | 42 | ### Publish to Github Pages 43 | 44 | ``` 45 | npm run deploy 46 | ``` 47 | 48 | This assumes that your repository is named `html5-games-workshop`. If not, you will need to update `package.json` and change: 49 | 50 | ```json 51 | "build:ghpages": "node index.js --base \"/html5-games-workshop\"", 52 | ``` 53 | 54 | …to use your repository name as `--base` parameter. 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const metalsmith = require('metalsmith'); 6 | const markdown = require('metalsmith-markdown'); 7 | const assets = require('metalsmith-assets'); 8 | const layouts = require('metalsmith-layouts'); 9 | const multiLanguage = require('metalsmith-multi-language'); 10 | const permalinks = require('metalsmith-permalinks'); 11 | const collections = require('metalsmith-collections'); 12 | const rewrite = require('metalsmith-rewrite'); 13 | const slug = require('metalsmith-slug'); 14 | const relative = require('metalsmith-relative'); 15 | const prefixoid = require('metalsmith-prefixoid'); 16 | 17 | const argv = require('minimist')(process.argv.slice(2)); 18 | 19 | const DEFAULT_LOCALE = 'en'; 20 | const LOCALES = ['en', 'es']; 21 | 22 | metalsmith(__dirname) 23 | .source('src/content') 24 | .use(collections({ 25 | 'platformer_en': { 26 | pattern: 'platformer/*_en.md', 27 | sortBy: 'path' 28 | }, 29 | 'platformer_es': { 30 | pattern: 'platformer/*_es.md', 31 | sortBy: 'path' 32 | }, 33 | 'setup_en': { 34 | pattern: 'setup/*_en.md', 35 | sortBy: 'path' 36 | }, 37 | 'bonus': { 38 | pattern: 'bonus/*.md' 39 | }, 40 | 'coach_en' :{ 41 | pattern: 'coach-guide/*.md' 42 | } 43 | })) 44 | .use(multiLanguage({ 45 | locales: LOCALES, 46 | default: DEFAULT_LOCALE 47 | })) 48 | .use(slug()) 49 | .use(markdown()) 50 | .use(permalinks({ 51 | relative: false, 52 | pattern: ':locale/:slug/', 53 | linksets: [{ 54 | match: { collection: 'platformer_en' }, 55 | pattern: ':locale/guides/platformer/:slug' 56 | }, { 57 | match: { collection: 'platformer_es' }, 58 | pattern: ':locale/guias/plataformas/:slug' 59 | }, { 60 | match: { collection: 'setup_en' }, 61 | pattern: ':locale/guides/setup/:slug' 62 | }, { 63 | match: { collection: 'bonus' }, 64 | pattern: ':locale/bonus/:slug' 65 | }, { 66 | match: { collection: 'coach_en'}, 67 | pattern: ':locale/guides/coach' 68 | }] 69 | })) 70 | .use(relative()) 71 | .use(layouts({ 72 | engine: 'pug', 73 | default: 'default.pug', 74 | pattern: '**/*.html', 75 | directory: 'src/layouts', 76 | cache: false, 77 | pretty: true 78 | })) 79 | .use(assets({source: 'src/assets'})) 80 | .use(prefixoid({ 81 | prefix: argv.base || '', 82 | tag: 'a', 83 | attr: 'href' 84 | })) 85 | .use(prefixoid({ 86 | prefix: argv.base || '', 87 | tag: 'img', 88 | attr: 'src' 89 | })) 90 | .destination('dist') 91 | .build(function (err) { 92 | if (err) { console.error(err); } 93 | }); 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html5-games-workshop", 3 | "version": "1.0.0", 4 | "description": "html5 game development workshop", 5 | "main": "index.js", 6 | "watch": { 7 | "build": { 8 | "patterns": [ 9 | "src" 10 | ], 11 | "extensions": "md,css,pug", 12 | "quiet": true 13 | } 14 | }, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "build": "node index.js", 18 | "build:ghpages": "node index.js --base \"/html5-games-workshop\"", 19 | "watch": "npm-watch", 20 | "ghpages": "gh-pages -d dist", 21 | "deploy": "npm run build:ghpages && npm run ghpages" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/mozdevs/html5-games-workshop.git" 26 | }, 27 | "keywords": [ 28 | "workshop", 29 | "gamedev", 30 | "games" 31 | ], 32 | "author": "Belén \"Benko\" Albeza", 33 | "license": "CC-BY-SA-4.0", 34 | "bugs": { 35 | "url": "https://github.com/mozdevs/html5-games-workshop/issues" 36 | }, 37 | "homepage": "https://github.com/mozdevs/html5-games-workshop#readme", 38 | "dependencies": { 39 | "gh-pages": "^0.12.0", 40 | "ghpages": "0.0.8", 41 | "metalsmith": "^2.3.0", 42 | "metalsmith-assets": "^0.1.0", 43 | "metalsmith-collections": "^0.9.0", 44 | "metalsmith-layouts": "^1.7.0", 45 | "metalsmith-markdown": "^0.2.1", 46 | "metalsmith-multi-language": "^0.2.0", 47 | "metalsmith-permalinks": "^0.5.0", 48 | "metalsmith-prefixoid": "^1.0.3", 49 | "metalsmith-relative": "^1.0.3", 50 | "metalsmith-rewrite": "^0.1.2", 51 | "metalsmith-slug": "^0.2.0", 52 | "minimist": "^1.2.0", 53 | "pug": "^2.0.0-beta9" 54 | }, 55 | "devDependencies": { 56 | "gh-pages": "^0.12.0", 57 | "npm-watch": "^0.1.8" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/animated_coin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/animated_coin.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/bitmap_font_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/bitmap_font_sheet.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/blurry_hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/blurry_hero.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/coin_scoreboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/coin_scoreboard.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/coin_spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/coin_spritesheet.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/door_spawned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/door_spawned.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/enemy_bounce.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/enemy_bounce.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/enemy_dying.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/enemy_dying.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/game_loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/game_loop.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/game_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/game_state.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/grass_1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/grass_1x1.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/grass_4x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/grass_4x1.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/ground_falling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/ground_falling.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/hero_animation_glitch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/hero_animation_glitch.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/hero_animations.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/hero_animations.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/hero_bad_position.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/hero_bad_position.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/hero_fall_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/hero_fall_bottom.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/hero_jump.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/hero_jump.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/hero_spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/hero_spritesheet.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/hud_icon_only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/hud_icon_only.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/invisible_walls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/invisible_walls.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/key_icon_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/key_icon_empty.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/key_icon_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/key_icon_filled.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/key_icon_spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/key_icon_spritesheet.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/key_spawned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/key_spawned.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/key_tween.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/key_tween.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/level00_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/level00_thumb.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/level_coin_scoreboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/level_coin_scoreboard.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/platformer_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/platformer_screenshot.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/platforms_falling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/platforms_falling.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/spider_disaster.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/spider_disaster.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/spider_spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/spider_spritesheet.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/spider_turning.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/spider_turning.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/spider_vs_wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/spider_vs_wall.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/start.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/start.zip -------------------------------------------------------------------------------- /src/assets/assets/platformer/static_coins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/static_coins.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/step00_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/step00_check.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/step01_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/step01_check.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/step02_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/step02_check.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/step03_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/step03_check.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/step06_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/step06_check.png -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step01.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // entry point 3 | // ============================================================================= 4 | 5 | window.onload = function () { 6 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 7 | }; 8 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step02.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // game states 3 | // ============================================================================= 4 | 5 | PlayState = {}; 6 | 7 | // load game assets here 8 | PlayState.preload = function () { 9 | this.game.load.image('background', 'images/background.png'); 10 | }; 11 | 12 | // create game entities and set up world here 13 | PlayState.create = function () { 14 | this.game.add.image(0, 0, 'background'); 15 | }; 16 | 17 | // ============================================================================= 18 | // entry point 19 | // ============================================================================= 20 | 21 | window.onload = function () { 22 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 23 | game.state.add('play', PlayState); 24 | game.state.start('play'); 25 | }; 26 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step03.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // game states 3 | // ============================================================================= 4 | 5 | PlayState = {}; 6 | 7 | PlayState.preload = function () { 8 | this.game.load.json('level:1', 'data/level01.json'); 9 | 10 | this.game.load.image('background', 'images/background.png'); 11 | this.game.load.image('ground', 'images/ground.png'); 12 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 13 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 14 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 15 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 16 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 17 | }; 18 | 19 | PlayState.create = function () { 20 | this.game.add.image(0, 0, 'background'); 21 | this._loadLevel(this.game.cache.getJSON('level:1')); 22 | }; 23 | 24 | PlayState._loadLevel = function (data) { 25 | // spawn all platforms 26 | data.platforms.forEach(this._spawnPlatform, this); 27 | }; 28 | 29 | PlayState._spawnPlatform = function (platform) { 30 | this.game.add.sprite(platform.x, platform.y, platform.image); 31 | }; 32 | 33 | // ============================================================================= 34 | // entry point 35 | // ============================================================================= 36 | 37 | window.onload = function () { 38 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 39 | game.state.add('play', PlayState); 40 | game.state.start('play'); 41 | }; 42 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step04.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | // call Phaser.Sprite constructor 10 | Phaser.Sprite.call(this, game, x, y, 'hero'); 11 | 12 | this.anchor.set(0.5, 0.5); 13 | } 14 | 15 | // inherit from Phaser.Sprite 16 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 17 | Hero.prototype.constructor = Hero; 18 | 19 | // ============================================================================= 20 | // game states 21 | // ============================================================================= 22 | 23 | PlayState = {}; 24 | 25 | PlayState.preload = function () { 26 | this.game.load.json('level:1', 'data/level01.json'); 27 | 28 | this.game.load.image('background', 'images/background.png'); 29 | this.game.load.image('ground', 'images/ground.png'); 30 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 31 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 32 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 33 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 34 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 35 | this.game.load.image('hero', 'images/hero_stopped.png'); 36 | }; 37 | 38 | PlayState.create = function () { 39 | this.game.add.image(0, 0, 'background'); 40 | this._loadLevel(this.game.cache.getJSON('level:1')); 41 | }; 42 | 43 | PlayState._loadLevel = function (data) { 44 | // spawn all platforms 45 | data.platforms.forEach(this._spawnPlatform, this); 46 | // spawn hero and enemies 47 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 48 | }; 49 | 50 | PlayState._spawnPlatform = function (platform) { 51 | this.game.add.sprite(platform.x, platform.y, platform.image); 52 | }; 53 | 54 | PlayState._spawnCharacters = function (data) { 55 | // spawn hero 56 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 57 | this.game.add.existing(this.hero); 58 | }; 59 | 60 | // ============================================================================= 61 | // entry point 62 | // ============================================================================= 63 | 64 | window.onload = function () { 65 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 66 | game.state.add('play', PlayState); 67 | game.state.start('play'); 68 | }; 69 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step05.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | 11 | this.anchor.set(0.5, 0.5); 12 | } 13 | 14 | // inherit from Phaser.Sprite 15 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 16 | Hero.prototype.constructor = Hero; 17 | 18 | Hero.prototype.move = function (direction) { 19 | this.x += direction * 2.5; // 2.5 pixels each frame 20 | }; 21 | 22 | // ============================================================================= 23 | // game states 24 | // ============================================================================= 25 | 26 | PlayState = {}; 27 | 28 | PlayState.init = function () { 29 | this.game.renderer.renderSession.roundPixels = true; 30 | 31 | this.keys = this.game.input.keyboard.addKeys({ 32 | left: Phaser.KeyCode.LEFT, 33 | right: Phaser.KeyCode.RIGHT 34 | }); 35 | }; 36 | 37 | PlayState.preload = function () { 38 | this.game.load.json('level:1', 'data/level01.json'); 39 | 40 | this.game.load.image('background', 'images/background.png'); 41 | this.game.load.image('ground', 'images/ground.png'); 42 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 43 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 44 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 45 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 46 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 47 | this.game.load.image('hero', 'images/hero_stopped.png'); 48 | }; 49 | 50 | PlayState.create = function () { 51 | this.game.add.image(0, 0, 'background'); 52 | this._loadLevel(this.game.cache.getJSON('level:1')); 53 | }; 54 | 55 | PlayState.update = function () { 56 | this._handleInput(); 57 | }; 58 | 59 | PlayState._handleInput = function () { 60 | if (this.keys.left.isDown) { // move hero left 61 | this.hero.move(-1); 62 | } 63 | else if (this.keys.right.isDown) { // move hero right 64 | this.hero.move(1); 65 | } 66 | }; 67 | 68 | PlayState._loadLevel = function (data) { 69 | // spawn all platforms 70 | data.platforms.forEach(this._spawnPlatform, this); 71 | // spawn hero and enemies 72 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 73 | }; 74 | 75 | PlayState._spawnPlatform = function (platform) { 76 | this.game.add.sprite(platform.x, platform.y, platform.image); 77 | }; 78 | 79 | PlayState._spawnCharacters = function (data) { 80 | // spawn hero 81 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 82 | this.game.add.existing(this.hero); 83 | }; 84 | 85 | // ============================================================================= 86 | // entry point 87 | // ============================================================================= 88 | 89 | window.onload = function () { 90 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 91 | game.state.add('play', PlayState); 92 | game.state.start('play'); 93 | }; 94 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step06.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | } 16 | 17 | // inherit from Phaser.Sprite 18 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 19 | Hero.prototype.constructor = Hero; 20 | 21 | Hero.prototype.move = function (direction) { 22 | const SPEED = 200; 23 | this.body.velocity.x = direction * SPEED; 24 | }; 25 | 26 | // ============================================================================= 27 | // game states 28 | // ============================================================================= 29 | 30 | PlayState = {}; 31 | 32 | PlayState.init = function () { 33 | this.game.renderer.renderSession.roundPixels = true; 34 | 35 | this.keys = this.game.input.keyboard.addKeys({ 36 | left: Phaser.KeyCode.LEFT, 37 | right: Phaser.KeyCode.RIGHT 38 | }); 39 | }; 40 | 41 | PlayState.preload = function () { 42 | this.game.load.json('level:1', 'data/level01.json'); 43 | 44 | this.game.load.image('background', 'images/background.png'); 45 | this.game.load.image('ground', 'images/ground.png'); 46 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 47 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 48 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 49 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 50 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 51 | this.game.load.image('hero', 'images/hero_stopped.png'); 52 | }; 53 | 54 | PlayState.create = function () { 55 | this.game.add.image(0, 0, 'background'); 56 | this._loadLevel(this.game.cache.getJSON('level:1')); 57 | }; 58 | 59 | PlayState.update = function () { 60 | this._handleInput(); 61 | }; 62 | 63 | PlayState._handleInput = function () { 64 | if (this.keys.left.isDown) { // move hero left 65 | this.hero.move(-1); 66 | } 67 | else if (this.keys.right.isDown) { // move hero right 68 | this.hero.move(1); 69 | } 70 | else { // stop 71 | this.hero.move(0); 72 | } 73 | }; 74 | 75 | PlayState._loadLevel = function (data) { 76 | // spawn all platforms 77 | data.platforms.forEach(this._spawnPlatform, this); 78 | // spawn hero and enemies 79 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 80 | }; 81 | 82 | PlayState._spawnPlatform = function (platform) { 83 | this.game.add.sprite(platform.x, platform.y, platform.image); 84 | }; 85 | 86 | PlayState._spawnCharacters = function (data) { 87 | // spawn hero 88 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 89 | this.game.add.existing(this.hero); 90 | }; 91 | 92 | // ============================================================================= 93 | // entry point 94 | // ============================================================================= 95 | 96 | window.onload = function () { 97 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 98 | game.state.add('play', PlayState); 99 | game.state.start('play'); 100 | }; 101 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step07.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | } 16 | 17 | // inherit from Phaser.Sprite 18 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 19 | Hero.prototype.constructor = Hero; 20 | 21 | Hero.prototype.move = function (direction) { 22 | const SPEED = 200; 23 | this.body.velocity.x = direction * SPEED; 24 | }; 25 | 26 | // ============================================================================= 27 | // game states 28 | // ============================================================================= 29 | 30 | PlayState = {}; 31 | 32 | PlayState.init = function () { 33 | this.game.renderer.renderSession.roundPixels = true; 34 | 35 | this.keys = this.game.input.keyboard.addKeys({ 36 | left: Phaser.KeyCode.LEFT, 37 | right: Phaser.KeyCode.RIGHT 38 | }); 39 | }; 40 | 41 | PlayState.preload = function () { 42 | this.game.load.json('level:1', 'data/level01.json'); 43 | 44 | this.game.load.image('background', 'images/background.png'); 45 | this.game.load.image('ground', 'images/ground.png'); 46 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 47 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 48 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 49 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 50 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 51 | this.game.load.image('hero', 'images/hero_stopped.png'); 52 | }; 53 | 54 | PlayState.create = function () { 55 | this.game.add.image(0, 0, 'background'); 56 | this._loadLevel(this.game.cache.getJSON('level:1')); 57 | }; 58 | 59 | PlayState.update = function () { 60 | this._handleCollisions(); 61 | this._handleInput(); 62 | }; 63 | 64 | PlayState._handleCollisions = function () { 65 | this.game.physics.arcade.collide(this.hero, this.platforms); 66 | }; 67 | 68 | PlayState._handleInput = function () { 69 | if (this.keys.left.isDown) { // move hero left 70 | this.hero.move(-1); 71 | } 72 | else if (this.keys.right.isDown) { // move hero right 73 | this.hero.move(1); 74 | } 75 | else { // stop 76 | this.hero.move(0); 77 | } 78 | }; 79 | 80 | PlayState._loadLevel = function (data) { 81 | // create all the groups/layers that we need 82 | this.platforms = this.game.add.group(); 83 | 84 | // spawn all platforms 85 | data.platforms.forEach(this._spawnPlatform, this); 86 | // spawn hero and enemies 87 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 88 | 89 | // enable gravity 90 | const GRAVITY = 1200; 91 | this.game.physics.arcade.gravity.y = GRAVITY; 92 | }; 93 | 94 | PlayState._spawnPlatform = function (platform) { 95 | let sprite = this.platforms.create( 96 | platform.x, platform.y, platform.image); 97 | 98 | this.game.physics.enable(sprite); 99 | sprite.body.allowGravity = false; 100 | sprite.body.immovable = true; 101 | }; 102 | 103 | PlayState._spawnCharacters = function (data) { 104 | // spawn hero 105 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 106 | this.game.add.existing(this.hero); 107 | }; 108 | 109 | // ============================================================================= 110 | // entry point 111 | // ============================================================================= 112 | 113 | window.onload = function () { 114 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 115 | game.state.add('play', PlayState); 116 | game.state.start('play'); 117 | }; 118 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step08.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | } 16 | 17 | // inherit from Phaser.Sprite 18 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 19 | Hero.prototype.constructor = Hero; 20 | 21 | Hero.prototype.move = function (direction) { 22 | const SPEED = 200; 23 | this.body.velocity.x = direction * SPEED; 24 | }; 25 | 26 | Hero.prototype.jump = function () { 27 | const JUMP_SPEED = 600; 28 | let canJump = this.body.touching.down; 29 | 30 | if (canJump) { 31 | this.body.velocity.y = -JUMP_SPEED; 32 | } 33 | 34 | return canJump; 35 | }; 36 | 37 | // ============================================================================= 38 | // game states 39 | // ============================================================================= 40 | 41 | PlayState = {}; 42 | 43 | PlayState.init = function () { 44 | this.game.renderer.renderSession.roundPixels = true; 45 | 46 | this.keys = this.game.input.keyboard.addKeys({ 47 | left: Phaser.KeyCode.LEFT, 48 | right: Phaser.KeyCode.RIGHT, 49 | up: Phaser.KeyCode.UP 50 | }); 51 | 52 | this.keys.up.onDown.add(function () { 53 | let didJump = this.hero.jump(); 54 | if (didJump) { 55 | this.sfx.jump.play(); 56 | } 57 | }, this); 58 | }; 59 | 60 | PlayState.preload = function () { 61 | this.game.load.json('level:1', 'data/level01.json'); 62 | 63 | this.game.load.image('background', 'images/background.png'); 64 | this.game.load.image('ground', 'images/ground.png'); 65 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 66 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 67 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 68 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 69 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 70 | this.game.load.image('hero', 'images/hero_stopped.png'); 71 | 72 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 73 | }; 74 | 75 | PlayState.create = function () { 76 | // create sound entities 77 | this.sfx = { 78 | jump: this.game.add.audio('sfx:jump') 79 | }; 80 | 81 | this.game.add.image(0, 0, 'background'); 82 | this._loadLevel(this.game.cache.getJSON('level:1')); 83 | }; 84 | 85 | PlayState.update = function () { 86 | this._handleCollisions(); 87 | this._handleInput(); 88 | }; 89 | 90 | PlayState._handleCollisions = function () { 91 | this.game.physics.arcade.collide(this.hero, this.platforms); 92 | }; 93 | 94 | PlayState._handleInput = function () { 95 | if (this.keys.left.isDown) { // move hero left 96 | this.hero.move(-1); 97 | } 98 | else if (this.keys.right.isDown) { // move hero right 99 | this.hero.move(1); 100 | } 101 | else { // stop 102 | this.hero.move(0); 103 | } 104 | }; 105 | 106 | PlayState._loadLevel = function (data) { 107 | // create all the groups/layers that we need 108 | this.platforms = this.game.add.group(); 109 | 110 | // spawn all platforms 111 | data.platforms.forEach(this._spawnPlatform, this); 112 | // spawn hero and enemies 113 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 114 | 115 | // enable gravity 116 | const GRAVITY = 1200; 117 | this.game.physics.arcade.gravity.y = GRAVITY; 118 | }; 119 | 120 | PlayState._spawnPlatform = function (platform) { 121 | let sprite = this.platforms.create( 122 | platform.x, platform.y, platform.image); 123 | 124 | this.game.physics.enable(sprite); 125 | sprite.body.allowGravity = false; 126 | sprite.body.immovable = true; 127 | }; 128 | 129 | PlayState._spawnCharacters = function (data) { 130 | // spawn hero 131 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 132 | this.game.add.existing(this.hero); 133 | }; 134 | 135 | // ============================================================================= 136 | // entry point 137 | // ============================================================================= 138 | 139 | window.onload = function () { 140 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 141 | game.state.add('play', PlayState); 142 | game.state.start('play'); 143 | }; 144 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step09.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | } 16 | 17 | // inherit from Phaser.Sprite 18 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 19 | Hero.prototype.constructor = Hero; 20 | 21 | Hero.prototype.move = function (direction) { 22 | const SPEED = 200; 23 | this.body.velocity.x = direction * SPEED; 24 | }; 25 | 26 | Hero.prototype.jump = function () { 27 | const JUMP_SPEED = 600; 28 | let canJump = this.body.touching.down; 29 | 30 | if (canJump) { 31 | this.body.velocity.y = -JUMP_SPEED; 32 | } 33 | 34 | return canJump; 35 | }; 36 | 37 | // ============================================================================= 38 | // game states 39 | // ============================================================================= 40 | 41 | PlayState = {}; 42 | 43 | PlayState.init = function () { 44 | this.game.renderer.renderSession.roundPixels = true; 45 | 46 | this.keys = this.game.input.keyboard.addKeys({ 47 | left: Phaser.KeyCode.LEFT, 48 | right: Phaser.KeyCode.RIGHT, 49 | up: Phaser.KeyCode.UP 50 | }); 51 | 52 | this.keys.up.onDown.add(function () { 53 | let didJump = this.hero.jump(); 54 | if (didJump) { 55 | this.sfx.jump.play(); 56 | } 57 | }, this); 58 | }; 59 | 60 | PlayState.preload = function () { 61 | this.game.load.json('level:1', 'data/level01.json'); 62 | 63 | this.game.load.image('background', 'images/background.png'); 64 | this.game.load.image('ground', 'images/ground.png'); 65 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 66 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 67 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 68 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 69 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 70 | this.game.load.image('hero', 'images/hero_stopped.png'); 71 | 72 | this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22); 73 | 74 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 75 | this.game.load.audio('sfx:coin', 'audio/coin.wav'); 76 | }; 77 | 78 | PlayState.create = function () { 79 | // create sound entities 80 | this.sfx = { 81 | jump: this.game.add.audio('sfx:jump'), 82 | coin: this.game.add.audio('sfx:coin'), 83 | }; 84 | 85 | this.game.add.image(0, 0, 'background'); 86 | this._loadLevel(this.game.cache.getJSON('level:1')); 87 | }; 88 | 89 | PlayState.update = function () { 90 | this._handleCollisions(); 91 | this._handleInput(); 92 | }; 93 | 94 | PlayState._handleCollisions = function () { 95 | this.game.physics.arcade.collide(this.hero, this.platforms); 96 | 97 | this.game.physics.arcade.overlap(this.hero, this.coins, this._onHeroVsCoin, 98 | null, this); 99 | }; 100 | 101 | PlayState._handleInput = function () { 102 | if (this.keys.left.isDown) { // move hero left 103 | this.hero.move(-1); 104 | } 105 | else if (this.keys.right.isDown) { // move hero right 106 | this.hero.move(1); 107 | } 108 | else { // stop 109 | this.hero.move(0); 110 | } 111 | }; 112 | 113 | PlayState._loadLevel = function (data) { 114 | // create all the groups/layers that we need 115 | this.platforms = this.game.add.group(); 116 | this.coins = this.game.add.group(); 117 | 118 | // spawn all platforms 119 | data.platforms.forEach(this._spawnPlatform, this); 120 | // spawn hero and enemies 121 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 122 | // spawn important objects 123 | data.coins.forEach(this._spawnCoin, this); 124 | 125 | // enable gravity 126 | const GRAVITY = 1200; 127 | this.game.physics.arcade.gravity.y = GRAVITY; 128 | }; 129 | 130 | PlayState._spawnPlatform = function (platform) { 131 | let sprite = this.platforms.create( 132 | platform.x, platform.y, platform.image); 133 | 134 | this.game.physics.enable(sprite); 135 | sprite.body.allowGravity = false; 136 | sprite.body.immovable = true; 137 | }; 138 | 139 | PlayState._spawnCharacters = function (data) { 140 | // spawn hero 141 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 142 | this.game.add.existing(this.hero); 143 | }; 144 | 145 | PlayState._spawnCoin = function (coin) { 146 | let sprite = this.coins.create(coin.x, coin.y, 'coin'); 147 | sprite.anchor.set(0.5, 0.5); 148 | 149 | this.game.physics.enable(sprite); 150 | sprite.body.allowGravity = false; 151 | 152 | sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looped 153 | sprite.animations.play('rotate'); 154 | }; 155 | 156 | PlayState._onHeroVsCoin = function (hero, coin) { 157 | this.sfx.coin.play(); 158 | coin.kill(); 159 | }; 160 | 161 | // ============================================================================= 162 | // entry point 163 | // ============================================================================= 164 | 165 | window.onload = function () { 166 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 167 | game.state.add('play', PlayState); 168 | game.state.start('play'); 169 | }; 170 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step10.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | } 16 | 17 | // inherit from Phaser.Sprite 18 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 19 | Hero.prototype.constructor = Hero; 20 | 21 | Hero.prototype.move = function (direction) { 22 | const SPEED = 200; 23 | this.body.velocity.x = direction * SPEED; 24 | }; 25 | 26 | Hero.prototype.jump = function () { 27 | const JUMP_SPEED = 600; 28 | let canJump = this.body.touching.down; 29 | 30 | if (canJump) { 31 | this.body.velocity.y = -JUMP_SPEED; 32 | } 33 | 34 | return canJump; 35 | }; 36 | 37 | // 38 | // Spider (enemy) 39 | // 40 | function Spider(game, x, y) { 41 | Phaser.Sprite.call(this, game, x, y, 'spider'); 42 | 43 | // anchor 44 | this.anchor.set(0.5); 45 | // animation 46 | this.animations.add('crawl', [0, 1, 2], 8, true); // 8fps, looped 47 | this.animations.play('crawl'); 48 | 49 | // physic properties 50 | this.game.physics.enable(this); 51 | this.body.collideWorldBounds = true; 52 | this.body.velocity.x = Spider.SPEED; 53 | } 54 | 55 | Spider.SPEED = 100; 56 | 57 | // inherit from Phaser.Sprite 58 | Spider.prototype = Object.create(Phaser.Sprite.prototype); 59 | Spider.prototype.constructor = Spider; 60 | 61 | Spider.prototype.update = function () { 62 | // check against walls and reverse direction if necessary 63 | if (this.body.touching.right || this.body.blocked.right) { 64 | this.body.velocity.x = -Spider.SPEED; // turn left 65 | } 66 | else if (this.body.touching.left || this.body.blocked.left) { 67 | this.body.velocity.x = Spider.SPEED; // turn right 68 | } 69 | }; 70 | 71 | // ============================================================================= 72 | // game states 73 | // ============================================================================= 74 | 75 | PlayState = {}; 76 | 77 | PlayState.init = function () { 78 | this.game.renderer.renderSession.roundPixels = true; 79 | 80 | this.keys = this.game.input.keyboard.addKeys({ 81 | left: Phaser.KeyCode.LEFT, 82 | right: Phaser.KeyCode.RIGHT, 83 | up: Phaser.KeyCode.UP 84 | }); 85 | 86 | this.keys.up.onDown.add(function () { 87 | let didJump = this.hero.jump(); 88 | if (didJump) { 89 | this.sfx.jump.play(); 90 | } 91 | }, this); 92 | }; 93 | 94 | PlayState.preload = function () { 95 | this.game.load.json('level:1', 'data/level01.json'); 96 | 97 | this.game.load.image('background', 'images/background.png'); 98 | this.game.load.image('ground', 'images/ground.png'); 99 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 100 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 101 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 102 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 103 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 104 | this.game.load.image('hero', 'images/hero_stopped.png'); 105 | this.game.load.image('invisible-wall', 'images/invisible_wall.png'); 106 | 107 | this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22); 108 | this.game.load.spritesheet('spider', 'images/spider.png', 42, 32); 109 | 110 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 111 | this.game.load.audio('sfx:coin', 'audio/coin.wav'); 112 | }; 113 | 114 | PlayState.create = function () { 115 | // create sound entities 116 | this.sfx = { 117 | jump: this.game.add.audio('sfx:jump'), 118 | coin: this.game.add.audio('sfx:coin'), 119 | }; 120 | 121 | this.game.add.image(0, 0, 'background'); 122 | this._loadLevel(this.game.cache.getJSON('level:1')); 123 | }; 124 | 125 | PlayState.update = function () { 126 | this._handleCollisions(); 127 | this._handleInput(); 128 | }; 129 | 130 | PlayState._handleCollisions = function () { 131 | this.game.physics.arcade.collide(this.spiders, this.platforms); 132 | this.game.physics.arcade.collide(this.spiders, this.enemyWalls); 133 | this.game.physics.arcade.collide(this.hero, this.platforms); 134 | 135 | this.game.physics.arcade.overlap(this.hero, this.coins, this._onHeroVsCoin, 136 | null, this); 137 | }; 138 | 139 | PlayState._handleInput = function () { 140 | if (this.keys.left.isDown) { // move hero left 141 | this.hero.move(-1); 142 | } 143 | else if (this.keys.right.isDown) { // move hero right 144 | this.hero.move(1); 145 | } 146 | else { // stop 147 | this.hero.move(0); 148 | } 149 | }; 150 | 151 | PlayState._loadLevel = function (data) { 152 | // create all the groups/layers that we need 153 | this.platforms = this.game.add.group(); 154 | this.coins = this.game.add.group(); 155 | this.spiders = this.game.add.group(); 156 | this.enemyWalls = this.game.add.group(); 157 | this.enemyWalls.visible = false; 158 | 159 | // spawn all platforms 160 | data.platforms.forEach(this._spawnPlatform, this); 161 | // spawn hero and enemies 162 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 163 | // spawn important objects 164 | data.coins.forEach(this._spawnCoin, this); 165 | 166 | // enable gravity 167 | const GRAVITY = 1200; 168 | this.game.physics.arcade.gravity.y = GRAVITY; 169 | }; 170 | 171 | PlayState._spawnPlatform = function (platform) { 172 | let sprite = this.platforms.create( 173 | platform.x, platform.y, platform.image); 174 | 175 | this.game.physics.enable(sprite); 176 | sprite.body.allowGravity = false; 177 | sprite.body.immovable = true; 178 | 179 | this._spawnEnemyWall(platform.x, platform.y, 'left'); 180 | this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right'); 181 | }; 182 | 183 | PlayState._spawnEnemyWall = function (x, y, side) { 184 | let sprite = this.enemyWalls.create(x, y, 'invisible-wall'); 185 | // anchor and y displacement 186 | sprite.anchor.set(side === 'left' ? 1 : 0, 1); 187 | // physic properties 188 | this.game.physics.enable(sprite); 189 | sprite.body.immovable = true; 190 | sprite.body.allowGravity = false; 191 | }; 192 | 193 | PlayState._spawnCharacters = function (data) { 194 | // spawn spiders 195 | data.spiders.forEach(function (spider) { 196 | let sprite = new Spider(this.game, spider.x, spider.y); 197 | this.spiders.add(sprite); 198 | }, this); 199 | 200 | // spawn hero 201 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 202 | this.game.add.existing(this.hero); 203 | }; 204 | 205 | PlayState._spawnCoin = function (coin) { 206 | let sprite = this.coins.create(coin.x, coin.y, 'coin'); 207 | sprite.anchor.set(0.5, 0.5); 208 | 209 | this.game.physics.enable(sprite); 210 | sprite.body.allowGravity = false; 211 | 212 | sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looped 213 | sprite.animations.play('rotate'); 214 | }; 215 | 216 | PlayState._onHeroVsCoin = function (hero, coin) { 217 | this.sfx.coin.play(); 218 | coin.kill(); 219 | }; 220 | 221 | // ============================================================================= 222 | // entry point 223 | // ============================================================================= 224 | 225 | window.onload = function () { 226 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 227 | game.state.add('play', PlayState); 228 | game.state.start('play'); 229 | }; 230 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step11.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | } 16 | 17 | // inherit from Phaser.Sprite 18 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 19 | Hero.prototype.constructor = Hero; 20 | 21 | Hero.prototype.move = function (direction) { 22 | const SPEED = 200; 23 | this.body.velocity.x = direction * SPEED; 24 | }; 25 | 26 | Hero.prototype.jump = function () { 27 | const JUMP_SPEED = 600; 28 | let canJump = this.body.touching.down; 29 | 30 | if (canJump) { 31 | this.body.velocity.y = -JUMP_SPEED; 32 | } 33 | 34 | return canJump; 35 | }; 36 | 37 | Hero.prototype.bounce = function () { 38 | const BOUNCE_SPEED = 200; 39 | this.body.velocity.y = -BOUNCE_SPEED; 40 | }; 41 | 42 | // 43 | // Spider (enemy) 44 | // 45 | function Spider(game, x, y) { 46 | Phaser.Sprite.call(this, game, x, y, 'spider'); 47 | 48 | // anchor 49 | this.anchor.set(0.5); 50 | // animation 51 | this.animations.add('crawl', [0, 1, 2], 8, true); // 8fps, looped 52 | this.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12); 53 | this.animations.play('crawl'); 54 | 55 | // physic properties 56 | this.game.physics.enable(this); 57 | this.body.collideWorldBounds = true; 58 | this.body.velocity.x = Spider.SPEED; 59 | } 60 | 61 | Spider.SPEED = 100; 62 | 63 | // inherit from Phaser.Sprite 64 | Spider.prototype = Object.create(Phaser.Sprite.prototype); 65 | Spider.prototype.constructor = Spider; 66 | 67 | Spider.prototype.update = function () { 68 | // check against walls and reverse direction if necessary 69 | if (this.body.touching.right || this.body.blocked.right) { 70 | this.body.velocity.x = -Spider.SPEED; // turn left 71 | } 72 | else if (this.body.touching.left || this.body.blocked.left) { 73 | this.body.velocity.x = Spider.SPEED; // turn right 74 | } 75 | }; 76 | 77 | Spider.prototype.die = function () { 78 | this.body.enable = false; 79 | 80 | this.animations.play('die').onComplete.addOnce(function () { 81 | this.kill(); 82 | }, this); 83 | }; 84 | 85 | 86 | // ============================================================================= 87 | // game states 88 | // ============================================================================= 89 | 90 | PlayState = {}; 91 | 92 | PlayState.init = function () { 93 | this.game.renderer.renderSession.roundPixels = true; 94 | 95 | this.keys = this.game.input.keyboard.addKeys({ 96 | left: Phaser.KeyCode.LEFT, 97 | right: Phaser.KeyCode.RIGHT, 98 | up: Phaser.KeyCode.UP 99 | }); 100 | 101 | this.keys.up.onDown.add(function () { 102 | let didJump = this.hero.jump(); 103 | if (didJump) { 104 | this.sfx.jump.play(); 105 | } 106 | }, this); 107 | }; 108 | 109 | PlayState.preload = function () { 110 | this.game.load.json('level:1', 'data/level01.json'); 111 | 112 | this.game.load.image('background', 'images/background.png'); 113 | this.game.load.image('ground', 'images/ground.png'); 114 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 115 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 116 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 117 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 118 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 119 | this.game.load.image('hero', 'images/hero_stopped.png'); 120 | this.game.load.image('invisible-wall', 'images/invisible_wall.png'); 121 | 122 | this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22); 123 | this.game.load.spritesheet('spider', 'images/spider.png', 42, 32); 124 | 125 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 126 | this.game.load.audio('sfx:coin', 'audio/coin.wav'); 127 | this.game.load.audio('sfx:stomp', 'audio/stomp.wav'); 128 | }; 129 | 130 | PlayState.create = function () { 131 | // create sound entities 132 | this.sfx = { 133 | jump: this.game.add.audio('sfx:jump'), 134 | coin: this.game.add.audio('sfx:coin'), 135 | stomp: this.game.add.audio('sfx:stomp') 136 | }; 137 | 138 | this.game.add.image(0, 0, 'background'); 139 | this._loadLevel(this.game.cache.getJSON('level:1')); 140 | }; 141 | 142 | PlayState.update = function () { 143 | this._handleCollisions(); 144 | this._handleInput(); 145 | }; 146 | 147 | PlayState._handleCollisions = function () { 148 | this.game.physics.arcade.collide(this.spiders, this.platforms); 149 | this.game.physics.arcade.collide(this.spiders, this.enemyWalls); 150 | this.game.physics.arcade.collide(this.hero, this.platforms); 151 | 152 | this.game.physics.arcade.overlap(this.hero, this.coins, this._onHeroVsCoin, 153 | null, this); 154 | this.game.physics.arcade.overlap(this.hero, this.spiders, 155 | this._onHeroVsEnemy, null, this); 156 | }; 157 | 158 | PlayState._handleInput = function () { 159 | if (this.keys.left.isDown) { // move hero left 160 | this.hero.move(-1); 161 | } 162 | else if (this.keys.right.isDown) { // move hero right 163 | this.hero.move(1); 164 | } 165 | else { // stop 166 | this.hero.move(0); 167 | } 168 | }; 169 | 170 | PlayState._loadLevel = function (data) { 171 | // create all the groups/layers that we need 172 | this.platforms = this.game.add.group(); 173 | this.coins = this.game.add.group(); 174 | this.spiders = this.game.add.group(); 175 | this.enemyWalls = this.game.add.group(); 176 | this.enemyWalls.visible = false; 177 | 178 | // spawn all platforms 179 | data.platforms.forEach(this._spawnPlatform, this); 180 | // spawn hero and enemies 181 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 182 | // spawn important objects 183 | data.coins.forEach(this._spawnCoin, this); 184 | 185 | // enable gravity 186 | const GRAVITY = 1200; 187 | this.game.physics.arcade.gravity.y = GRAVITY; 188 | }; 189 | 190 | PlayState._spawnPlatform = function (platform) { 191 | let sprite = this.platforms.create( 192 | platform.x, platform.y, platform.image); 193 | 194 | this.game.physics.enable(sprite); 195 | sprite.body.allowGravity = false; 196 | sprite.body.immovable = true; 197 | 198 | this._spawnEnemyWall(platform.x, platform.y, 'left'); 199 | this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right'); 200 | }; 201 | 202 | PlayState._spawnEnemyWall = function (x, y, side) { 203 | let sprite = this.enemyWalls.create(x, y, 'invisible-wall'); 204 | // anchor and y displacement 205 | sprite.anchor.set(side === 'left' ? 1 : 0, 1); 206 | // physic properties 207 | this.game.physics.enable(sprite); 208 | sprite.body.immovable = true; 209 | sprite.body.allowGravity = false; 210 | }; 211 | 212 | PlayState._spawnCharacters = function (data) { 213 | // spawn spiders 214 | data.spiders.forEach(function (spider) { 215 | let sprite = new Spider(this.game, spider.x, spider.y); 216 | this.spiders.add(sprite); 217 | }, this); 218 | 219 | // spawn hero 220 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 221 | this.game.add.existing(this.hero); 222 | }; 223 | 224 | PlayState._spawnCoin = function (coin) { 225 | let sprite = this.coins.create(coin.x, coin.y, 'coin'); 226 | sprite.anchor.set(0.5, 0.5); 227 | 228 | this.game.physics.enable(sprite); 229 | sprite.body.allowGravity = false; 230 | 231 | sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looped 232 | sprite.animations.play('rotate'); 233 | }; 234 | 235 | PlayState._onHeroVsCoin = function (hero, coin) { 236 | this.sfx.coin.play(); 237 | coin.kill(); 238 | }; 239 | 240 | PlayState._onHeroVsEnemy = function (hero, enemy) { 241 | if (hero.body.velocity.y > 0) { // kill enemies when hero is falling 242 | hero.bounce(); 243 | enemy.die(); 244 | this.sfx.stomp.play(); 245 | } 246 | else { // game over -> restart the game 247 | this.sfx.stomp.play(); 248 | this.game.state.restart(); 249 | } 250 | }; 251 | 252 | // ============================================================================= 253 | // entry point 254 | // ============================================================================= 255 | 256 | window.onload = function () { 257 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 258 | game.state.add('play', PlayState); 259 | game.state.start('play'); 260 | }; 261 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step12.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | } 16 | 17 | // inherit from Phaser.Sprite 18 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 19 | Hero.prototype.constructor = Hero; 20 | 21 | Hero.prototype.move = function (direction) { 22 | const SPEED = 200; 23 | this.body.velocity.x = direction * SPEED; 24 | }; 25 | 26 | Hero.prototype.jump = function () { 27 | const JUMP_SPEED = 600; 28 | let canJump = this.body.touching.down; 29 | 30 | if (canJump) { 31 | this.body.velocity.y = -JUMP_SPEED; 32 | } 33 | 34 | return canJump; 35 | }; 36 | 37 | Hero.prototype.bounce = function () { 38 | const BOUNCE_SPEED = 200; 39 | this.body.velocity.y = -BOUNCE_SPEED; 40 | }; 41 | 42 | // 43 | // Spider (enemy) 44 | // 45 | function Spider(game, x, y) { 46 | Phaser.Sprite.call(this, game, x, y, 'spider'); 47 | 48 | // anchor 49 | this.anchor.set(0.5); 50 | // animation 51 | this.animations.add('crawl', [0, 1, 2], 8, true); // 8fps, looped 52 | this.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12); 53 | this.animations.play('crawl'); 54 | 55 | // physic properties 56 | this.game.physics.enable(this); 57 | this.body.collideWorldBounds = true; 58 | this.body.velocity.x = Spider.SPEED; 59 | } 60 | 61 | Spider.SPEED = 100; 62 | 63 | // inherit from Phaser.Sprite 64 | Spider.prototype = Object.create(Phaser.Sprite.prototype); 65 | Spider.prototype.constructor = Spider; 66 | 67 | Spider.prototype.update = function () { 68 | // check against walls and reverse direction if necessary 69 | if (this.body.touching.right || this.body.blocked.right) { 70 | this.body.velocity.x = -Spider.SPEED; // turn left 71 | } 72 | else if (this.body.touching.left || this.body.blocked.left) { 73 | this.body.velocity.x = Spider.SPEED; // turn right 74 | } 75 | }; 76 | 77 | Spider.prototype.die = function () { 78 | this.body.enable = false; 79 | 80 | this.animations.play('die').onComplete.addOnce(function () { 81 | this.kill(); 82 | }, this); 83 | }; 84 | 85 | 86 | // ============================================================================= 87 | // game states 88 | // ============================================================================= 89 | 90 | PlayState = {}; 91 | 92 | PlayState.init = function () { 93 | this.game.renderer.renderSession.roundPixels = true; 94 | 95 | this.keys = this.game.input.keyboard.addKeys({ 96 | left: Phaser.KeyCode.LEFT, 97 | right: Phaser.KeyCode.RIGHT, 98 | up: Phaser.KeyCode.UP 99 | }); 100 | 101 | this.keys.up.onDown.add(function () { 102 | let didJump = this.hero.jump(); 103 | if (didJump) { 104 | this.sfx.jump.play(); 105 | } 106 | }, this); 107 | 108 | this.coinPickupCount = 0; 109 | }; 110 | 111 | PlayState.preload = function () { 112 | this.game.load.json('level:1', 'data/level01.json'); 113 | 114 | this.game.load.image('font:numbers', 'images/numbers.png'); 115 | 116 | this.game.load.image('background', 'images/background.png'); 117 | this.game.load.image('ground', 'images/ground.png'); 118 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 119 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 120 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 121 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 122 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 123 | this.game.load.image('hero', 'images/hero_stopped.png'); 124 | this.game.load.image('invisible-wall', 'images/invisible_wall.png'); 125 | this.game.load.image('icon:coin', 'images/coin_icon.png'); 126 | 127 | this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22); 128 | this.game.load.spritesheet('spider', 'images/spider.png', 42, 32); 129 | 130 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 131 | this.game.load.audio('sfx:coin', 'audio/coin.wav'); 132 | this.game.load.audio('sfx:stomp', 'audio/stomp.wav'); 133 | }; 134 | 135 | PlayState.create = function () { 136 | // create sound entities 137 | this.sfx = { 138 | jump: this.game.add.audio('sfx:jump'), 139 | coin: this.game.add.audio('sfx:coin'), 140 | stomp: this.game.add.audio('sfx:stomp') 141 | }; 142 | 143 | // create level 144 | this.game.add.image(0, 0, 'background'); 145 | this._loadLevel(this.game.cache.getJSON('level:1')); 146 | 147 | // crete hud with scoreboards) 148 | this._createHud(); 149 | }; 150 | 151 | PlayState.update = function () { 152 | this._handleCollisions(); 153 | this._handleInput(); 154 | 155 | this.coinFont.text = `x${this.coinPickupCount}`; 156 | }; 157 | 158 | PlayState._handleCollisions = function () { 159 | this.game.physics.arcade.collide(this.spiders, this.platforms); 160 | this.game.physics.arcade.collide(this.spiders, this.enemyWalls); 161 | this.game.physics.arcade.collide(this.hero, this.platforms); 162 | 163 | this.game.physics.arcade.overlap(this.hero, this.coins, this._onHeroVsCoin, 164 | null, this); 165 | this.game.physics.arcade.overlap(this.hero, this.spiders, 166 | this._onHeroVsEnemy, null, this); 167 | }; 168 | 169 | PlayState._handleInput = function () { 170 | if (this.keys.left.isDown) { // move hero left 171 | this.hero.move(-1); 172 | } 173 | else if (this.keys.right.isDown) { // move hero right 174 | this.hero.move(1); 175 | } 176 | else { // stop 177 | this.hero.move(0); 178 | } 179 | }; 180 | 181 | PlayState._loadLevel = function (data) { 182 | // create all the groups/layers that we need 183 | this.platforms = this.game.add.group(); 184 | this.coins = this.game.add.group(); 185 | this.spiders = this.game.add.group(); 186 | this.enemyWalls = this.game.add.group(); 187 | this.enemyWalls.visible = false; 188 | 189 | // spawn all platforms 190 | data.platforms.forEach(this._spawnPlatform, this); 191 | // spawn hero and enemies 192 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 193 | // spawn important objects 194 | data.coins.forEach(this._spawnCoin, this); 195 | 196 | // enable gravity 197 | const GRAVITY = 1200; 198 | this.game.physics.arcade.gravity.y = GRAVITY; 199 | }; 200 | 201 | PlayState._spawnPlatform = function (platform) { 202 | let sprite = this.platforms.create( 203 | platform.x, platform.y, platform.image); 204 | 205 | this.game.physics.enable(sprite); 206 | sprite.body.allowGravity = false; 207 | sprite.body.immovable = true; 208 | 209 | this._spawnEnemyWall(platform.x, platform.y, 'left'); 210 | this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right'); 211 | }; 212 | 213 | PlayState._spawnEnemyWall = function (x, y, side) { 214 | let sprite = this.enemyWalls.create(x, y, 'invisible-wall'); 215 | // anchor and y displacement 216 | sprite.anchor.set(side === 'left' ? 1 : 0, 1); 217 | // physic properties 218 | this.game.physics.enable(sprite); 219 | sprite.body.immovable = true; 220 | sprite.body.allowGravity = false; 221 | }; 222 | 223 | PlayState._spawnCharacters = function (data) { 224 | // spawn spiders 225 | data.spiders.forEach(function (spider) { 226 | let sprite = new Spider(this.game, spider.x, spider.y); 227 | this.spiders.add(sprite); 228 | }, this); 229 | 230 | // spawn hero 231 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 232 | this.game.add.existing(this.hero); 233 | }; 234 | 235 | PlayState._spawnCoin = function (coin) { 236 | let sprite = this.coins.create(coin.x, coin.y, 'coin'); 237 | sprite.anchor.set(0.5, 0.5); 238 | 239 | this.game.physics.enable(sprite); 240 | sprite.body.allowGravity = false; 241 | 242 | sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looped 243 | sprite.animations.play('rotate'); 244 | }; 245 | 246 | PlayState._onHeroVsCoin = function (hero, coin) { 247 | this.sfx.coin.play(); 248 | coin.kill(); 249 | this.coinPickupCount++; 250 | }; 251 | 252 | PlayState._onHeroVsEnemy = function (hero, enemy) { 253 | if (hero.body.velocity.y > 0) { // kill enemies when hero is falling 254 | hero.bounce(); 255 | enemy.die(); 256 | this.sfx.stomp.play(); 257 | } 258 | else { // game over -> restart the game 259 | this.sfx.stomp.play(); 260 | this.game.state.restart(); 261 | } 262 | }; 263 | 264 | PlayState._createHud = function () { 265 | const NUMBERS_STR = '0123456789X '; 266 | this.coinFont = this.game.add.retroFont('font:numbers', 20, 26, 267 | NUMBERS_STR); 268 | 269 | let coinIcon = this.game.make.image(0, 0, 'icon:coin'); 270 | let coinScoreImg = this.game.make.image(coinIcon.x + coinIcon.width, 271 | coinIcon.height / 2, this.coinFont); 272 | coinScoreImg.anchor.set(0, 0.5); 273 | 274 | this.hud = this.game.add.group(); 275 | this.hud.add(coinIcon); 276 | this.hud.add(coinScoreImg); 277 | this.hud.position.set(10, 10); 278 | }; 279 | 280 | // ============================================================================= 281 | // entry point 282 | // ============================================================================= 283 | 284 | window.onload = function () { 285 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 286 | game.state.add('play', PlayState); 287 | game.state.start('play'); 288 | }; 289 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step13.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | 16 | this.animations.add('stop', [0]); 17 | this.animations.add('run', [1, 2], 8, true); // 8fps looped 18 | this.animations.add('jump', [3]); 19 | this.animations.add('fall', [4]); 20 | } 21 | 22 | // inherit from Phaser.Sprite 23 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 24 | Hero.prototype.constructor = Hero; 25 | 26 | Hero.prototype.move = function (direction) { 27 | const SPEED = 200; 28 | this.body.velocity.x = direction * SPEED; 29 | 30 | // update image flipping & animations 31 | if (this.body.velocity.x < 0) { 32 | this.scale.x = -1; 33 | } 34 | else if (this.body.velocity.x > 0) { 35 | this.scale.x = 1; 36 | } 37 | }; 38 | 39 | Hero.prototype.jump = function () { 40 | const JUMP_SPEED = 600; 41 | let canJump = this.body.touching.down; 42 | 43 | if (canJump) { 44 | this.body.velocity.y = -JUMP_SPEED; 45 | } 46 | 47 | return canJump; 48 | }; 49 | 50 | Hero.prototype.bounce = function () { 51 | const BOUNCE_SPEED = 200; 52 | this.body.velocity.y = -BOUNCE_SPEED; 53 | }; 54 | 55 | Hero.prototype.update = function () { 56 | // update sprite animation, if it needs changing 57 | let animationName = this._getAnimationName(); 58 | if (this.animations.name !== animationName) { 59 | this.animations.play(animationName); 60 | } 61 | }; 62 | 63 | Hero.prototype._getAnimationName = function () { 64 | let name = 'stop'; // default animation 65 | 66 | // jumping 67 | if (this.body.velocity.y < 0) { 68 | name = 'jump'; 69 | } 70 | // falling 71 | else if (this.body.velocity.y >= 0 && !this.body.touching.down) { 72 | name = 'fall'; 73 | } 74 | else if (this.body.velocity.x !== 0 && this.body.touching.down) { 75 | name = 'run'; 76 | } 77 | 78 | return name; 79 | }; 80 | 81 | // 82 | // Spider (enemy) 83 | // 84 | function Spider(game, x, y) { 85 | Phaser.Sprite.call(this, game, x, y, 'spider'); 86 | 87 | // anchor 88 | this.anchor.set(0.5); 89 | // animation 90 | this.animations.add('crawl', [0, 1, 2], 8, true); // 8fps, looped 91 | this.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12); 92 | this.animations.play('crawl'); 93 | 94 | // physic properties 95 | this.game.physics.enable(this); 96 | this.body.collideWorldBounds = true; 97 | this.body.velocity.x = Spider.SPEED; 98 | } 99 | 100 | Spider.SPEED = 100; 101 | 102 | // inherit from Phaser.Sprite 103 | Spider.prototype = Object.create(Phaser.Sprite.prototype); 104 | Spider.prototype.constructor = Spider; 105 | 106 | Spider.prototype.update = function () { 107 | // check against walls and reverse direction if necessary 108 | if (this.body.touching.right || this.body.blocked.right) { 109 | this.body.velocity.x = -Spider.SPEED; // turn left 110 | } 111 | else if (this.body.touching.left || this.body.blocked.left) { 112 | this.body.velocity.x = Spider.SPEED; // turn right 113 | } 114 | }; 115 | 116 | Spider.prototype.die = function () { 117 | this.body.enable = false; 118 | 119 | this.animations.play('die').onComplete.addOnce(function () { 120 | this.kill(); 121 | }, this); 122 | }; 123 | 124 | 125 | // ============================================================================= 126 | // game states 127 | // ============================================================================= 128 | 129 | PlayState = {}; 130 | 131 | PlayState.init = function () { 132 | this.game.renderer.renderSession.roundPixels = true; 133 | 134 | this.keys = this.game.input.keyboard.addKeys({ 135 | left: Phaser.KeyCode.LEFT, 136 | right: Phaser.KeyCode.RIGHT, 137 | up: Phaser.KeyCode.UP 138 | }); 139 | 140 | this.keys.up.onDown.add(function () { 141 | let didJump = this.hero.jump(); 142 | if (didJump) { 143 | this.sfx.jump.play(); 144 | } 145 | }, this); 146 | 147 | this.coinPickupCount = 0; 148 | }; 149 | 150 | PlayState.preload = function () { 151 | this.game.load.json('level:1', 'data/level01.json'); 152 | 153 | this.game.load.image('font:numbers', 'images/numbers.png'); 154 | 155 | this.game.load.image('background', 'images/background.png'); 156 | this.game.load.image('ground', 'images/ground.png'); 157 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 158 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 159 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 160 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 161 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 162 | this.game.load.image('invisible-wall', 'images/invisible_wall.png'); 163 | this.game.load.image('icon:coin', 'images/coin_icon.png'); 164 | 165 | this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22); 166 | this.game.load.spritesheet('spider', 'images/spider.png', 42, 32); 167 | this.game.load.spritesheet('hero', 'images/hero.png', 36, 42); 168 | 169 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 170 | this.game.load.audio('sfx:coin', 'audio/coin.wav'); 171 | this.game.load.audio('sfx:stomp', 'audio/stomp.wav'); 172 | }; 173 | 174 | PlayState.create = function () { 175 | // create sound entities 176 | this.sfx = { 177 | jump: this.game.add.audio('sfx:jump'), 178 | coin: this.game.add.audio('sfx:coin'), 179 | stomp: this.game.add.audio('sfx:stomp') 180 | }; 181 | 182 | // create level 183 | this.game.add.image(0, 0, 'background'); 184 | this._loadLevel(this.game.cache.getJSON('level:1')); 185 | 186 | // crete hud with scoreboards) 187 | this._createHud(); 188 | }; 189 | 190 | PlayState.update = function () { 191 | this._handleCollisions(); 192 | this._handleInput(); 193 | 194 | this.coinFont.text = `x${this.coinPickupCount}`; 195 | }; 196 | 197 | PlayState._handleCollisions = function () { 198 | this.game.physics.arcade.collide(this.spiders, this.platforms); 199 | this.game.physics.arcade.collide(this.spiders, this.enemyWalls); 200 | this.game.physics.arcade.collide(this.hero, this.platforms); 201 | 202 | this.game.physics.arcade.overlap(this.hero, this.coins, this._onHeroVsCoin, 203 | null, this); 204 | this.game.physics.arcade.overlap(this.hero, this.spiders, 205 | this._onHeroVsEnemy, null, this); 206 | }; 207 | 208 | PlayState._handleInput = function () { 209 | if (this.keys.left.isDown) { // move hero left 210 | this.hero.move(-1); 211 | } 212 | else if (this.keys.right.isDown) { // move hero right 213 | this.hero.move(1); 214 | } 215 | else { // stop 216 | this.hero.move(0); 217 | } 218 | }; 219 | 220 | PlayState._loadLevel = function (data) { 221 | // create all the groups/layers that we need 222 | this.platforms = this.game.add.group(); 223 | this.coins = this.game.add.group(); 224 | this.spiders = this.game.add.group(); 225 | this.enemyWalls = this.game.add.group(); 226 | this.enemyWalls.visible = false; 227 | 228 | // spawn all platforms 229 | data.platforms.forEach(this._spawnPlatform, this); 230 | // spawn hero and enemies 231 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 232 | // spawn important objects 233 | data.coins.forEach(this._spawnCoin, this); 234 | 235 | // enable gravity 236 | const GRAVITY = 1200; 237 | this.game.physics.arcade.gravity.y = GRAVITY; 238 | }; 239 | 240 | PlayState._spawnPlatform = function (platform) { 241 | let sprite = this.platforms.create( 242 | platform.x, platform.y, platform.image); 243 | 244 | this.game.physics.enable(sprite); 245 | sprite.body.allowGravity = false; 246 | sprite.body.immovable = true; 247 | 248 | this._spawnEnemyWall(platform.x, platform.y, 'left'); 249 | this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right'); 250 | }; 251 | 252 | PlayState._spawnEnemyWall = function (x, y, side) { 253 | let sprite = this.enemyWalls.create(x, y, 'invisible-wall'); 254 | // anchor and y displacement 255 | sprite.anchor.set(side === 'left' ? 1 : 0, 1); 256 | // physic properties 257 | this.game.physics.enable(sprite); 258 | sprite.body.immovable = true; 259 | sprite.body.allowGravity = false; 260 | }; 261 | 262 | PlayState._spawnCharacters = function (data) { 263 | // spawn spiders 264 | data.spiders.forEach(function (spider) { 265 | let sprite = new Spider(this.game, spider.x, spider.y); 266 | this.spiders.add(sprite); 267 | }, this); 268 | 269 | // spawn hero 270 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 271 | this.game.add.existing(this.hero); 272 | }; 273 | 274 | PlayState._spawnCoin = function (coin) { 275 | let sprite = this.coins.create(coin.x, coin.y, 'coin'); 276 | sprite.anchor.set(0.5, 0.5); 277 | 278 | this.game.physics.enable(sprite); 279 | sprite.body.allowGravity = false; 280 | 281 | sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looped 282 | sprite.animations.play('rotate'); 283 | }; 284 | 285 | PlayState._onHeroVsCoin = function (hero, coin) { 286 | this.sfx.coin.play(); 287 | coin.kill(); 288 | this.coinPickupCount++; 289 | }; 290 | 291 | PlayState._onHeroVsEnemy = function (hero, enemy) { 292 | if (hero.body.velocity.y > 0) { // kill enemies when hero is falling 293 | hero.bounce(); 294 | enemy.die(); 295 | this.sfx.stomp.play(); 296 | } 297 | else { // game over -> restart the game 298 | this.sfx.stomp.play(); 299 | this.game.state.restart(); 300 | } 301 | }; 302 | 303 | PlayState._createHud = function () { 304 | const NUMBERS_STR = '0123456789X '; 305 | this.coinFont = this.game.add.retroFont('font:numbers', 20, 26, 306 | NUMBERS_STR); 307 | 308 | let coinIcon = this.game.make.image(0, 0, 'icon:coin'); 309 | let coinScoreImg = this.game.make.image(coinIcon.x + coinIcon.width, 310 | coinIcon.height / 2, this.coinFont); 311 | coinScoreImg.anchor.set(0, 0.5); 312 | 313 | this.hud = this.game.add.group(); 314 | this.hud.add(coinIcon); 315 | this.hud.add(coinScoreImg); 316 | this.hud.position.set(10, 10); 317 | }; 318 | 319 | // ============================================================================= 320 | // entry point 321 | // ============================================================================= 322 | 323 | window.onload = function () { 324 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 325 | game.state.add('play', PlayState); 326 | game.state.start('play'); 327 | }; 328 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/steps/step14.js: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // sprites 3 | // ============================================================================= 4 | 5 | // 6 | // hero sprite 7 | // 8 | function Hero(game, x, y) { 9 | Phaser.Sprite.call(this, game, x, y, 'hero'); 10 | this.anchor.set(0.5, 0.5); 11 | 12 | // physic properties 13 | this.game.physics.enable(this); 14 | this.body.collideWorldBounds = true; 15 | 16 | this.animations.add('stop', [0]); 17 | this.animations.add('run', [1, 2], 8, true); // 8fps looped 18 | this.animations.add('jump', [3]); 19 | this.animations.add('fall', [4]); 20 | } 21 | 22 | // inherit from Phaser.Sprite 23 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 24 | Hero.prototype.constructor = Hero; 25 | 26 | Hero.prototype.move = function (direction) { 27 | const SPEED = 200; 28 | this.body.velocity.x = direction * SPEED; 29 | 30 | // update image flipping & animations 31 | if (this.body.velocity.x < 0) { 32 | this.scale.x = -1; 33 | } 34 | else if (this.body.velocity.x > 0) { 35 | this.scale.x = 1; 36 | } 37 | }; 38 | 39 | Hero.prototype.jump = function () { 40 | const JUMP_SPEED = 600; 41 | let canJump = this.body.touching.down; 42 | 43 | if (canJump) { 44 | this.body.velocity.y = -JUMP_SPEED; 45 | } 46 | 47 | return canJump; 48 | }; 49 | 50 | Hero.prototype.bounce = function () { 51 | const BOUNCE_SPEED = 200; 52 | this.body.velocity.y = -BOUNCE_SPEED; 53 | }; 54 | 55 | Hero.prototype.update = function () { 56 | // update sprite animation, if it needs changing 57 | let animationName = this._getAnimationName(); 58 | if (this.animations.name !== animationName) { 59 | this.animations.play(animationName); 60 | } 61 | }; 62 | 63 | Hero.prototype._getAnimationName = function () { 64 | let name = 'stop'; // default animation 65 | 66 | // jumping 67 | if (this.body.velocity.y < 0) { 68 | name = 'jump'; 69 | } 70 | // falling 71 | else if (this.body.velocity.y >= 0 && !this.body.touching.down) { 72 | name = 'fall'; 73 | } 74 | else if (this.body.velocity.x !== 0 && this.body.touching.down) { 75 | name = 'run'; 76 | } 77 | 78 | return name; 79 | }; 80 | 81 | // 82 | // Spider (enemy) 83 | // 84 | function Spider(game, x, y) { 85 | Phaser.Sprite.call(this, game, x, y, 'spider'); 86 | 87 | // anchor 88 | this.anchor.set(0.5); 89 | // animation 90 | this.animations.add('crawl', [0, 1, 2], 8, true); // 8fps, looped 91 | this.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12); 92 | this.animations.play('crawl'); 93 | 94 | // physic properties 95 | this.game.physics.enable(this); 96 | this.body.collideWorldBounds = true; 97 | this.body.velocity.x = Spider.SPEED; 98 | } 99 | 100 | Spider.SPEED = 100; 101 | 102 | // inherit from Phaser.Sprite 103 | Spider.prototype = Object.create(Phaser.Sprite.prototype); 104 | Spider.prototype.constructor = Spider; 105 | 106 | Spider.prototype.update = function () { 107 | // check against walls and reverse direction if necessary 108 | if (this.body.touching.right || this.body.blocked.right) { 109 | this.body.velocity.x = -Spider.SPEED; // turn left 110 | } 111 | else if (this.body.touching.left || this.body.blocked.left) { 112 | this.body.velocity.x = Spider.SPEED; // turn right 113 | } 114 | }; 115 | 116 | Spider.prototype.die = function () { 117 | this.body.enable = false; 118 | 119 | this.animations.play('die').onComplete.addOnce(function () { 120 | this.kill(); 121 | }, this); 122 | }; 123 | 124 | 125 | // ============================================================================= 126 | // game states 127 | // ============================================================================= 128 | 129 | PlayState = {}; 130 | 131 | PlayState.init = function () { 132 | this.game.renderer.renderSession.roundPixels = true; 133 | 134 | this.keys = this.game.input.keyboard.addKeys({ 135 | left: Phaser.KeyCode.LEFT, 136 | right: Phaser.KeyCode.RIGHT, 137 | up: Phaser.KeyCode.UP 138 | }); 139 | 140 | this.keys.up.onDown.add(function () { 141 | let didJump = this.hero.jump(); 142 | if (didJump) { 143 | this.sfx.jump.play(); 144 | } 145 | }, this); 146 | 147 | this.coinPickupCount = 0; 148 | this.hasKey = false; 149 | }; 150 | 151 | PlayState.preload = function () { 152 | this.game.load.json('level:1', 'data/level01.json'); 153 | 154 | this.game.load.image('font:numbers', 'images/numbers.png'); 155 | 156 | this.game.load.image('background', 'images/background.png'); 157 | this.game.load.image('ground', 'images/ground.png'); 158 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 159 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 160 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 161 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 162 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 163 | this.game.load.image('invisible-wall', 'images/invisible_wall.png'); 164 | this.game.load.image('icon:coin', 'images/coin_icon.png'); 165 | this.game.load.image('key', 'images/key.png'); 166 | 167 | this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22); 168 | this.game.load.spritesheet('spider', 'images/spider.png', 42, 32); 169 | this.game.load.spritesheet('hero', 'images/hero.png', 36, 42); 170 | this.game.load.spritesheet('door', 'images/door.png', 42, 66); 171 | this.game.load.spritesheet('icon:key', 'images/key_icon.png', 34, 30); 172 | 173 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 174 | this.game.load.audio('sfx:coin', 'audio/coin.wav'); 175 | this.game.load.audio('sfx:stomp', 'audio/stomp.wav'); 176 | this.game.load.audio('sfx:key', 'audio/key.wav'); 177 | this.game.load.audio('sfx:door', 'audio/door.wav'); 178 | }; 179 | 180 | PlayState.create = function () { 181 | // create sound entities 182 | this.sfx = { 183 | jump: this.game.add.audio('sfx:jump'), 184 | coin: this.game.add.audio('sfx:coin'), 185 | stomp: this.game.add.audio('sfx:stomp'), 186 | key: this.game.add.audio('sfx:key'), 187 | door: this.game.add.audio('sfx:door') 188 | }; 189 | 190 | // create level 191 | this.game.add.image(0, 0, 'background'); 192 | this._loadLevel(this.game.cache.getJSON('level:1')); 193 | 194 | // crete hud with scoreboards) 195 | this._createHud(); 196 | }; 197 | 198 | PlayState.update = function () { 199 | this._handleCollisions(); 200 | this._handleInput(); 201 | 202 | this.coinFont.text = `x${this.coinPickupCount}`; 203 | this.keyIcon.frame = this.hasKey ? 1 : 0; 204 | }; 205 | 206 | PlayState._handleCollisions = function () { 207 | this.game.physics.arcade.collide(this.spiders, this.platforms); 208 | this.game.physics.arcade.collide(this.spiders, this.enemyWalls); 209 | this.game.physics.arcade.collide(this.hero, this.platforms); 210 | 211 | this.game.physics.arcade.overlap(this.hero, this.coins, this._onHeroVsCoin, 212 | null, this); 213 | this.game.physics.arcade.overlap(this.hero, this.spiders, 214 | this._onHeroVsEnemy, null, this); 215 | this.game.physics.arcade.overlap(this.hero, this.key, this._onHeroVsKey, 216 | null, this) 217 | this.game.physics.arcade.overlap(this.hero, this.door, this._onHeroVsDoor, 218 | // ignore if there is no key or the player is on air 219 | function (hero, door) { 220 | return this.hasKey && hero.body.touching.down; 221 | }, this); 222 | }; 223 | 224 | PlayState._handleInput = function () { 225 | if (this.keys.left.isDown) { // move hero left 226 | this.hero.move(-1); 227 | } 228 | else if (this.keys.right.isDown) { // move hero right 229 | this.hero.move(1); 230 | } 231 | else { // stop 232 | this.hero.move(0); 233 | } 234 | }; 235 | 236 | PlayState._loadLevel = function (data) { 237 | // create all the groups/layers that we need 238 | this.bgDecoration = this.game.add.group(); 239 | this.platforms = this.game.add.group(); 240 | this.coins = this.game.add.group(); 241 | this.spiders = this.game.add.group(); 242 | this.enemyWalls = this.game.add.group(); 243 | this.enemyWalls.visible = false; 244 | 245 | // spawn all platforms 246 | data.platforms.forEach(this._spawnPlatform, this); 247 | // spawn hero and enemies 248 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 249 | // spawn important objects 250 | data.coins.forEach(this._spawnCoin, this); 251 | this._spawnDoor(data.door.x, data.door.y); 252 | this._spawnKey(data.key.x, data.key.y); 253 | 254 | // enable gravity 255 | const GRAVITY = 1200; 256 | this.game.physics.arcade.gravity.y = GRAVITY; 257 | }; 258 | 259 | PlayState._spawnPlatform = function (platform) { 260 | let sprite = this.platforms.create( 261 | platform.x, platform.y, platform.image); 262 | 263 | this.game.physics.enable(sprite); 264 | sprite.body.allowGravity = false; 265 | sprite.body.immovable = true; 266 | 267 | this._spawnEnemyWall(platform.x, platform.y, 'left'); 268 | this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right'); 269 | }; 270 | 271 | PlayState._spawnEnemyWall = function (x, y, side) { 272 | let sprite = this.enemyWalls.create(x, y, 'invisible-wall'); 273 | // anchor and y displacement 274 | sprite.anchor.set(side === 'left' ? 1 : 0, 1); 275 | // physic properties 276 | this.game.physics.enable(sprite); 277 | sprite.body.immovable = true; 278 | sprite.body.allowGravity = false; 279 | }; 280 | 281 | PlayState._spawnCharacters = function (data) { 282 | // spawn spiders 283 | data.spiders.forEach(function (spider) { 284 | let sprite = new Spider(this.game, spider.x, spider.y); 285 | this.spiders.add(sprite); 286 | }, this); 287 | 288 | // spawn hero 289 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 290 | this.game.add.existing(this.hero); 291 | }; 292 | 293 | PlayState._spawnCoin = function (coin) { 294 | let sprite = this.coins.create(coin.x, coin.y, 'coin'); 295 | sprite.anchor.set(0.5, 0.5); 296 | 297 | this.game.physics.enable(sprite); 298 | sprite.body.allowGravity = false; 299 | 300 | sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looped 301 | sprite.animations.play('rotate'); 302 | }; 303 | 304 | PlayState._spawnDoor = function (x, y) { 305 | this.door = this.bgDecoration.create(x, y, 'door'); 306 | this.door.anchor.setTo(0.5, 1); 307 | this.game.physics.enable(this.door); 308 | this.door.body.allowGravity = false; 309 | }; 310 | 311 | PlayState._spawnKey = function (x, y) { 312 | this.key = this.bgDecoration.create(x, y, 'key'); 313 | this.key.anchor.set(0.5, 0.5); 314 | // enable physics to detect collisions, so the hero can pick the key up 315 | this.game.physics.enable(this.key); 316 | this.key.body.allowGravity = false; 317 | // add a small 'up & down' animation via a tween 318 | this.key.y -= 3; 319 | this.game.add.tween(this.key) 320 | .to({y: this.key.y + 6}, 800, Phaser.Easing.Sinusoidal.InOut) 321 | .yoyo(true) 322 | .loop() 323 | .start(); 324 | }; 325 | 326 | 327 | PlayState._onHeroVsCoin = function (hero, coin) { 328 | this.sfx.coin.play(); 329 | coin.kill(); 330 | this.coinPickupCount++; 331 | }; 332 | 333 | PlayState._onHeroVsEnemy = function (hero, enemy) { 334 | if (hero.body.velocity.y > 0) { // kill enemies when hero is falling 335 | hero.bounce(); 336 | enemy.die(); 337 | this.sfx.stomp.play(); 338 | } 339 | else { // game over -> restart the game 340 | this.sfx.stomp.play(); 341 | this.game.state.restart(); 342 | } 343 | }; 344 | 345 | PlayState._onHeroVsKey = function (hero, key) { 346 | this.sfx.key.play(); 347 | key.kill(); 348 | this.hasKey = true; 349 | }; 350 | 351 | PlayState._onHeroVsDoor = function (hero, door) { 352 | this.sfx.door.play(); 353 | this.game.state.restart(); 354 | // TODO: go to the next level instead 355 | }; 356 | 357 | PlayState._createHud = function () { 358 | const NUMBERS_STR = '0123456789X '; 359 | this.coinFont = this.game.add.retroFont('font:numbers', 20, 26, 360 | NUMBERS_STR); 361 | 362 | this.keyIcon = this.game.make.image(0, 19, 'icon:key'); 363 | this.keyIcon.anchor.set(0, 0.5); 364 | 365 | let coinIcon = this.game.make.image(this.keyIcon.width + 7, 0, 'icon:coin'); 366 | let coinScoreImg = this.game.make.image(coinIcon.x + coinIcon.width, 367 | coinIcon.height / 2, this.coinFont); 368 | coinScoreImg.anchor.set(0, 0.5); 369 | 370 | this.hud = this.game.add.group(); 371 | this.hud.add(coinIcon); 372 | this.hud.add(coinScoreImg); 373 | this.hud.add(this.keyIcon); 374 | this.hud.position.set(10, 10); 375 | }; 376 | 377 | // ============================================================================= 378 | // entry point 379 | // ============================================================================= 380 | 381 | window.onload = function () { 382 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 383 | game.state.add('play', PlayState); 384 | game.state.start('play'); 385 | }; 386 | -------------------------------------------------------------------------------- /src/assets/assets/platformer/walking_spider.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/walking_spider.gif -------------------------------------------------------------------------------- /src/assets/assets/platformer/win_condition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/assets/platformer/win_condition.png -------------------------------------------------------------------------------- /src/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Georgia, serif; 3 | padding: 0; 4 | margin: 0; 5 | line-height: 1.5; 6 | font-size: 1.2em; 7 | } 8 | 9 | main { 10 | max-width: 960px; 11 | margin: 0 auto; 12 | } 13 | 14 | img { 15 | max-width: 100%; 16 | background: #efefef; 17 | } 18 | 19 | a { 20 | color: #00a; 21 | } 22 | 23 | li { 24 | margin: 0.25em 0; 25 | } 26 | 27 | code:not([class*="language-"]) { 28 | background-color: #f5f2f0; 29 | font-family: Courier, monospace; 30 | font-size: 1em; 31 | } 32 | 33 | pre code[class*="language-"] { 34 | font-size: 0.9em; 35 | } 36 | 37 | .main-header { 38 | padding: 1em 0em; 39 | border-bottom: 1px solid #ccc; 40 | } 41 | 42 | @media(min-width:1250px) { 43 | .main-header { 44 | background: url(../images/moz-logo.svg) 1vw center no-repeat; 45 | background-size: 10vw; 46 | } 47 | 48 | } 49 | 50 | @media(max-width:1249px) { 51 | main, .main-header { 52 | padding-left: 1em; 53 | padding-right: 1em; 54 | } 55 | } 56 | 57 | article h1 { 58 | font-size: 1.8em; 59 | } 60 | 61 | .main-header h1 { 62 | max-width: 960px; 63 | margin: 0 auto; 64 | } 65 | 66 | .main-header a { 67 | text-decoration: none; 68 | } 69 | 70 | .main-footer { 71 | margin-top: 4em; 72 | padding: 1em; 73 | border-top: 1px solid #ccc; 74 | } 75 | 76 | .main-footer p { 77 | max-width: 960px; 78 | margin-left: auto; 79 | margin-right: auto; 80 | } 81 | 82 | .paginated-nav { 83 | margin-top: 2em; 84 | font-size: 1.2em; 85 | } 86 | 87 | .paginated-nav ul { 88 | list-style-type: none; 89 | padding-left: 0; 90 | margin-left: 0; 91 | display: flex; 92 | } 93 | 94 | .paginated-nav li { 95 | flex: 1 1 auto; 96 | } 97 | 98 | .paginated-nav .next { 99 | text-align: right; 100 | font-weight: bold; 101 | } 102 | 103 | .toc { 104 | column-count: 2; 105 | } 106 | 107 | .author { 108 | font-size: 0.9em; 109 | font-style: italic; 110 | } 111 | -------------------------------------------------------------------------------- /src/assets/images/moz-favicon.svg: -------------------------------------------------------------------------------- 1 | moz-symbol-rgb-interim -------------------------------------------------------------------------------- /src/assets/images/moz-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layer 1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/platformer/audio/bgm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/audio/bgm.mp3 -------------------------------------------------------------------------------- /src/assets/platformer/audio/bgm.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/audio/bgm.ogg -------------------------------------------------------------------------------- /src/assets/platformer/audio/coin.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/audio/coin.wav -------------------------------------------------------------------------------- /src/assets/platformer/audio/door.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/audio/door.wav -------------------------------------------------------------------------------- /src/assets/platformer/audio/jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/audio/jump.wav -------------------------------------------------------------------------------- /src/assets/platformer/audio/key.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/audio/key.wav -------------------------------------------------------------------------------- /src/assets/platformer/audio/stomp.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/audio/stomp.wav -------------------------------------------------------------------------------- /src/assets/platformer/data/level00.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": [ 3 | {"image": "ground", "x": 0, "y": 546}, 4 | {"image": "grass:4x1", "x": 420, "y": 420} 5 | ], 6 | "decoration": [ 7 | {"frame": 2, "x": 630, "y": 504}, 8 | {"frame": 2, "x": 663, "y": 504}, 9 | {"frame": 2, "x": 697, "y": 504}, 10 | {"frame": 3, "x": 756, "y": 504}, 11 | {"frame": 1, "x": 84, "y": 504}, 12 | {"frame": 0, "x": 252, "y": 504}, 13 | {"frame": 4, "x": 462, "y": 378} 14 | ], 15 | "coins": [ 16 | {"x": 147, "y": 525}, {"x": 189, "y": 525}, 17 | {"x": 399, "y": 399}, {"x": 357, "y": 420}, {"x": 336, "y": 462}, 18 | {"x": 819, "y": 525}, {"x": 861, "y": 525}, {"x": 903, "y": 525} 19 | ], 20 | "hero": {"x": 21, "y": 525}, 21 | "spiders": [ 22 | ], 23 | "door": {"x": 231, "y": 546}, 24 | "key": {"x": 525, "y": 336} 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/platformer/data/level01.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": [ 3 | {"image": "ground", "x": 0, "y": 546}, 4 | {"image": "grass:8x1", "x": 0, "y": 420}, 5 | {"image": "grass:2x1", "x": 420, "y": 336}, 6 | {"image": "grass:1x1", "x": 588, "y": 504}, 7 | {"image": "grass:8x1", "x": 672, "y": 378}, 8 | {"image": "grass:4x1", "x": 126, "y": 252}, 9 | {"image": "grass:6x1", "x": 462, "y": 168}, 10 | {"image": "grass:2x1", "x": 798, "y": 84} 11 | ], 12 | "decoration": [ 13 | {"frame": 0, "x": 84, "y": 504}, {"frame": 1, "x": 420, "y": 504}, 14 | {"frame": 3, "x": 672, "y": 504}, {"frame": 4, "x": 595, "y": 462}, 15 | {"frame": 2, "x": 142, "y": 378}, {"frame": 1, "x": 168, "y": 378}, 16 | {"frame": 0, "x": 714, "y": 336}, 17 | {"frame": 4, "x": 420, "y": 294}, 18 | {"frame": 1, "x": 515, "y": 126}, {"frame": 3, "x": 525, "y": 126} 19 | ], 20 | "coins": [ 21 | {"x": 231, "y": 524}, {"x": 273, "y": 524}, {"x": 315, "y": 524}, {"x": 357, "y": 524}, 22 | {"x": 819, "y": 524}, {"x": 861, "y": 524}, {"x": 903, "y": 524}, {"x": 945, "y": 524}, 23 | {"x": 399, "y": 294}, {"x": 357, "y": 315}, {"x": 336, "y": 357}, 24 | {"x": 777, "y": 357}, {"x": 819, "y": 357}, {"x": 861, "y": 357}, {"x": 903, "y": 357}, {"x": 945, "y": 357}, 25 | {"x": 189, "y": 231}, {"x": 231, "y": 231}, 26 | {"x": 525, "y": 147}, {"x": 567, "y": 147}, {"x": 609, "y": 147}, {"x": 651, "y": 147}, 27 | {"x": 819, "y": 63}, {"x": 861, "y": 63} 28 | ], 29 | "hero": {"x": 21, "y": 525}, 30 | "spiders": [{"x": 121, "y": 399}, {"x": 800, "y": 362}, {"x": 500, "y": 147}], 31 | "door": {"x": 169, "y": 546}, 32 | "key": {"x": 903, "y": 105} 33 | } 34 | -------------------------------------------------------------------------------- /src/assets/platformer/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/background.png -------------------------------------------------------------------------------- /src/assets/platformer/images/coin_animated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/coin_animated.png -------------------------------------------------------------------------------- /src/assets/platformer/images/coin_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/coin_icon.png -------------------------------------------------------------------------------- /src/assets/platformer/images/decor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/decor.png -------------------------------------------------------------------------------- /src/assets/platformer/images/door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/door.png -------------------------------------------------------------------------------- /src/assets/platformer/images/grass_1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/grass_1x1.png -------------------------------------------------------------------------------- /src/assets/platformer/images/grass_2x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/grass_2x1.png -------------------------------------------------------------------------------- /src/assets/platformer/images/grass_4x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/grass_4x1.png -------------------------------------------------------------------------------- /src/assets/platformer/images/grass_6x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/grass_6x1.png -------------------------------------------------------------------------------- /src/assets/platformer/images/grass_8x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/grass_8x1.png -------------------------------------------------------------------------------- /src/assets/platformer/images/ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/ground.png -------------------------------------------------------------------------------- /src/assets/platformer/images/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/hero.png -------------------------------------------------------------------------------- /src/assets/platformer/images/hero_stopped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/hero_stopped.png -------------------------------------------------------------------------------- /src/assets/platformer/images/invisible_wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/invisible_wall.png -------------------------------------------------------------------------------- /src/assets/platformer/images/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/key.png -------------------------------------------------------------------------------- /src/assets/platformer/images/key_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/key_icon.png -------------------------------------------------------------------------------- /src/assets/platformer/images/numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/numbers.png -------------------------------------------------------------------------------- /src/assets/platformer/images/spider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozdevs/html5-games-workshop/9e1ae36d195903be59d852448e374343a0938e71/src/assets/platformer/images/spider.png -------------------------------------------------------------------------------- /src/assets/platformer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTML5 games workshop: One-screen platformer 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /src/assets/vendor/prism/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+bash&plugins=normalize-whitespace */ 2 | /** 3 | * prism.js default theme for JavaScript, CSS and HTML 4 | * Based on dabblet (http://dabblet.com) 5 | * @author Lea Verou 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: black; 11 | background: none; 12 | text-shadow: 0 1px white; 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | white-space: normal; 67 | } 68 | 69 | .token.comment, 70 | .token.prolog, 71 | .token.doctype, 72 | .token.cdata { 73 | color: slategray; 74 | } 75 | 76 | .token.punctuation { 77 | color: #999; 78 | } 79 | 80 | .namespace { 81 | opacity: .7; 82 | } 83 | 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #905; 92 | } 93 | 94 | .token.selector, 95 | .token.attr-name, 96 | .token.string, 97 | .token.char, 98 | .token.builtin, 99 | .token.inserted { 100 | color: #690; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url, 106 | .language-css .token.string, 107 | .style .token.string { 108 | color: #a67f59; 109 | background: hsla(0, 0%, 100%, .5); 110 | } 111 | 112 | .token.atrule, 113 | .token.attr-value, 114 | .token.keyword { 115 | color: #07a; 116 | } 117 | 118 | .token.function { 119 | color: #DD4A68; 120 | } 121 | 122 | .token.regex, 123 | .token.important, 124 | .token.variable { 125 | color: #e90; 126 | } 127 | 128 | .token.important, 129 | .token.bold { 130 | font-weight: bold; 131 | } 132 | .token.italic { 133 | font-style: italic; 134 | } 135 | 136 | .token.entity { 137 | cursor: help; 138 | } 139 | -------------------------------------------------------------------------------- /src/content/bonus/resources_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Resources 3 | --- 4 | 5 | Here is a collection of resources for **HTML5 game development**. There is a generic mega-list with more resources (not just related to HTML5) at [Pixel Prospector](http://www.pixelprospector.com/). 6 | 7 | ## Phaser-related resources 8 | 9 | - [Download the latest version of Phaser](http://phaser.io/download/stable). 10 | - [Phaser's documentation](http://phaser.io/docs/). 11 | - [Phaser's examples](http://phaser.io/examples): official collection of Phaser examples. This is an excellent place if you want to do something but have no clue about which parts of Phaser API's you should use. 12 | - [HTML5 Game Devs forum](http://www.html5gamedevs.com/): a great place to ask questions or showcase your game. 13 | 14 | ## Inspiration 15 | 16 | - [_How to succeed at making one game a month_](https://gamedevelopment.tutsplus.com/articles/1gam-how-to-succeed-at-making-one-game-a-month--gamedev-3695): an article that describes a great approach for indie or solo development. A must-read if you are a beginner at making games or struggle to finish your projects. 17 | - [Itch.io](https://itch.io/): a game publishing portal for indies, with lots of free games as well. It's great to see what others are doing, and perfect for inspiration. 18 | - [_Finishing a game_](http://makegames.tumblr.com/post/1136623767/finishing-a-game): an article about focusing on finishing projects and not getting derailed, by the creator of _Spelunky_. 19 | - [_Surviving Ludum Dare_](https://belenalbeza.com/surviving-ludum-dare/): recommendations to succeed at Ludum Dare or other game jams. 20 | 21 | ## Art assets 22 | 23 | - [Kenney's art assets](http://kenney.nl/assets): multiple art packs, with professional quality, released in the public domain. 24 | - [Open Game Art](http://opengameart.org/): a collection of different art assets –sprites sound effect, music tracks, textures…– released under a license that allows re-using (sometimes under certain constraints). 25 | - [freesound](https://www.freesound.org/): Sound and music, released under a Creative Commons license. 26 | 27 | ## Engines 28 | 29 | Here are some game development libraries you can use to make your games with JavaScript. All of these are open source. 30 | 31 | - [Phaser](http://phaser.io/): 2D game framework. 32 | - [A-Frame](https://aframe.io/): framework for virtual reality experiences and games. 33 | - [PlayCanvas Engine](http://developer.playcanvas.com/en/engine/): 3D game engine. 34 | 35 | Here are some of the game development engines that support exporting to HTML5. 36 | 37 | - [Unity](https://unity3d.com/): 3D engine with support for many platforms and a very large community. 38 | - [Unreal](https://unrealengine.com/): 3D engine used from indie games to blockbuster AAA titles. 39 | - [Game Maker](http://www.yoyogames.com/gamemaker): 2D game engine, beginner-friendly, and very popular among indie developers. 40 | - [PICO-8](http://www.lexaloffle.com/pico-8.php): a fantasy console that allows you to create small games with LUA and export them to the Web. 41 | 42 | For a more exhaustive list, check out the [Engine & Tools](https://developer.mozilla.org/en-US/docs/Games/Tools/Engines_and_tools) page at the MDN. 43 | 44 | ## Other software and tools 45 | 46 | - [Audiotool](https://www.audiotool.com/): online, free DAW. 47 | - [Bxfr](http://www.bfxr.net/): online, retro sound effects generator. 48 | - [Github](https://github.com/): version control with Git. 49 | - [Trello](https://trello.com/): project management tool. 50 | - [Pyxel Edit](http://pyxeledit.com/): very affordable pixel art editor. 51 | -------------------------------------------------------------------------------- /src/content/coach-guide/index_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guide for coaches and instructors 3 | layout: guide_index.pug 4 | collection_base: setup 5 | author_twitter: ladybenko 6 | author_name: Belén "Benko" Albeza 7 | --- 8 | 9 | This guide is aimed for coaches/instructors of this workshop. It contains information about the workshop and a few recommendations to run it. 10 | 11 | ### Methodology 12 | 13 | The main part of the workshop is the [_Make a platformer game_](/en/guides/platformer/start-here/) guide. Attendees will create a platformer game with Phaser and JavaScript, step by step. 14 | 15 | Full source code is provided, and attendees can complete the whole workshop just by copying and pasting code. 16 | 17 | ### Target audience 18 | 19 | This workshop is aimed at web developers. **Basic knowledge of JavaScript** is required. Although the workshop can be completed just by copying and pasting code, people who can read JavaScript and understand what is going on –or being able to ask the right question– will benefit the most. 20 | 21 | ### Duration 22 | 23 | The workshop is meant to last **a full day**, with lunch and coffee break. A half-day workshop would be possible, but warn people to not to feel bad if they don't complete it. How far they will get depends on the attendees' proficiency with JavaScript or development in general. 24 | 25 | In the half-day workshop we ran in Barcelona, the people who were fluent in JavaScript finished the whole workshop and even added some extra features on their own. Most people arrived at least at the part where the main character could jump and pick up coins. 26 | 27 | ### Preparations 28 | 29 | Make sure that all participants can **run a local server** in their machines _before_ the event starts (point them to the [_Setup your machine for HTML5 game development_ en/guides/setup/setup-your-machine/) guide). For people with Mac or Linux machines this is trivial and can be explained at the event (i.e. Python –which comes with a local server– comes pre-installed in these OS), but people running Windows will probably need to download additional software. 30 | 31 | It's convenient that an **introduction to game development** is delivered. You can use [this presentation deck](https://belen-albeza.github.io/intro-gamedev/#1) by Belén for this matter. The goal is to provide some basic game development concepts (such as what is a sprite, a bitmap font, game states, etc.) as well as motivation and reasons to **make games for the Web**. 32 | 33 | ### Venue 34 | 35 | Since it's a long workshop and game development can hit the CPU hard, ensure that there are **power outlets** for all participants. 36 | 37 | Since there will always be people who have not installed a local server at their machines, ensure that there is Internet connection available, so they can download Apache or install an npm module. 38 | 39 | Don't let all women, PoC or other minorities to seat in the back rows –as it is common in most events–. Invite them to take the front seats if they are comfortable on doing so. 40 | 41 | ### During the workshop 42 | 43 | If the attendees don't have a consistent level/skills on JavaScript, don't go step by step: let each person to do the workshop at their own pace. 44 | 45 | This was the case at the workshop we ran at Barcelona, and we decided to do at the beginning an overview of all the steps, and then let the attendees to do each step at their will. 46 | 47 | Remind people that at the end of each step there is a **checklist** they need to run to ensure they have done stuff properly at that step. Remind them also that they can download the source code of that step, in case they got stuck. 48 | 49 | ### What if people finish early 50 | 51 | Encourage them to try to implement on their own the improvements that the final version has (a death animation for the main character, fade in/out when changing levels, etc.) 52 | 53 | Other ideas to suggest: 54 | 55 | - Help other attendees to finish the workshop! 56 | - Change the graphics on the game for their own. 57 | - Implement victory and game over screens (this can be done via game states). 58 | - Add a new enemy. 59 | - Make new levels by creating more JSON files. 60 | 61 | ### What if people can't finish the whole thing 62 | 63 | Encourage them to finish the workshop at home! There is enough text available that anybody should be able to complete on their own. 64 | -------------------------------------------------------------------------------- /src/content/index_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | permalink: false 4 | collection_base: platformer 5 | --- 6 | 7 | Learn to create **HTML5 games** with JavaScript and Phaser! 8 | 9 | ## For attendees 10 | 11 | Check out our guides: 12 | 13 | 1. [Set up your machine for HTML5 game development](/en/guides/setup/setup-your-machine/) 14 | 1. [Make a platformer game](/en/guides/platformer/start-here/) 15 | 16 | Bonus materials: 17 | 18 | - [Resources](/en/bonus/resources/) 19 | 20 | ## For coaches 21 | 22 | - [Guide to run this workshop](/en/guides/coach/) 23 | -------------------------------------------------------------------------------- /src/content/platformer/index_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Start here 3 | layout: guide_index.pug 4 | collection_base: platformer 5 | author_twitter: ladybenko 6 | author_name: Belén "Benko" Albeza" 7 | --- 8 | 9 | We are going to create a classic **one-screen platformer** game! It will feature a main character, who can run and jump to platforms. There will also be enemies that this character will have to avoid –or kill! The goal of the game is to fetch the key and open the door that leads to the next level. 10 | 11 | [![Screenshot](/assets/platformer/platformer_screenshot.png)](/platformer/) 12 | 13 | You can [play the game here](/platformer/). 14 | 15 | We will be implementing the following game development concepts: 16 | 17 | - **Loading** assets. 18 | - Handling **game states**. 19 | - Rendering **images** on the screen. 20 | - Implementing **sprites**. 21 | - Reading the player's input via **keyboard**. 22 | - Using a **physics engine** to move sprites and handle **collisions**. 23 | - Writing **text** with a bitmap font. 24 | - Playing **sound** effects and background music. 25 | 26 | We will focus on game development concepts and the Phaser API in a way that is accessible to as many people as possible. This means that some good practises, like modules, that require of additional tools or a better understanding of JavaScript will _not_ be seen here. 27 | 28 | That said, if _you_ are familiar with this tools/concepts and want to use them in this workshop, by all means, do it. 29 | 30 | ## Important! 31 | 32 | This guide uses [Phaser version **2.6.2 "Kore Springs"**](http://phaser.io/docs/2.6.2/index). This version is what it's included in the project template provided in the next step. 33 | 34 | It is possible that later on some changes in Phaser API in future versions might make this guide not 100% compatible with the latest Phaser version. We will try to keep this updated, though. 35 | 36 | ## About the art assets 37 | 38 | The graphic and audio assets of the game in this guide have been released in the public domain under a [CC0 license](https://creativecommons.org/share-your-work/public-domain/cc0/). These assets are: 39 | 40 | - The images have been created by [Kenney](http://kenney.nl/), and are part of his [_Platformer Art: Pixel Redux_ set](http://opengameart.org/content/platformer-art-pixel-redux) (they have been scaled up, and some of them have minor edits). 41 | - The background music track, [_Happy Adventure_](http://opengameart.org/content/happy-adventure-loop), has been created by [Rick Hoppmann](http://www.tinyworlds.org/). 42 | - The sound effects have been randomly generated with the [Bfxr](http://www.bfxr.net/) synth. 43 | -------------------------------------------------------------------------------- /src/content/platformer/step01_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Initialise Phaser 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step01.js 5 | --- 6 | 7 | ## Tasks 8 | 9 | ### Set up the project skeleton 10 | 11 | 1. Create a directory/folder for the game in your computer. 12 | 1. Download the [initial project skeleton](/assets/platformer/start.zip) and unzip its contents in the directory you just created. Make sure that the resulting structure looks like this: 13 | 14 | ```bash 15 | game 16 | ├── audio 17 | ├── data 18 | ├── images 19 | ├── index.html 20 | └── js 21 | ``` 22 | 1. Launch a **local web server** (we have seen how to do that in the [install guide](/en/guides/setup/setup-your-machine/)) and check that you can get to the `index.html` file in the browser. For instance, if you have launched your web server in the port `3000`, you should be able to see the contents of `index.html` by accessing `http://0.0.0.0:3000`. 23 | 24 | ### Initialise Phaser and the canvas 25 | 26 | 1. HTML5 games need a `` element to draw graphics. Phaser can create one automatically when we initialise the game. We need to supply the ID of the element that will wrap the canvas –in our case, it will be a `
` that we have in our `index.file`. We will also be providing the canvas' dimensions (960✕600). 27 | 28 | To do that, open `js/main.js` in your text editor and edit the `window.onload` function to initialise Phaser: 29 | 30 | ```js 31 | window.onload = function () { 32 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 33 | }; 34 | ``` 35 | 36 | You might be wondering what is this `Phaser.AUTO` parameter we are passing. It's to specify whether we want a 2D canvas or a WebGL canvas. By setting it to `AUTO`, it will try to use a WebGL canvas –for most games it's most performant– and, when it isn't available, will fallback to use a regular 2D canvas. 37 | 38 | 1. Refresh your browser so you can see the changes. You should be able to see a black canvas with the dimensions we specified in the initialisation. 39 | 40 | ![Empy canvas on the screen](/assets/platformer/step00_check.png) 41 | 42 | ## Checklist 43 | 44 | Before you go ahead, make sure: 45 | 46 | - You can access the contents of `index.html` in your browser (by launching a local server). 47 | - You see a black canvas element on the screen. 48 | 49 | All done? Then let's continue! The glory of game development awaits us! 50 | -------------------------------------------------------------------------------- /src/content/platformer/step02_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The game loop 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step02.js 5 | --- 6 | 7 | The **game loop** is the core of every game. It's what allows us to update the game logic and render the graphics every frame –hopefully 60 times per second! 8 | 9 | ![The game loop](/assets/platformer/game_loop.png) 10 | 11 | In Phaser, the game loop is handled automatically via **game states**. A game state represents one "screen" in our game: the loading screen, the main menu, a level, etc. Each state is divided into phases or steps, the most important are: 12 | 13 | ![Game state](/assets/platformer/game_state.png) 14 | 15 | As you can see, `update` and `render` form the game loop. These phases **are called automatically** each frame, so we don't need to worry to implement a game loop and keep track of the timing. 16 | 17 | A game state in Phaser is just an `Object` with some methods that we can override. We will be overriding some of these in order to load an image and render it on the screen. 18 | 19 | ## Tasks 20 | 21 | ### Create a game state 22 | 23 | 1. As before, edit `main.js` so it looks like this: 24 | 25 | ```js 26 | PlayState = {}; 27 | 28 | window.onload = function () { 29 | let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game'); 30 | game.state.add('play', PlayState); 31 | game.state.start('play'); 32 | }; 33 | ``` 34 | 35 | ### Load and render an image 36 | 37 | 1. To **load an image**, we will make use of the `preload` phase of our game state. In this phase we will load all the assets that we require (images, sound effects, etc.). 38 | 39 | To use a phase in a game state we need to add a method with a matching name. In our case, we will be creating `PlayState.preload`: 40 | 41 | ```js 42 | // write this under 43 | // PlayState = {}; 44 | 45 | // load game assets here 46 | PlayState.preload = function () { 47 | this.game.load.image('background', 'images/background.png'); 48 | }; 49 | ``` 50 | 51 | Things to note: 52 | 53 | 1. We have a reference to the `Phaser.Game` instance inside the game state via `this.game`. 54 | 2. When we load an asset, we assign it an (arbitrary) key. We will use this key later to reference that asset. 55 | 56 | 1. To **render an image** we need to create an instance of `Phaser.Image`, which is one of the many _game entities_ in Phaser. We can do this using the `game.add` factory, which will automatically add the image to the **game world** so it gets drawn on the screen automatically every frame. 57 | 58 | Add the following method to our `PlayState`: 59 | 60 | ```js 61 | // create game entities and set up world here 62 | PlayState.create = function () { 63 | this.game.add.image(0, 0, 'background'); 64 | }; 65 | ``` 66 | 67 | We are providing the X and Y coordinates –`(0, 0)` is the top left corner– and the key to the asset we just loaded. 68 | 69 | If you check out the game, you should see a pretty background drawn in the screen: 70 | 71 | ![A background, rendered](/assets/platformer/step01_check.png) 72 | 73 | ## Checklist 74 | 75 | - The background image is rendered in the screen. 76 | 77 | Rendering an image in the game loop is the first step in crafting games. Get ready for the next step! 78 | -------------------------------------------------------------------------------- /src/content/platformer/step03_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Creating platforms 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step03.js 5 | --- 6 | 7 | A platformer game needs… platforms, right? There are multiple techniques to handling platforms and the physics related to them. In this workshop, we will consider the platforms as **sprites**, like other characters in the game. 8 | 9 | There are more efficient and flexible ways to do this, but for a one-screen platformer, this one is performant enough and, more importantly, the _most simple way_. 10 | 11 | This is how some of the platforms look like (a 4✕1 and a 1✕1): 12 | 13 | ![4x1 grass platform](/assets/platformer/grass_4x1.png) ![1x1 grass platform](/assets/platformer/grass_1x1.png) 14 | 15 | As with images, there is a factory method to create **sprites** (in this case, instances of [`Phaser.Sprite`](http://phaser.io/docs/2.6.2/Phaser.Sprite.html)) and add them automatically to the game world. 16 | 17 | But _where_ to place the platforms? We could hardcode the whole thing, but in the long run it's better to have the level data in a separate file that we can load. We have some **level data as JSON** files in the `data/` folder. 18 | 19 | Ideally, these files would be generated with a level editor tool, but you can add more levels to the game after the workshop by creating your own JSON files! 20 | 21 | If you open one of these JSON files, you can see how platform data is specified: 22 | 23 | ```js 24 | { 25 | "platforms": [ 26 | {"image": "ground", "x": 0, "y": 546}, 27 | {"image": "grass:4x1", "x": 420, "y": 420} 28 | ], 29 | // .... 30 | } 31 | ``` 32 | 33 | ## Tasks 34 | 35 | ### Load the level data 36 | 37 | 1. Phaser considers JSON files as another type of asset with can load within the game. Let's load the level data in the `preload` method: 38 | 39 | ```js 40 | PlayState.preload = function () { 41 | this.game.load.json('level:1', 'data/level01.json'); 42 | // ... 43 | }; 44 | ``` 45 | 46 | 1. Now modify `create`: 47 | 48 | ```js 49 | PlayState.create = function () { 50 | //... 51 | this._loadLevel(this.game.cache.getJSON('level:1')); 52 | }; 53 | 54 | PlayState._loadLevel = function (data) { 55 | }; 56 | ``` 57 | 58 | You can check this works if you add a `console.log(data)` in `PlayState._loadLevel` –and don't forget to remove it afterwards. 59 | 60 | ### Spawn platform sprites 61 | 62 | 63 | 1. Before creating the sprites, we need to load the images that the platforms will use. As usual, we do this in the `preload` method: 64 | 65 | ```js 66 | PlayState.preload = function () { 67 | // ... 68 | this.game.load.image('ground', 'images/ground.png'); 69 | this.game.load.image('grass:8x1', 'images/grass_8x1.png'); 70 | this.game.load.image('grass:6x1', 'images/grass_6x1.png'); 71 | this.game.load.image('grass:4x1', 'images/grass_4x1.png'); 72 | this.game.load.image('grass:2x1', 'images/grass_2x1.png'); 73 | this.game.load.image('grass:1x1', 'images/grass_1x1.png'); 74 | }; 75 | ``` 76 | 77 | 1. Now let's spawn the platforms. The level JSON file contains a `platform` property with an `Array` of the info necessary to spawn the platforms: their position, and the image. So we just need to iterate over this `Array` and add new sprites to the game world: 78 | 79 | ```js 80 | PlayState._loadLevel = function (data) { 81 | // spawn all platforms 82 | data.platforms.forEach(this._spawnPlatform, this); 83 | }; 84 | 85 | PlayState._spawnPlatform = function (platform) { 86 | this.game.add.sprite(platform.x, platform.y, platform.image); 87 | }; 88 | ``` 89 | 90 | If you are thinking why we are splitting this into different methods, it's because `_loadLevel` will become very crowded in the following steps. 91 | 92 | Refresh your browser and you should see our platform sprites! 93 | 94 | ![Platform sprites](/assets/platformer/step02_check.png) 95 | 96 | ## Checklist 97 | 98 | - You can see platforms rendered over the background 99 | - Make sure you are using `game.add.sprite` to create the platforms and _not_ `game.add.image`! 100 | -------------------------------------------------------------------------------- /src/content/platformer/step04_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The main character sprite 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step04.js 5 | --- 6 | 7 | The hero or main character will be another **sprite** in our game. However, this sprite is more complex than the platforms, since it needs more business logics: moving around, jumping, etc. 8 | 9 | Wouldn't be nice to have a class for these sprites with `jump`, `move`, etc. methods? We can achieve this by **extending** (also known as "inheriting from") `Phaser.Sprite`. 10 | 11 | In JavaScript, we can extend classes following this pattern. Imagine that we have a base class `Vehicle` and we want to extend it in `Car`: 12 | 13 | ```js 14 | function Car() { 15 | // call parent constructor 16 | Vehicle.call(this); 17 | } 18 | 19 | // clone Vehicle's prototype into Car 20 | Car.prototype = Object.create(Vehicle.prototype); 21 | // restore the constructor at Car 22 | Car.prototype.constructor = Car; 23 | ``` 24 | 25 | We will use this pattern for extending `Phaser.Sprite`. 26 | 27 | Yes, sometimes inheritance is not the best choice and usually in JavaScript composition is more favoured. However, Phaser's architecture expects us to extend `Phaser.Sprite`, so this is what we are doing. 28 | 29 | ## Tasks 30 | 31 | ### Load the hero image 32 | 33 | 1. In `preload`: 34 | 35 | ```js 36 | PlayState.preload = function () { 37 | // ... 38 | this.game.load.image('hero', 'images/hero_stopped.png'); 39 | }; 40 | ``` 41 | 42 | ### Inherit from `Phaser.Sprite` 43 | 44 | 1. Add the following at the top of `main.js`. This follows the JavaScript inheritance pattern we have already seen. Note how we can have our own custom parameters –in this case, we are not requiring to provide the image key in the `Hero` constructor. 45 | 46 | ```js 47 | function Hero(game, x, y) { 48 | // call Phaser.Sprite constructor 49 | Phaser.Sprite.call(this, game, x, y, 'hero'); 50 | } 51 | 52 | // inherit from Phaser.Sprite 53 | Hero.prototype = Object.create(Phaser.Sprite.prototype); 54 | Hero.prototype.constructor = Hero; 55 | ``` 56 | 57 | ### Spawn the hero when loading the level. 58 | 59 | 1. As with platforms, the hero position is stored in the JSON level file. We will create a new method, `_spawnCharacters`, to spawn the hero and, later on, the enemies. 60 | 61 | ```js 62 | PlayState._loadLevel = function (data) { 63 | //... 64 | // spawn hero and enemies 65 | this._spawnCharacters({hero: data.hero}); 66 | }; 67 | ``` 68 | 69 | ```js 70 | PlayState._spawnCharacters = function (data) { 71 | // spawn hero 72 | this.hero = new Hero(this.game, data.hero.x, data.hero.y); 73 | this.game.add.existing(this.hero); 74 | }; 75 | ``` 76 | 77 | 2. Check how it looks like. You should see the hero… not in a very good position: 78 | 79 | ![Bad-positioned hero](/assets/platformer/hero_bad_position.png) 80 | 81 | Why is this? Is the level data wrong? What happens is that, _usually_, we'd like sprites to be **handled by their center**. This helps in operations like rotations, flipping, etc. and it's also more intuitive. Let's fix this. 82 | 83 | 3. In Phaser, the point where we handle sprites and images is called **`anchor`**. It's a vector, and it accepts values in the `0` (left) to `1` (right) range. So the central point would be `(0.5, 0.5)`. Modify the `Hero` constructor to set up the anchor: 84 | 85 | ```js 86 | function Hero(game, x, y) { 87 | // ... 88 | this.anchor.set(0.5, 0.5); 89 | } 90 | ``` 91 | 92 | Refresh the browser again and you should see the hero positioned just over the ground: 93 | 94 | ![Hero positioned correctly in the scenario](/assets/platformer/step03_check.png) 95 | 96 | ## Checklist 97 | 98 | - There is a hero sprite over the ground, on the bottom left part of the level. 99 | -------------------------------------------------------------------------------- /src/content/platformer/step05_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Keyboard controls 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step05.js 5 | --- 6 | 7 | The player will be able to control the main character with the keyboard. For now, we will make the character move left and right when the player presses the arrow keys. 8 | 9 | Phaser let us detect a key status (and listen to events like the key being released, etc.) via instances of `Phaser.Key`, each instance being associated to a specific key. Since we don't need to listen to the _whole_ keyboard, we can settle for one instance for the left arrow key, and another one for the right arrow key. 10 | 11 | ## Tasks 12 | 13 | ### Create instances of `Phaser.Key` 14 | 15 | 1. We can easily create `Phaser.Key` instances with the [`game.input.keyboard.addKeys`](http://phaser.io/docs/2.6.2/Phaser.Keyboard.html#addKeys) method, which allow us to create multiple keys at once. We will create them in the `init` phase, since we don't need any of the assets loaded in `preload`. 16 | 17 | ```js 18 | PlayState.init = function () { 19 | this.keys = this.game.input.keyboard.addKeys({ 20 | left: Phaser.KeyCode.LEFT, 21 | right: Phaser.KeyCode.RIGHT 22 | }); 23 | }; 24 | ``` 25 | 26 | You can perfectly create the keys in the `create` phase, though. But sometimes reserving `create` to spawn game entities that _need_ the assets in `preload` can help to make the code more readable. 27 | 28 | ### Add a `move` method to `Hero` 29 | 30 | 1. This is when having a custom class comes handy! Let's add a `move` method which will receive the direction as a parameter: `-1` will mean left, and `1` will mean right: 31 | 32 | ```js 33 | // add this method –and the ongoing Hero methods– AFTER these lines, or you 34 | // will override them when cloning the Phaser.Sprite prototype 35 | // 36 | // Hero.prototype = Object.create(Phaser.Sprite.prototype); 37 | // Hero.prototype.constructor = Hero; 38 | 39 | Hero.prototype.move = function (direction) { 40 | this.x += direction * 2.5; // 2.5 pixels each frame 41 | }; 42 | ``` 43 | 44 | ### Call `Hero.move` when keys are being pressed 45 | 46 | 1. Remember how `update` and `render` were special phases of a state that were called automatically? Well, we will need to use `update` for this one: we want to check the status of the left and right arrow keys and, if they are pressed, move the character. 47 | 48 | ```js 49 | PlayState.update = function () { 50 | this._handleInput(); 51 | }; 52 | ``` 53 | 54 | ```js 55 | PlayState._handleInput = function () { 56 | if (this.keys.left.isDown) { // move hero left 57 | this.hero.move(-1); 58 | } 59 | else if (this.keys.right.isDown) { // move hero right 60 | this.hero.move(1); 61 | } 62 | }; 63 | ``` 64 | 65 | 1. Load the game in the browser and make sure you can move the character left and right. Woohoo! 66 | 67 | ### Fix a tiny glitch 68 | 69 | 1. If your sight is sharp you may have noticed the following glitch when moving the character: 70 | 71 | ![Blurry hero sprite](/assets/platformer/blurry_hero.png) 72 | 73 | Do you see it? The hero sprite sometimes appear blurry, specially when compared to the background and platforms. 74 | 75 | This is due to an anti-aliasing technique performed when drawing an image in not round coordinates (for instance, `100.27` instead of `100`). For most games it is OK because it allows for smoother movements, but since this game uses pixel art, it doesn't look nice when it's blurred, even slightly. 76 | 77 | Fortunately for us, there is a way in Phaser to force the rendering system to round the position values when drawing images. 78 | 79 | We can do this in the `init` method, since it gets executed before any other phase: 80 | 81 | ```js 82 | PlayState.init = function () { 83 | this.game.renderer.renderSession.roundPixels = true; 84 | // ... 85 | }; 86 | ``` 87 | 88 | ## Checklist 89 | 90 | - The character moves left and right with the arrow keys. 91 | - The character stays sharp after having moved. You can check this more easily if you zoom in your browser (`Ctrl` `+` for Win/Linux, or `⌘` `+` for Mac OS). 92 | -------------------------------------------------------------------------------- /src/content/platformer/step06_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Moving sprites with physics 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step06.js 5 | --- 6 | 7 | It's always a good idea to tie movement to _time_. Previously we just set the character to move a fixed amount _per frame_, but we are ignoring how many frames per second our game is executing! 8 | 9 | We could handle movements manually by querying for the **delta time** (the time that has elapsed between this frame an the previous one), but Phaser offer us a more convenient way: the use of a **Physics engine**. 10 | 11 | Physics engines are usually expensive in terms of computation, but Phaser has implemented a very fast and small engine named Arcade Physics. It is very limited in features, but it's enough to handle a platformer game like ours –and we will not get a performance hit! 12 | 13 | We will use the physics engine to move sprite, but also –later on– to handle gravity, collision tests, etc. 14 | 15 | The important thing to take into account is that each sprite will have a physical **body**, and if this body is moved, rotated, etc. by the physics engine, Phaser will automatically update their rendering properties (like `x` or `y`), so we don't need to keep track of it. 16 | 17 | ## Tasks 18 | 19 | ### Make the main character use the physics engine for movement 20 | 21 | 1. First we need to create a body for the character. This gets done when we "enable" physics for this sprite. Modify the `Hero` constructor: 22 | 23 | ```js 24 | function Hero(game, x, y) { 25 | // ... 26 | this.game.physics.enable(this); 27 | } 28 | ``` 29 | 30 | 1. Now we just need to make the `move` method affect the body of the sprite instead of directly modifying its position. What we need is to modify the sprite's velocity so it can move left or right. Edit the `Hero.move` method so it looks like this: 31 | 32 | ```js 33 | Hero.prototype.move = function (direction) { 34 | const SPEED = 200; 35 | this.body.velocity.x = direction * SPEED; 36 | }; 37 | ``` 38 | 39 | 1. Try this out in the browser! Can you move left and right? Yes? Well done! But now we have a different problem… we need to be able to _stop_ the character! 40 | 41 | ### Stop the main character 42 | 43 | 1. We didn't need to do this before because we were modifying the _position_, but now we are modifying the _velocity_ –and obviously objects with a non-zero velocity, move. We can stop the character by setting its speed to zero, and we can do that just by passing `0` as the direction when no key is being pressed: 44 | 45 | ```js 46 | PlayState._handleInput = function () { 47 | if (this.keys.left.isDown) { // move hero left 48 | // ... 49 | } 50 | else if (this.keys.right.isDown) { // move hero right 51 | // ... 52 | } 53 | else { // stop 54 | this.hero.move(0); 55 | } 56 | }; 57 | ``` 58 | 59 | ### Prevent the main character to get out of the screen 60 | 61 | 1. This is a taste of what a physics engine can do for us with very little code from our part. Let's prevent the main character to move outside the bounds of the screen. In Phaser this can be done by setting a flag in the body. Edit the `Hero` constructor: 62 | 63 | ```js 64 | function Hero(game, x, y) { 65 | // ... 66 | this.body.collideWorldBounds = true; 67 | } 68 | ``` 69 | 70 | ## Checklist 71 | 72 | - You can still move the main character left and right with the arrow keys. 73 | - The character stops if no key is being pressed. 74 | - The character cannot move out of the screen. 75 | -------------------------------------------------------------------------------- /src/content/platformer/step07_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gravity 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step07.js 5 | --- 6 | 7 | Using a physics engine makes jumping and handling gravity easy. Now we will handle gravity in the world, making the character step _on_ platforms. And as a side effect, we will make the character not to go trough walls too! 8 | 9 | We can set a global gravity that affects all the entities in the world. In a platformer game, We want the _characters_ (like the hero and some enemies) to be affected by it. Other sprites (like pickable coins, or _platforms_ themselves) should be immobile and not be affected by gravity). 10 | 11 | One thing that we will start doing from now on is to group multiple sprites of the same kind into a **sprite list**: in Phaser they are instances of `Phaser.Group`. Once there, we can –among other things– perform collision tests between groups or between a single sprite and a whole group. 12 | 13 | ## Tasks 14 | 15 | ### Enable gravity in the world 16 | 17 | 1. Edit `PlayState._loadLevel` to enable the gravity: 18 | 19 | ```js 20 | PlayState._loadLevel = function (data) { 21 | // ... 22 | 23 | // enable gravity 24 | const GRAVITY = 1200; 25 | this.game.physics.arcade.gravity.y = GRAVITY; 26 | }; 27 | ``` 28 | 29 | We are doing this here and not in `PlayState.init` to have more flexibility… in this way, in the future we could set the gravity value in the JSON file and allow each level to have their own gravity… Some platformers have levels in the Moon! 30 | 31 | 1. Check the result in the browser… you will see that the main character falls down. The other sprites (the platforms) aren't affected because they don't have a physic body –yet. 32 | 33 | ![Main character falling down](/assets/platformer/hero_fall_bottom.png) 34 | 35 | ### Make the character collide against the platforms 36 | 37 | 1. We don't want the main character to go through platforms –it's not a ghost! First we need to store the platforms into a group. Let's create it before spawning any sprite: 38 | 39 | ```js 40 | PlayState._loadLevel = function (data) { 41 | // create all the groups/layers that we need 42 | this.platforms = this.game.add.group(); 43 | 44 | // ... 45 | }; 46 | ``` 47 | 48 | 1. Now change `_spawnPlatform` so the sprite gets added to the group and we enable physics on it, to check for collisions: 49 | 50 | ```js 51 | PlayState._spawnPlatform = function (platform) { 52 | let sprite = this.platforms.create( 53 | platform.x, platform.y, platform.image); 54 | 55 | this.game.physics.enable(sprite); 56 | }; 57 | ``` 58 | 59 | `Phaser.Group.create` is a factory method for sprites. The new sprite will be added as a child of the group. 60 | 61 | 1. Finally, perform collision checks between the main character and the platforms. Using `collide` will make the physics engine to avoid bodies going through other bodies: 62 | 63 | ```js 64 | PlayState.update = function () { 65 | this._handleCollisions(); 66 | this._handleInput(); 67 | }; 68 | 69 | PlayState._handleCollisions = function () { 70 | this.game.physics.arcade.collide(this.hero, this.platforms); 71 | }; 72 | ``` 73 | 74 | 1. If you try it out, you will see how the platforms fall! And there is one remaining platform that stays on the top of the character –because we prevented the character to move outside of the screen, remember? 75 | 76 | ![Platforms falling](/assets/platformer/platforms_falling.gif) 77 | 78 | ### Fix collisions 79 | 80 | 1. Let's disable gravity for platforms. There is a flag for that in the body: 81 | 82 | ```js 83 | PlayState._spawnPlatform = function (platform) { 84 | // ... 85 | sprite.body.allowGravity = false; 86 | }; 87 | ``` 88 | 89 | 1. Refresh the game in the browser and you will be able to see how the platforms stay in their place… except the ground. This is happening because the main character is falling and _pushing_ against the ground –like a pool ball against other balls. 90 | 91 | ![Ground falling](/assets/platformer/ground_falling.gif) 92 | 93 | 1. In order to fix this, we need to tell the physics engine that the platforms _can't be moved_ when colliding. We do this by setting another flag: 94 | 95 | ```js 96 | PlayState._spawnPlatform = function (platform) { 97 | // ... 98 | sprite.body.immovable = true; 99 | }; 100 | ``` 101 | 102 | Everything should be working as expected now! As a bonus, see how the character can't go through the small wall/platform on the ground: 103 | 104 | ![Character vs Wall](/assets/platformer/step06_check.png) 105 | 106 | ## Checklist 107 | 108 | - Platforms stay at their place. 109 | - The main character does not fall _through_ the ground. 110 | - The main character can't go through the small wall on the ground. 111 | 112 | Now on to doing some jumps! 113 | -------------------------------------------------------------------------------- /src/content/platformer/step08_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Jumps 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step08.js 5 | --- 6 | 7 | Once we have gravity in place, making the main character to jump is almost trivial! If you remember physics class in school, a parabolic movement needs downward gravity applied to a body (we already did that in the previous step) and then some speed applied at the initial moment upwards so the body goes up and down in a **parabola**. 8 | 9 | We will make the main character to jump when the player presses the up arrow key. We will also play a sound effect when this happens, since **audio is crucial** –even more than graphics– to provide feedback to the user! 10 | 11 | ## Tasks 12 | 13 | ### Detect when the up key has been pressed 14 | 15 | 1. Create an instance of `Phaser.Key` tied to the up arrow key. We will do that by modifying the `addKeys` call we already had in place in `init`: 16 | 17 | ```js 18 | PlayState.init = function () { 19 | // ... 20 | this.keys = this.game.input.keyboard.addKeys({ 21 | left: Phaser.KeyCode.LEFT, 22 | right: Phaser.KeyCode.RIGHT, 23 | up: Phaser.KeyCode.UP // add this line 24 | }); 25 | }; 26 | ``` 27 | 28 | 1. Instead checking for whether the key is pressed or not, we will listen for the "on key down" event and jump when it happens. In Phaser, events are called **signals** (they are instances of [`Phaser.Signal`](http://phaser.io/docs/2.6.2/Phaser.Signal.html)), and it's very easy to subscribe and unsubscribe from them. 29 | 30 | ```js 31 | this.keys.up.onDown.add(function () { 32 | this.hero.jump(); 33 | }, this); 34 | ``` 35 | 36 | Like many other functions in JavaScript, the extra argument after the callback is what will become the `this` context when the callback is executed. 37 | 38 | ### Implement the jump method 39 | 40 | 1. Let's implement the `jump` method for `Hero`: 41 | 42 | ```js 43 | Hero.prototype.jump = function () { 44 | const JUMP_SPEED = 600; 45 | this.body.velocity.y = -JUMP_SPEED; 46 | }; 47 | ``` 48 | 49 | 1. Try it in the browser and check that the character can jump. You will find a bug, though: the character can jump mid-air! Although double jumps are not rare in platformer games, _infinite_ jumps sure are. We will force the character to not jump mid-air. 50 | 51 | 1. We can check if a body is touching another body. Since platforms have physic bodies, we can know whether the main character is touching another body at the bottom or not. Modify the jump method so it looks like this: 52 | 53 | ```js 54 | Hero.prototype.jump = function () { 55 | const JUMP_SPEED = 600; 56 | let canJump = this.body.touching.down; 57 | 58 | if (canJump) { 59 | this.body.velocity.y = -JUMP_SPEED; 60 | } 61 | 62 | return canJump; 63 | }; 64 | ``` 65 | 66 | Note that we are also returning whether the character managed to jump or not… we will use this soon! 67 | 68 | ### Play a sound effect when jumping 69 | 70 | 1. Sounds are also a game entity, but they obviously don't get rendered on the screen. But the process to handle them is similar to images. Let's start by loading the audio asset in `preload`: 71 | 72 | ```js 73 | PlayState.preload = function () { 74 | // ... 75 | this.game.load.audio('sfx:jump', 'audio/jump.wav'); 76 | }; 77 | ``` 78 | 79 | 1. Now let's create the audio entity, which will be an instance of [`Phaser.Sound`](http://phaser.io/docs/2.6.2/Phaser.Sound.html). We can create these and add them to the game world with the `game.add` factory, as usual: 80 | 81 | ```js 82 | PlayState.create = function () { 83 | // create sound entities 84 | this.sfx = { 85 | jump: this.game.add.audio('sfx:jump') 86 | }; 87 | // ... 88 | }; 89 | ``` 90 | 91 | 1. Last, we need to play the sound effect when a jump has been made. Remember how we had the `Hero.jump` method to return `true` or `false` depending on whether the jump was possible? We will make use of this now! Modify the listener for the arrow key so it looks like this: 92 | 93 | ```js 94 | PlayState.init = function () { 95 | // ... 96 | this.keys.up.onDown.add(function () { 97 | let didJump = this.hero.jump(); 98 | if (didJump) { 99 | this.sfx.jump.play(); 100 | } 101 | }, this); 102 | }; 103 | ``` 104 | 105 | Try it out in the browser. With a bit of skill, you should be able to jump to reach all the platforms in the level. 106 | 107 | ![Main character jumping](/assets/platformer/hero_jump.gif) 108 | 109 | 110 | ## Checklist 111 | 112 | - The character can jump! 113 | - The character _can not_ jump mid-air. 114 | - A sound effect is played when jumping. 115 | -------------------------------------------------------------------------------- /src/content/platformer/step09_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pickable coins 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step09.js 5 | --- 6 | 7 | We have the core game mechanic –jumping– in place, so it's time to make the game more attractive and fun. We will add some coins for the main character to **pick up**. These coins will also be **animated**, so we will learn how to animate sprites. 8 | 9 | In Phaser, animations are **keyframe-based**. This means that the sprite will change the image it's displaying periodically, and thus we will see it animated. If you have worked with CSS before, does this sound familiar? 10 | 11 | ![Coin spritesheet](/assets/platformer/coin_spritesheet.png) 12 | 13 | This is our coin's **spritesheet**, and Phaser makes really easy to work with them and use them for animations. 14 | 15 | Yup, CSS borrowed the name for the image technique from game development! 16 | 17 | To collect the coins we will **detect when the main character has touched** any of them. The Arcade physics engine will assist us to do so, but we will another method, `overlap`, instead of `collide`. Why? `collide` actually _resolves_ collisions, by separating the bodies so objects don't go through other objects: this allows for behaviours such as bouncing, pushing, blocking, etc. However we don't want the coins to _block_ the character, so we will merely perform a **hit test** and see if the character's body is overlapping a coin's body. 18 | 19 | ## Tasks 20 | 21 | ### Load the spritesheet 22 | 23 | 1. Spritesheets are a special type of asset, so we need to load them with `game.load.spritesheet` –and not with `game.load.image`. Note that we need to specify the dimensions of each individual frame (22✕22 pixels in this case): 24 | 25 | ```js 26 | PlayState.preload = function () { 27 | // ... 28 | this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22); 29 | }; 30 | ``` 31 | 32 | ### Spawn the coins 33 | 34 | 1. Coins data is stored in the level JSON file, so we will spawn them when we load the level. We also need a group to store all the coins, so we can detect later whether the player has touched them. 35 | 36 | ```js 37 | PlayState._loadLevel = function (data) { 38 | this.platforms = this.game.add.group(); 39 | this.coins = this.game.add.group(); 40 | 41 | // ... 42 | 43 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 44 | // spawn important objects 45 | data.coins.forEach(this._spawnCoin, this); 46 | 47 | // ... 48 | }; 49 | ``` 50 | 51 | 1. Onto our new `_spawnCoin` method! Coins will have no behavior (besides a looping animation), so we don't need a custom class for it and can settle for regular `Phaser.Sprite` instances. 52 | 53 | ```js 54 | PlayState._spawnCoin = function (coin) { 55 | let sprite = this.coins.create(coin.x, coin.y, 'coin'); 56 | sprite.anchor.set(0.5, 0.5); 57 | }; 58 | ``` 59 | 60 | 1. This is a good point to see if it's working in the browser. You should be able to see some –still static!– coins spawned through all the level. 61 | 62 | ![Static coins](/assets/platformer/static_coins.png) 63 | 64 | ### Add an animation! 65 | 66 | 1. Each sprite can have multiple animations, but here we only need one (the coin rotating). When adding a new animation, we specify which frame indices it will use. Optionally, we can set the animation speed (measured in frames per second) and whether the animation should loop or not. We will add and play the animation in the `_spawnCoin` method: 67 | 68 | ```js 69 | PlayState._spawnCoin = function (coin) { 70 | // ... 71 | sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looped 72 | sprite.animations.play('rotate'); 73 | }; 74 | ``` 75 | 76 | 1. Reload the browser and you should see the coins animated like this: 77 | 78 | ![Animated coin](/assets/platformer/animated_coin.gif) 79 | 80 | ### Make the character pick up coins 81 | 82 | 1. Let's _check_ for collisions between the character and the coins. Since we will use the physics engine for this, we need to give the coins a physic body (and don't forget to disable gravity or the coins will fall!). 83 | 84 | ```js 85 | PlayState._spawnCoin = function (coin) { 86 | // ... 87 | this.game.physics.enable(sprite); 88 | sprite.body.allowGravity = false; 89 | }; 90 | ``` 91 | 92 | 1. Now onto the detection itself! As we have said before, we will use `overlap` and not `collide` because we just want to query for overlaps, and not the coins to _block_ the character. 93 | 94 | ```js 95 | PlayState._handleCollisions = function () { 96 | //... 97 | this.game.physics.arcade.overlap(this.hero, this.coins, this._onHeroVsCoin, 98 | null, this); 99 | }; 100 | ``` 101 | 102 | If you are wondering what that `null` is for… We can add a **filter** function to exclude some of the sprites for this check. Since we want to check _all_ coins, we can just pass `null` to indicate "no filter, please". 103 | 104 | 1. Let's implement now `_onHeroVSCoin`, which is the callback that will be executed every time the main character touches a coin. What we will be doing is to get rid off the coin –this can be done by calling the `Phaser.Sprite.kill` method. 105 | 106 | ```js 107 | PlayState._onHeroVsCoin = function (hero, coin) { 108 | coin.kill(); 109 | }; 110 | ``` 111 | 112 | ### Play some audio feedback 113 | 114 | 1. Picking coin should feel _rewarding_ and playing a sound effect will help to achieve this. Let's load it in `preload`: 115 | 116 | ```js 117 | PlayState.preload = function () { 118 | // ... 119 | this.game.load.audio('sfx:coin', 'audio/coin.wav'); 120 | }; 121 | ``` 122 | 123 | 1. Now we just have to create a `Phaser.Sound` instance… 124 | 125 | ```js 126 | PlayState.create = function () { 127 | this.sfx = { 128 | jump: this.game.add.audio('sfx:jump'), 129 | coin: this.game.add.audio('sfx:coin') 130 | }; 131 | // ... 132 | }; 133 | ``` 134 | 135 | 1. And play the sound effect in the overlap callback! 136 | 137 | ```js 138 | PlayState._onHeroVsCoin = function (hero, coin) { 139 | this.sfx.coin.play(); 140 | // ... 141 | }; 142 | ``` 143 | 144 | Now you should be able to move the main character and collect all the coins in the level. 145 | 146 | ## Checklist 147 | 148 | - Coins are displayed in the level with an animation. 149 | - The main character can pick up coins, and they disappear when it happens. 150 | - There's a sound effect playing when picking up a coin. 151 | -------------------------------------------------------------------------------- /src/content/platformer/step10_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Walking enemies 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step10.js 5 | --- 6 | 7 | Right now the only challenge the player needs to overcome in our game is to execute jumps properly. It's not very fun –specially since there are no pits the character can fall into–, so let's add a hazard in the form of enemies. 8 | 9 | Meet the mighty spiders! 10 | 11 | ![Walking spider](/assets/platformer/walking_spider.gif) 12 | 13 | This enemy has a simple behaviour: move horizontally until it finds a "border" (a wall, the bounds of the screen, or the end of the platform) and then turn into the opposite direction. 14 | 15 | As you could see in the GIF, spiders are animated. This is its spritesheet: 16 | 17 | ![Spider spritesheet](/assets/platformer/spider_spritesheet.png) 18 | 19 | We will use a trick so the spiders don't fall off platforms: **invisible walls**. These walls will be sprites, with a physic body, but will not be seen. The main character will also be oblivious to them. But the spiders… the spiders will collide against these walls and turn around! 20 | 21 | Here is how these walls would look like, if they were being displayed: note that there's one at the edge of each platform. 22 | 23 | ![Invisible walls](/assets/platformer/invisible_walls.png) 24 | 25 | ## Tasks 26 | 27 | ### Create a custom sprite for the enemies 28 | 29 | 1. First we need to load the spritesheet in `preload`: 30 | 31 | ```js 32 | PlayState.preload = function () { 33 | // ... 34 | this.game.load.spritesheet('spider', 'images/spider.png', 42, 32); 35 | }; 36 | ``` 37 | 38 | 1. Now let's code a custom class that extends `Phaser.Sprite`, as we did with the main character. In the constructor, we will enable physics for this sprite, add the walking animation and set the sprite to move right initially: 39 | 40 | ```js 41 | function Spider(game, x, y) { 42 | Phaser.Sprite.call(this, game, x, y, 'spider'); 43 | 44 | // anchor 45 | this.anchor.set(0.5); 46 | // animation 47 | this.animations.add('crawl', [0, 1, 2], 8, true); 48 | this.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12); 49 | this.animations.play('crawl'); 50 | 51 | // physic properties 52 | this.game.physics.enable(this); 53 | this.body.collideWorldBounds = true; 54 | this.body.velocity.x = Spider.SPEED; 55 | } 56 | 57 | Spider.SPEED = 100; 58 | 59 | // inherit from Phaser.Sprite 60 | Spider.prototype = Object.create(Phaser.Sprite.prototype); 61 | Spider.prototype.constructor = Spider; 62 | ``` 63 | 64 | ### Spawn the spiders 65 | 66 | 1. The level JSON file contains the points where the spiders should be created, so we will spawn them in `_loadLevel`, as we have done with the rest of the sprites. Add there a new **group** to store the spiders, right below where the coins group is being created. We are also passing the spiders data to the `_spawnCharacters` method. 67 | 68 | ```js 69 | PlayState._loadLevel = function (data) { 70 | // ... 71 | this.coins = this.game.add.group(); 72 | this.spiders = this.game.add.group(); 73 | // ... 74 | // spawn hero and enemies 75 | this._spawnCharacters({hero: data.hero, spiders: data.spiders}); 76 | }; 77 | ``` 78 | 79 | 1. Now spawn the spiders at `_spawnCharacters`: 80 | 81 | ```js 82 | PlayState._spawnCharacters = function (data) { 83 | // spawn spiders 84 | data.spiders.forEach(function (spider) { 85 | let sprite = new Spider(this.game, spider.x, spider.y); 86 | this.spiders.add(sprite); 87 | }, this); 88 | // ... 89 | }; 90 | ``` 91 | 92 | 1. Try it out and you will see a small disaster… 93 | 94 | ![Spiders affected by gravity](/assets/platformer/spider_disaster.gif) 95 | 96 | This is happening because the spiders are being affected by gravity and restricted to stay within the screen bounds, but we are not resolving collisions against the world (i.e. the platforms!). 97 | 98 | ### Resolve collisions 99 | 100 | 1. The first step is to enable collision resolution between the spiders and the platforms, like we did with the main character: 101 | 102 | ```js 103 | PlayState._handleCollisions = function () { 104 | this.game.physics.arcade.collide(this.spiders, this.platforms); 105 | // ... 106 | }; 107 | ``` 108 | 109 | ### Add invisible "walls" so the spiders don't fall off platforms 110 | 111 | 1. Let's add those invisible walls so the poor spiders don't fall off. Let's load the image first –it will not be displayed, but it's used so the sprite knows how big the wall is: 112 | 113 | ```js 114 | PlayState.preload = function () { 115 | // ... 116 | this.game.load.image('invisible-wall', 'images/invisible_wall.png'); 117 | // ... 118 | }; 119 | ``` 120 | 121 | 1. We also need a group to store these walls, so we can do collision detection later. Create this group after the one that holds the spiders: 122 | 123 | ```js 124 | PlayState._loadLevel = function (data) { 125 | // ... 126 | this.spiders = this.game.add.group(); 127 | this.enemyWalls = this.game.add.group(); 128 | // ... 129 | }; 130 | ``` 131 | 132 | 1. Now let's create two walls per spawned platform: one at the left side, another one at the right side: 133 | 134 | ```js 135 | PlayState._spawnPlatform = function (platform) { 136 | // ... 137 | this._spawnEnemyWall(platform.x, platform.y, 'left'); 138 | this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right'); 139 | }; 140 | ``` 141 | 142 | ```js 143 | PlayState._spawnEnemyWall = function (x, y, side) { 144 | let sprite = this.enemyWalls.create(x, y, 'invisible-wall'); 145 | // anchor and y displacement 146 | sprite.anchor.set(side === 'left' ? 1 : 0, 1); 147 | 148 | // physic properties 149 | this.game.physics.enable(sprite); 150 | sprite.body.immovable = true; 151 | sprite.body.allowGravity = false; 152 | }; 153 | ``` 154 | 155 | 1. We need to resolve collisions against these walls so the spiders can't go through them, right after checking for collisions against platforms… 156 | 157 | ```js 158 | PlayState._handleCollisions = function () { 159 | this.game.physics.arcade.collide(this.spiders, this.platforms); 160 | this.game.physics.arcade.collide(this.spiders, this.enemyWalls); 161 | // ... 162 | }; 163 | ``` 164 | 165 | 1. If you reload the browser you can see how some pink walls stop the spiders from falling! 166 | 167 | ![Spider blocked by wall](/assets/platformer/spider_vs_wall.png) 168 | 169 | 1. We obviously don't want to show those walls to the player, so let's hide them right after creating the group. We can hide game entities by setting their `visible` property to `false`: 170 | 171 | ```js 172 | PlayState._loadLevel = function (data) { 173 | // ... 174 | this.enemyWalls = this.game.add.group(); 175 | this.enemyWalls.visible = false; 176 | // ... 177 | }; 178 | ``` 179 | 180 | ### Make the spiders turn 181 | 182 | 1. We know that there is a flag in a sprite's body, `touching`, that we can query to see whether the sprite is touching another one. These is what we need to detect that we have colliding against a wall or a platform. 183 | 184 | However, we will also need to check for the `blocked` flag, since it will tell us collisions against the world bounds. 185 | 186 | Add an `update` method to `Spider`. This method will be **called automatically** by Phaser every frame. Remember that we must add new methods to custom sprites _after_ having cloned their parent's prototype: 187 | 188 | ```js 189 | Spider.prototype.update = function () { 190 | // check against walls and reverse direction if necessary 191 | if (this.body.touching.right || this.body.blocked.right) { 192 | this.body.velocity.x = -Spider.SPEED; // turn left 193 | } 194 | else if (this.body.touching.left || this.body.blocked.left) { 195 | this.body.velocity.x = Spider.SPEED; // turn right 196 | } 197 | }; 198 | ``` 199 | 200 | Done! Spiders should be turning around when they reach the end of the platform, a wall, or the border of the screen: 201 | 202 | ![Spider turning into the opposite direction](/assets/platformer/spider_turning.gif) 203 | 204 | ## Checklist 205 | 206 | - There are three cute spiders walking around happily without falling down or going through platforms. 207 | - Spiders turn when they reach an obstacle or the end of the platform, so they stay in motion continuously. 208 | - The main character cannot influence the spiders movement in any way. 209 | -------------------------------------------------------------------------------- /src/content/platformer/step11_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Death 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step11.js 5 | --- 6 | 7 | We have enemies, but right now there's no interaction between them and the main character. Let's allow them to kill each other! 8 | 9 | - The spiders will kill the main character simply by touching them. 10 | - The main character will only be able to kill an enemy by jumping (or falling) over them. 11 | 12 | As with picking up coins, we will need to merely have a **hit test** (with `overlap`) and not resolving collisions (i.e. separating bodies, etc.). 13 | 14 | ## Tasks 15 | 16 | ### Make the spiders able to kill the main character 17 | 18 | 1. Killing or being killed is an important event, and we should provide a lot of feedback to the user. We will be playing a sound effect when this happens, so let's load the audio asset and create its corresponding sound entity: 19 | 20 | ```js 21 | PlayState.create = function () { 22 | this.sfx = { 23 | // ... 24 | stomp: this.game.add.audio('sfx:stomp') 25 | }; 26 | // ... 27 | }; 28 | ``` 29 | 30 | ```js 31 | PlayState.preload = function () { 32 | // ... 33 | this.game.load.audio('sfx:stomp', 'audio/stomp.wav'); 34 | }; 35 | ``` 36 | 37 | 1. To do the killing, we need to detect when a spider is touching the main character. We can do this by calling `overlap`: 38 | 39 | ```js 40 | PlayState._handleCollisions = function () { 41 | // ... 42 | this.game.physics.arcade.overlap(this.hero, this.spiders, 43 | this._onHeroVsEnemy, null, this); 44 | }; 45 | ``` 46 | 47 | 1. We need to implement the `_onHeroVsEnemy` callback method. For now, we'll just make the spider to kill the hero. When that happens, we will play a sound effect and **restart the level** (by restarting the game state). 48 | 49 | ```js 50 | PlayState._onHeroVsEnemy = function (hero, enemy) { 51 | this.sfx.stomp.play(); 52 | this.game.state.restart(); 53 | }; 54 | ``` 55 | 56 | 1. Try it in the browser and make sure that the level restarts whenever the main character touches an enemy. 57 | 58 | ### Kill those enemies! 59 | 60 | 1. Let's allow the main character to kill the spiders. To detect whether it's falling or not, we can check the vertical velocity of the body. If it's positive, it means the character is falling and, thus, able to kill! Let's modify the `_onHeroVsEnemy` callback to detect if the contact has been produced during a fall: 61 | 62 | ```js 63 | PlayState._onHeroVsEnemy = function (hero, enemy) { 64 | if (hero.body.velocity.y > 0) { // kill enemies when hero is falling 65 | enemy.kill(); 66 | this.sfx.stomp.play(); 67 | } 68 | else { // game over -> restart the game 69 | this.sfx.stomp.play(); 70 | this.game.state.restart(); 71 | } 72 | }; 73 | ``` 74 | 75 | 1. Try it and you should be able to kill the spiders. But it looks a bit odd, isn't it? Let's add a small bounce to the main character, like in classic platformers: 76 | 77 | ```js 78 | Hero.prototype.bounce = function () { 79 | const BOUNCE_SPEED = 200; 80 | this.body.velocity.y = -BOUNCE_SPEED; 81 | }; 82 | ``` 83 | 84 | ```js 85 | PlayState._onHeroVsEnemy = function (hero, enemy) { 86 | if (hero.body.velocity.y > 0) { 87 | hero.bounce(); 88 | // ... 89 | } 90 | // ... 91 | }; 92 | ``` 93 | 94 | 1. Try it again. Much better, isn't it? 95 | 96 | ![Bouncing on enemies](/assets/platformer/enemy_bounce.gif) 97 | 98 | ### Dying animation 99 | 100 | 1. Let's make killing enemies even more satisfying by adding an animation for when the spider has been hit. We will use the last two frames of the spritesheet for this. 101 | 102 | ```js 103 | function Spider(game, x, y) { 104 | // ... 105 | this.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12); 106 | // ... 107 | } 108 | ``` 109 | 110 | 1. Once thing we are going to need to do is to delay the actual killing, for when a sprite doesn't exist it's not visible and doesn't get updated. Let's add a new method for the spider to agonize: 111 | 112 | ```js 113 | Spider.prototype.die = function () { 114 | this.body.enable = false; 115 | 116 | this.animations.play('die').onComplete.addOnce(function () { 117 | this.kill(); 118 | }, this); 119 | }; 120 | ``` 121 | 122 | Note how we are **disabling the body** to remove the sprite from physics operation. This is important so the spider stops and isn't taken into account for collisions. 123 | 124 | 1. Now change the `kill` call on `_heroVsEnemy` for a call to this new method: 125 | 126 | ```js 127 | PlayState._onHeroVsEnemy = function (hero, enemy) { 128 | // ... 129 | if (hero.body.velocity.y > 0) { 130 | // make sure you remove enemy.kill() !!! 131 | enemy.die(); 132 | } 133 | // ... 134 | }; 135 | ``` 136 | 1. It should be working now! 137 | 138 | ![Spider dying animation](/assets/platformer/enemy_dying.gif) 139 | -------------------------------------------------------------------------------- /src/content/platformer/step12_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Scoreboard 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step12.js 5 | --- 6 | 7 | In this step we are going to add a scoreboard that displays how many coins the main character has collected: 8 | 9 | ![Coin scoreboard](/assets/platformer/coin_scoreboard.png) 10 | 11 | In order to do that, we need to be able to **write text** in the screen. In games, this can be done in different ways: 12 | 13 | - By using a regular TTF font, like Times New Roman (for HTML5 games this could be a Web Font) 14 | - By using a bitmap font, which is actually a spritesheet, and render the characters one by one like they were images. 15 | 16 | For the scoreboard we will use a bitmap font, which are called in Phaser **retro fonts**. The font will consist only of digits, a blank space and an `x` character. Here's the spritesheet: 17 | 18 | ![Bitmap font spritesheet](/assets/platformer/bitmap_font_sheet.png) 19 | 20 | It's important to know that in order to render a text with a bitmap font, we need both an instance of `Phaser.RetroFont` _and_ an instance of `Phaser.Image`. Why? The retro font holds the _image data_ in memory (i.e. the pixel values of a rendered text), but then we need a Phaser _entity_ that can make use of that image data, such as `Phaser.Image` (or even `Phaser.Sprite`)! 21 | 22 | 23 | ## Tasks 24 | 25 | ### Keep track of how many coins have been collected 26 | 27 | 1. We only need a property in `PlayState` to do this. We are initialising it to zero in `init`, and then increasing this counter when a coin is picked up: 28 | 29 | ```js 30 | PlayState.init = function () { 31 | // ... 32 | this.coinPickupCount = 0; 33 | }; 34 | ``` 35 | 36 | ```js 37 | PlayState._onHeroVsCoin = function (hero, coin) { 38 | // ... 39 | this.coinPickupCount++; 40 | }; 41 | ``` 42 | 43 | ### Draw a coin icon on top of everything 44 | 45 | 1. Load the image asset in `preload`: 46 | 47 | ```js 48 | PlayState.preload = function () { 49 | // ... 50 | this.game.load.image('icon:coin', 'images/coin_icon.png'); 51 | // ... 52 | }; 53 | ``` 54 | 55 | 1. We will separate the creation of UI elements into a separate method. Inside it, we will create a new group to store all the UI icons, text, etc. 56 | 57 | ```js 58 | PlayState._createHud = function () { 59 | let coinIcon = this.game.make.image(0, 0, 'icon:coin'); 60 | 61 | this.hud = this.game.add.group(); 62 | this.hud.add(coinIcon); 63 | this.hud.position.set(10, 10); 64 | }; 65 | ``` 66 | 67 | Note how all entities inside `this.hud` will get rendered _relatively_ to it. This means that, since the hud is in position `(10, 10)`, if we draw an image at –for instance– `(5, 5)`, it will get rendered at position `(15, 15)` of the screen. 68 | 69 | 1. Since the HUD must be rendered on top of everything else, it should be created _after_ spawning all the elements in the level: 70 | 71 | ```js 72 | PlayState.create = function () { 73 | // ... 74 | this._createHud(); 75 | } 76 | ``` 77 | 78 | 1. Check that the coin icon is rendered at the top left of the screen: 79 | 80 | ![HUD with coin icon](/assets/platformer/hud_icon_only.png) 81 | 82 | ### Write the text 83 | 84 | 1. Finally we get to the most interesting part! As usual, we need to load the asset that will make up the font. Note that, even though _conceptually_ it is a spritesheet, in Phaser it needs to be loaded it with `load.image`: 85 | 86 | ```js 87 | PlayState.preload = function () { 88 | // ... 89 | this.game.load.image('font:numbers', 'images/numbers.png'); 90 | // ... 91 | }; 92 | ``` 93 | 94 | 1. Now we need to instantiate `Phaser.RetroFont`, that will be able to compute how a text looks like with the bitmap font spritesheet. 95 | 96 | ```js 97 | PlayState._createHud = function () { 98 | const NUMBERS_STR = '0123456789X '; 99 | this.coinFont = this.game.add.retroFont('font:numbers', 20, 26, 100 | NUMBERS_STR, 6); 101 | // ... 102 | }; 103 | ``` 104 | 105 | Since Phaser has no idea of the contents of the spritesheet, we need to tell it when creating the retro font: the width and height of each character and which characters are being included (the orden is important!) 106 | 107 | 1. With the retro font created, we need to make use of it from a game entity. We will use a `Phaser.Image` for this: 108 | 109 | ```js 110 | PlayState._createHud = function () { 111 | // let coinIcon = ... 112 | let coinScoreImg = this.game.make.image(coinIcon.x + coinIcon.width, 113 | coinIcon.height / 2, this.coinFont); 114 | coinScoreImg.anchor.set(0, 0.5); 115 | 116 | // ... 117 | this.hud.add(coinScoreImg); 118 | }; 119 | ``` 120 | 121 | 1. Last, we just need to tell the retro font which text string to render. 122 | 123 | ```js 124 | PlayState.update = function () { 125 | // ... 126 | this.coinFont.text = `x${this.coinPickupCount}`; 127 | }; 128 | ``` 129 | 130 | Try it in the browser and see how the text changes with every coin collected! 131 | 132 | ![Level with coin score board](/assets/platformer/level_coin_scoreboard.png) 133 | -------------------------------------------------------------------------------- /src/content/platformer/step13_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Animations for the main character 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step13.js 5 | --- 6 | 7 | Right now we have a few animated sprites in the game: the coins and the enemy spiders. But none for the main character! We are going to implement them now. 8 | 9 | This is the character's spritesheet and the animations in it: 10 | 11 | ![Main character spritesheet](/assets/platformer/hero_spritesheet.png) 12 | 13 | - Stopped: frame #0 14 | - Running: frames #1 - #2 15 | - Jumping (upwards): frame #3 16 | - Falling: frame #4 17 | 18 | There's also a dying/hit animation in the spritesheet, but we will implement it in a later stage. 19 | 20 | As you can see, this can be a bit complex, so the approach that we will follow to handle animations for the main character is to **check every frame** which animation should be active and, if it's different, we'll play another one. 21 | 22 | ## Tasks 23 | 24 | ### Add the new animations 25 | 26 | 1. Previously we had `hero_stopped.png` assigned to the `hero` key, loaded as an image. We need to get rid of that, so **delete this line** in the `preload`: 27 | 28 | ```js 29 | PlayState.preload = function () { 30 | // delete this line below 31 | this.game.load.image('hero', 'images/hero_stopped.png'); 32 | }; 33 | ``` 34 | 35 | 1. Now we need to load the new spritesheet into the `hero` key: 36 | 37 | ```js 38 | PlayState.preload = function () { 39 | // ... 40 | this.game.load.spritesheet('hero', 'images/hero.png', 36, 42); 41 | // ... 42 | }; 43 | ``` 44 | 45 | 1. Add the new animations in the `Hero` constructor: 46 | 47 | ```js 48 | function Hero(game, x, y) { 49 | // ... 50 | this.animations.add('stop', [0]); 51 | this.animations.add('run', [1, 2], 8, true); // 8fps looped 52 | this.animations.add('jump', [3]); 53 | this.animations.add('fall', [4]); 54 | } 55 | ``` 56 | 57 | ### Calculate which animation should be playing 58 | 59 | 1. This is the new `Hero` method that will return the _name_ of the animation that should be playing: 60 | 61 | ```js 62 | Hero.prototype._getAnimationName = function () { 63 | let name = 'stop'; // default animation 64 | 65 | // jumping 66 | if (this.body.velocity.y < 0) { 67 | name = 'jump'; 68 | } 69 | // falling 70 | else if (this.body.velocity.y >= 0 && !this.body.touching.down) { 71 | name = 'fall'; 72 | } 73 | else if (this.body.velocity.x !== 0 && this.body.touching.down) { 74 | name = 'run'; 75 | } 76 | 77 | return name; 78 | }; 79 | ``` 80 | 81 | Note how in the _falling_ state we are both checking that the vertical velocity is positive (it goes downwards) _and_ that the main character is not touching a platform. Why? Because when the character is on the ground it still has a vertical velocity caused by **the gravity**. The character doesn't fall because there is a body blocking them, not because their vertical velocity is zero. 82 | 83 | 1. We will create an `update` method for `Hero` in which we will check which animation should be playing and switch to a new one if necessary. Remember that `update` methods in `Phaser.Sprite` instances get called automatically each frame! 84 | 85 | ```js 86 | Hero.prototype.update = function () { 87 | // update sprite animation, if it needs changing 88 | let animationName = this._getAnimationName(); 89 | if (this.animations.name !== animationName) { 90 | this.animations.play(animationName); 91 | } 92 | }; 93 | ``` 94 | 95 | 1. Try it now in the browser! Run, jump around… You should be able to see all the animations in place. _And_ a little glitch: the character **does not face** the right direction when moving left. 96 | 97 | ![Animations… with a glitch!](/assets/platformer/hero_animation_glitch.gif) 98 | 99 | ### Make the character face the right direction 100 | 101 | 1. It may sound weird, but usually in game development **flipping** (or mirroring) an image is achieved by applying a **negative scale** to the image. So applying a scale of `-100%` horizontally will flip the image of the character to face to the left. 102 | 103 | Add this to the `move` method, since we know the direction in that moment: 104 | 105 | ```js 106 | Hero.prototype.move = function (direction) { 107 | // ... 108 | if (this.body.velocity.x < 0) { 109 | this.scale.x = -1; 110 | } 111 | else if (this.body.velocity.x > 0) { 112 | this.scale.x = 1; 113 | } 114 | }; 115 | ``` 116 | 117 | In Phaser scales are normalized, so `0.5` means `50%`, `1` means `100%` and so on. 118 | 119 | The final result is the main character facing the right direction when moving. 120 | 121 | ![Main character, properly animated](/assets/platformer/hero_animations.gif) 122 | 123 | ## Checklist 124 | 125 | - The main character shows different animations or images for the following actions: not moving, running, jumping and falling. 126 | - The main character faces the correct direction when moving either left or right. 127 | -------------------------------------------------------------------------------- /src/content/platformer/step14_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Win condition 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step14.js 5 | --- 6 | 7 | Currently the player can _lose_ in the game –and they will have to start over again–, but there is no way for them to _win_. 8 | 9 | We are going to add two elements to the level: a **door** and a **key**. The goal of the game would be to fetch the key and then go back to the door and open it to advance to the next level. We will also add an **icon** next to the coin scoreboard to display whether the key has been picked up yet or not. 10 | 11 | In the JSON file there is already the data of where the door and the key should be placed. 12 | 13 | Here's how the whole thing will look like: 14 | 15 | ![Level with the win condition elements](/assets/platformer/win_condition.png) 16 | 17 | ## Tasks 18 | 19 | ### Create the door 20 | 21 | 1. The door is a spritesheet (showing it closed and open): 22 | 23 | ```js 24 | PlayState.preload = function () { 25 | // ... 26 | this.game.load.spritesheet('door', 'images/door.png', 42, 66); 27 | }; 28 | ``` 29 | 30 | 1. The door needs to appear _below_ all the other sprites. We will be adding later some other elements that act as decoration (bushes, fences, flowers…) and need to appear at the back as well. For this, we will create a new group to store these kind of objects: 31 | 32 | ```js 33 | PlayState._loadLevel = function (data) { 34 | this.bgDecoration = this.game.add.group(); 35 | // ... 36 | }; 37 | ``` 38 | 39 | Since this group is created _before_ any other, the objects it contains will appear below the rest. 40 | 41 | 1. We will split the creation of the door and the key in separate functions. The door will be created within a new `PlayState` method, `_spawnDoor`: 42 | 43 | ```js 44 | PlayState._spawnDoor = function (x, y) { 45 | this.door = this.bgDecoration.create(x, y, 'door'); 46 | this.door.anchor.setTo(0.5, 1); 47 | this.game.physics.enable(this.door); 48 | this.door.body.allowGravity = false; 49 | }; 50 | ``` 51 | 52 | Note that we have enabled physics in it. This is because we are going to detect if there is a **collision between the door and the main character** and see if the key has been already picked to trigger the win condition. 53 | 54 | 1. Now we just need to call that method from `_loadLevel`: 55 | 56 | ```js 57 | PlayState._loadLevel = function (data) { 58 | // ... 59 | // after spawning the coins in this line: 60 | // data.coins.forEach(this._spawnCoin, this); 61 | this._spawnDoor(data.door.x, data.door.y); 62 | // ... 63 | }; 64 | ``` 65 | 66 | 1. Load the game in the browser and see how the door has been created: 67 | 68 | ![Door](/assets/platformer/door_spawned.png) 69 | 70 | ### Create the key 71 | 72 | 1. The key is very similar to the door, but it just has a single image, not a spritesheet: 73 | 74 | ```js 75 | LoadingState.preload = function () { 76 | // ... 77 | this.game.load.image('key', 'images/key.png'); 78 | }; 79 | ``` 80 | 81 | 1. As with the door, we will have a separate new method to spawn the key: 82 | 83 | ```js 84 | PlayState._spawnKey = function (x, y) { 85 | this.key = this.bgDecoration.create(x, y, 'key'); 86 | this.key.anchor.set(0.5, 0.5); 87 | this.game.physics.enable(this.key); 88 | this.key.body.allowGravity = false; 89 | }; 90 | ``` 91 | 92 | Since the key should also appear behind enemies and other sprites, we are adding it to the same group as the door. 93 | 94 | 1. And we call the `_spawnKey` method just after having created the door: 95 | 96 | ```js 97 | PlayState._loadLevel = function (data) { 98 | // ... 99 | // add it below the call to _spawnDoor 100 | // this._spawnDoor(data.door.x, data.door.y); 101 | this._spawnKey(data.key.x, data.key.y); 102 | // ... 103 | }; 104 | ``` 105 | 106 | 1. Now you should be able to see the key at the top right region of the screen! 107 | 108 | ![Static key](/assets/platformer/key_spawned.png) 109 | 110 | ### Implement the win condition 111 | 112 | 1. The win condition is touching the door once the character has picked up the key. We are going to store whether the key has been picked up or not in a flag, as a property of `PlayState`: 113 | 114 | ```js 115 | PlayState.init = function () { 116 | // ... 117 | this.hasKey = false; 118 | }; 119 | ``` 120 | 121 | The `hasKey` flag will be set to `true` once the key has been collected. 122 | 123 | 1. To make sure the player understands that picking up the key is an important action, we are going to play a sound effect when this happens. So let's load its asset and create a `Phaser.Sound` instance for it. We are doing the same for the "open door" sound effect here as well. 124 | 125 | ```js 126 | PlayState.preload = function () { 127 | // ... 128 | this.game.load.audio('sfx:key', 'audio/key.wav'); 129 | this.game.load.audio('sfx:door', 'audio/door.wav'); 130 | }; 131 | ``` 132 | 133 | ```js 134 | PlayState.create = function () { 135 | this.sfx = { 136 | key: this.game.add.audio('sfx:key'), 137 | door: this.game.add.audio('sfx:door'), 138 | // ... 139 | }; 140 | // ... 141 | }; 142 | ``` 143 | 144 | 1. We are going to collect the key in the same way that we collect the coins: call `overlap` in the Arcade physics engine and then kill the key so it doesn't appear anymore. We will also play the sound effect, and set `hasKey` to `true`: 145 | 146 | ```js 147 | PlayState._handleCollisions = function () { 148 | // ... 149 | this.game.physics.arcade.overlap(this.hero, this.key, this._onHeroVsKey, 150 | null, this) 151 | }; 152 | ``` 153 | 154 | ```js 155 | PlayState._onHeroVsKey = function (hero, key) { 156 | this.sfx.key.play(); 157 | key.kill(); 158 | this.hasKey = true; 159 | }; 160 | ``` 161 | 162 | 1. Play the game, fetch the key and notice how it disappears and the sound effect is playing. 163 | 164 | 1. We now have the first part of the win condition: fetching the key. Let's implement the final one: opening the door with it. 165 | 166 | ```js 167 | PlayState._handleCollisions = function () { 168 | // ... 169 | this.game.physics.arcade.overlap(this.hero, this.door, this._onHeroVsDoor, 170 | // ignore if there is no key or the player is on air 171 | function (hero, door) { 172 | return this.hasKey && hero.body.touching.down; 173 | }, this); 174 | }; 175 | ``` 176 | 177 | This time, we have made use of the **filter** function we can pass to `overlap`. This is because we don't want the overlap test to pass if the player hasn't fetched the key yet or if the main character is jumping –it would be weird to open a key while jumping, right? 178 | 179 | 1. The collision callback looks like this: 180 | 181 | ```js 182 | PlayState._onHeroVsDoor = function (hero, door) { 183 | this.sfx.door.play(); 184 | this.game.state.restart(); 185 | // TODO: go to the next level instead 186 | }; 187 | ``` 188 | 189 | For now, we are just playing a sound effect and restarting the level. Later on, we will implement level switching so the player can advance through all of them! 190 | 191 | 1. Try it! Play the level, fetch the key and then go back to the door. The level should restart and you should hear the door opening. 192 | 193 | ### Spice-up the key… 194 | 195 | 1. Right now the key is very static. We don't have an animation in a spritesheet, but the key is an important object and should be highlighted somehow… we will do it by adding a _movement_ animation, instead of a image-based one. 196 | 197 | We can easily get this via [Phaser.Tween](http://phaser.io/docs/2.6.2/Phaser.Tween.html) instances. If you have worked with Flash or jQuery animations, tweens will be very familiar to you. 198 | 199 | ```js 200 | PlayState._spawnKey = function (x, y) { 201 | // ... 202 | // add a small 'up & down' animation via a tween 203 | this.key.y -= 3; 204 | this.game.add.tween(this.key) 205 | .to({y: this.key.y + 6}, 800, Phaser.Easing.Sinusoidal.InOut) 206 | .yoyo(true) 207 | .loop() 208 | .start(); 209 | }; 210 | ``` 211 | 212 | The tween above will move the key up and down slightly, continuously. If you want more information about tweens, or want to tweak it, you can check out Phaser's [documentation](http://phaser.io/docs/2.6.2/Phaser.Tween.html) or [the examples](http://phaser.io/examples/v2/category/tweens). 213 | 214 | 1. Load the game and you should be able to see the animation in place. 215 | 216 | ![Key tweening](/assets/platformer/key_tween.gif) 217 | 218 | ### Add the key icon 219 | 220 | 1. Last, we will add an icon next to the scoreboard to display if the key has been picked up. We will use a spritesheet for it: 221 | 222 | ```js 223 | PlayState.preload = function () { 224 | // ... 225 | this.game.load.spritesheet('icon:key', 'images/key_icon.png', 34, 30); 226 | } 227 | ``` 228 | 229 | 1. We will make an image in `_createHud`: 230 | 231 | ```js 232 | PlayState._createHud = function () { 233 | this.keyIcon = this.game.make.image(0, 19, 'icon:key'); 234 | this.keyIcon.anchor.set(0, 0.5); 235 | // ... 236 | this.hud.add(this.keyIcon); 237 | }; 238 | ``` 239 | 240 | 1. Don't forget to move the scoreboard to the right to make room for the key icon! Change the spawning point of the coin icon: 241 | 242 | ```js 243 | PlayState._createHud = function () { 244 | // ... 245 | // remove the previous let coinIcon = ... line and use this one instead 246 | let coinIcon = this.game.make.image(this.keyIcon.width + 7, 0, 'icon:coin'); 247 | // ... 248 | }; 249 | ``` 250 | 251 | 1. If you load the game you will be able to see the icon! 252 | 253 | ![Key icon (empty frame)](/assets/platformer/key_icon_empty.png) 254 | 255 | 1. Now we need to change the frame of the spritesheet depending on whether the key has been picked up or not. With sprites, we have used animations before to handle spritesheets, but since this is not an animation and we don't need to control the timing, we can just use the `frame` property to select the frame index we want: 256 | 257 | ```js 258 | PlayState.update = function () { 259 | // ... 260 | this.keyIcon.frame = this.hasKey ? 1 : 0; 261 | }; 262 | ``` 263 | 264 | 1. Play the level again, pick up the key and… ta-da! 265 | 266 | ![Key icon (filled)](/assets/platformer/key_icon_filled.png) 267 | 268 | ## Checklist 269 | 270 | - A door and a key appear in the level. 271 | - If the main character picks up the key, it disappears and a sound effect is played. 272 | - The level restarts when the main character gets to the door, having picked up the key. 273 | - The level _does not_ restart when the main character gets to the door when the key has not been collected. 274 | - There is an icon at the top left part of the screen that indicates if the key has been picked up. 275 | -------------------------------------------------------------------------------- /src/content/platformer/step15_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Switching levels 3 | layout: guide_step.pug 4 | download: /assets/platformer/steps/step15.js 5 | --- 6 | 7 | We have the win condition in place, but right now the only thing it does is restarting the current level… which is no fun! If you look into the `data` directory of the game, you will see that there are two JSON files: `level00.json` and `level01.json`. 8 | 9 | What we are going to do is to **start the game** at level `#0`, and then switch to `#1` when `#0` is completed. Once the last level (in this case, `#1`) we should present some kind of "game completed" screen, but unfortunately that is out of the scope of this guide. What we would to instead is to **restart the whole game from the beginning**. 10 | 11 | And remember that, even though there are only two levels already pre-made, **you can build your own** by adding more JSON files! 12 | 13 | The way we have of switching levels is by passing to `PlayState` which level we want to use. Even though we haven't been using it, it turns out that the `init` method of game states can accept parameters! We will tell the state which level to use with that. 14 | 15 | ## Tasks 16 | 17 | ### Load the other level 18 | 19 | 1. Let's start by loading the first level JSON file in `preload`: 20 | 21 | ```js 22 | PlayState.preload = function () { 23 | this.game.load.json('level:0', 'data/level00.json'); 24 | // ... 25 | }; 26 | ``` 27 | 28 | 1. We need to add a parameter to `init`. We are going to assume it will be an object with some properties. We will only use `level` now, but you could add other configuration in here. 29 | 30 | ```js 31 | const LEVEL_COUNT = 2; 32 | 33 | PlayState.init = function (data) { 34 | // ... 35 | this.level = (data.level || 0) % LEVEL_COUNT; 36 | }; 37 | ``` 38 | 39 | The `LEVEL_COUNT` constant is there to restart the game from the first level when we reach the end. If you add more levels and JSON files later, don't forget to update the value of this constant! 40 | 41 | 1. We also need to update the call to `_loadLevel`. Right now we have hardcoded the key of the JSON asset loaded, but we need to build that key taking into account which is the current level. 42 | 43 | ```js 44 | PlayState.create = function () { 45 | // ... 46 | // substitute the line below for the new one! 47 | // this._loadLevel(this.game.cache.getJSON('level:1')); 48 | this._loadLevel(this.game.cache.getJSON(`level:${this.level}`)); 49 | // ... 50 | }; 51 | ``` 52 | 53 | 1. Now we just need to tell `init` which level we want to load. We will pass a `{level: 0}` object, which will load the first level. 54 | 55 | Change the call to start the `PlayState` in `window.onload`: 56 | 57 | ```js 58 | window.onload = function () { 59 | // ... 60 | // change the line below for the new one! 61 | // game.state.start('play'); 62 | game.state.start('play', true, false, {level: 0}); 63 | }; 64 | ``` 65 | 66 | What are those booleans doing there? If you check out Phaser's documentation for `start` you will find the answer: the first one, `true`, tells Phaser that you want to keep the cache (i.e. the assets that we have already loaded); the second one, `false` tells Phaser that we do _not_ want to keep the existing world objects: it will wipe out all the current entities –sprites, texts, images, groups, etc.–. These are the default values, by the way. 67 | 68 | 1. Try it and you should see a different level loaded now! 69 | 70 | ![The first level](/assets/platformer/level00_thumb.png) 71 | 72 | ### Let the player advance through levels 73 | 74 | 1. If you try to complete the level, you will see how the game freezes due to a JavaScript error. This is because we are restarting the level, `init` is expecting a parameter, and we are not providing it! 75 | 76 | Let's fix this by modifying the `game.state.restart` calls in our code. The one triggered by the win condition is located at `_onHeroVsDoor`. Get rid of the old call and use this new one, with parameters: 77 | 78 | ```js 79 | PlayState._onHeroVsDoor = function (hero, door) { 80 | // ... 81 | // delete the previous call to restart() 82 | // this.game.state.restart(); 83 | this.game.state.restart(true, false, { level: this.level + 1 }); 84 | }; 85 | ``` 86 | 87 | See how we are increasing the level? And since in `init` we use a modulo operation on the level number, we achieve the effect of going back to the beginning of the game when all the levels have been completed. 88 | 89 | 1. Now let's fix the other call to restart the level, that happens when an enemy kills the main character. 90 | 91 | ```js 92 | PlayState._onHeroVsEnemy = function (hero, enemy) { 93 | // ... 94 | else { 95 | // ... 96 | // delete previous call to restart 97 | // this.game.state.restart(); 98 | this.game.state.restart(true, false, {level: this.level}); 99 | } 100 | }; 101 | ``` 102 | 103 | All done! Play the game and win both levels to make sure everything works. 104 | 105 | ## Checklist 106 | 107 | - The game starts from a new level 108 | - Winning a level gets us to the next one 109 | - Once all levels have been completed, the game is restarted from the first level. 110 | - When the main character is killed, the _current_ level gets restarted. 111 | -------------------------------------------------------------------------------- /src/content/platformer/step16_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Moving forward… 3 | layout: guide_step.pug 4 | --- 5 | 6 | **Congratulations**! You completed the workshop and have a playable game you can share with other people. 7 | 8 | However, you might have noticed that the finished version of the game is a bit more **polished**: 9 | 10 | - It features a loading state (useful for when you publish your game in the Web) 11 | - It has a music track playing in the background. 12 | - The main character can jump higher or lower depending on how much time the up arrow key was pressed. 13 | - The main character features a dying animation. 14 | - The main character has a "enter the door" animation for when a level it's finished. 15 | - Camera fade out and fade in. 16 | 17 | The source code of this version has been provided for you to peek around and learn from it. It doesn't contain significantly _new_ things, so you should be able to follow it along with the help of [Phaser's documentation](http://phaser.io/docs). 18 | 19 | A good way of learning would be to try to **replicate those features without seeing the code** beforehand. Check out [Phaser's examples](http://phaser.io/examples), look for tutorials online, answers in online forums, etc. 20 | 21 | If you get stuck, or are curious about how a feature was implemented in this case (there's usually more than just one way of doing things!), you have the full source code at your disposal. 22 | 23 | ## Some tips to take into account 24 | 25 | - You can keep an sprite in the world but remove it from all the physics calculations by disabling its body. For example: 26 | 27 | ```js 28 | this.body.enable = false; 29 | ``` 30 | 31 | - [`Phaser.Camera`](http://phaser.io/docs/2.6.2/Phaser.Camera.html) has methods for fadign in and out. 32 | 33 | - You can subscribe to an `onComplete` event of an animation or tween to do something once the animation or tween has finished. 34 | 35 | - Songs are handled with the same class than sound effects, `Phaser.Sound`. 36 | 37 | ## Game development resources 38 | 39 | If you are itching to make more games, you may want to check out [the resources page](/en/bonus/resources/), which lists tools, articles, etc. to develop HTML5 games! 40 | -------------------------------------------------------------------------------- /src/content/setup/index_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Setup your machine 3 | layout: guide_index.pug 4 | collection_base: setup 5 | author_twitter: ladybenko 6 | author_name: Belén "Benko" Albeza 7 | --- 8 | 9 | In order to complete this workshop, you need to prepare your machine for HTML5 game development. If you are already a seasoned front-end developer, you might have already all the tools you need in your system! 10 | 11 | This workshop is aimed at people from all levels. That is why we will only use the absolute minimum amount of tools and software. 12 | 13 | This is what you need to have in place: 14 | 15 | - A text editor 16 | - Access to the terminal/console in your system 17 | - A local web server 18 | 19 | ## A text editor 20 | 21 | You will use this to **code** the game. A good one is [Atom](https://atom.io/), which is available for Windows, Mac and Linux and it's open source. 22 | 23 | Other alternatives are: 24 | 25 | - [Brackets](http://brackets.io/) 26 | - [Sublime](https://www.sublimetext.com/) 27 | - vim ;) 28 | 29 | ## A terminal 30 | 31 | Depending on how you choose to complete the next step (launching a local web server), you might need to use the terminal in your system. Learn how to launch the one in your system: 32 | 33 | - In Windows, it is known as the **Command Prompt**, and you can access is at `Start > Run` and typing `cmd`. 34 | - On Mac OS, it is the **Terminal** app, and you can find it in your `Applications` folder. 35 | - In Linux, most distributions put a terminal icon in the dock for easy access. In Ubuntu, it is an app known as **Terminal**. 36 | 37 | Of course, if you have already other terminals in your system and prefer those, feel free to use them! 38 | -------------------------------------------------------------------------------- /src/content/setup/step00_en.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Launch a local server 3 | --- 4 | 5 | All game engines that allow for asset loading within the engine require running the game in a **local web server**. The reason is that browsers, for security reasons, block files loading from different domains. 6 | 7 | When you simply open an HTML file from your computer in the browser, the protocol used is `file://`. This means that the site is not running on any domain/address at all, and thus Phaser is unable to dynamically load assets such as images, audio files, etc. 8 | 9 | The solution to this is to run a local web server in our machine and serve our files from there. 10 | 11 | Depending on your operating system and what you have already installed, there are several ways to do this. 12 | 13 | _Note: we will assume that the game you want to run is inside the `my-game` directory._ 14 | 15 | ## If you have Node 16 | 17 | If you have Node.js in your system… congratulations! You can just install a web server and run it whenever you need it. 18 | 19 | Execute this line in your terminal / command line to install the http-server package: 20 | 21 | ```sh 22 | npm -g install http-server 23 | ``` 24 | 25 | And then you can just run it from any directory and it will serve its contents: 26 | 27 | ```sh 28 | cd my-game 29 | http-server 30 | ``` 31 | 32 | ### An even better alternative… 33 | 34 | For development you might also be interested in Browser Sync, a tool that will launch a server and **automatically reload the browser** when files are changed. 35 | 36 | ```sh 37 | npm -g install browser-sync 38 | ``` 39 | 40 | This will launch a server and reload the browser whenever a JavaScript file is modified: 41 | 42 | ```sh 43 | cd my-game 44 | browser-sync start --server --files="**/*.js" 45 | ``` 46 | 47 | ## If you have Mac OS or Linux (or Python in your system) 48 | 49 | If you have Python in your system –and you _have it_ if you are running a Unix-like system, such as Mac OS X or Linux–, then you don't need to install anything else! 50 | 51 | Run this from the terminal: 52 | 53 | ```sh 54 | cd my-game 55 | python -m SimpleHTTPServer 56 | ``` 57 | 58 | ## I don't have Node or Python :( 59 | 60 | OK, don't panic! Here are some alternatives: 61 | 62 | - Some text editors and IDE's incorporate a local web server. [Brackets](http://brackets.io/) is one of those. 63 | 64 | - Check out Phaser's _Getting Started_ guide [suggestions]( https://phaser.io/tutorials/getting-started/part2). 65 | -------------------------------------------------------------------------------- /src/layouts/default.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | head 3 | title HTML5 Games Workshop - #{title} 4 | meta(charset='utf-8') 5 | meta(name='viewport', content='width=device-width, initial-scale=1') 6 | link(rel='stylesheet', href=relative('/css/styles.css')) 7 | link(rel='stylesheet', href=relative('/vendor/prism/prism.css')) 8 | script(src=relative('/vendor/prism/prism.js')) 9 | body 10 | header.main-header 11 | - var url = relative(locale === defaultLocale ? '/' : `/${locale}/`); 12 | h1: a(href=url) HTML5 Games Workshop 13 | 14 | main: article 15 | - let availableLocales = locales.filter(x => x !== locale && !!lang(x)) 16 | if availableLocales.length > 0 17 | p Also available in#{' '} 18 | each lc, index in availableLocales 19 | a(href=relative(`/${lang(lc).path}/`))= lc 20 | if index < availableLocales.length - 1 21 | |,#{' '} 22 | |. 23 | block content 24 | h1= title 25 | != contents 26 | block extra 27 | if next || previous 28 | nav.paginated-nav: ul 29 | if previous 30 | li.previous « Previous:  31 | a(href=relative(`/${previous.path}/`)) #{previous.title} 32 | if next 33 | li.next Next:  34 | a(href=relative(`/${next.path}/`)) #{next.title} 35 | | » 36 | footer.main-footer 37 | p With love,
the game dev fairies at Mozilla. 38 | -------------------------------------------------------------------------------- /src/layouts/guide_index.pug: -------------------------------------------------------------------------------- 1 | extends default.pug 2 | 3 | block content 4 | h1= title 5 | 6 | aside.author 7 | p Guide written by  8 | | #{author_name} ( 9 | a(href=`https://twitter.com/${author_twitter}`) @#{author_twitter} 10 | |). 11 | 12 | != contents 13 | 14 | h1 Contents of this guide 15 | ol.toc(start=0) 16 | each step in collections[`${collection_base}_${locale}`] 17 | li: a(href=relative(`/${step.path}/`))= step.title 18 | -------------------------------------------------------------------------------- /src/layouts/guide_step.pug: -------------------------------------------------------------------------------- 1 | extends default.pug 2 | 3 | block extra 4 | h2 Download 5 | if download 6 | p Are you stuck? Take a look at 7 | = ' ' 8 | a(href=relative(download), download) the source code 9 | = ' ' 10 | | for this step. 11 | --------------------------------------------------------------------------------