├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .yo-rc.json ├── LICENSE.md ├── README.md ├── app ├── index.html ├── scripts │ ├── config.js │ ├── constants │ │ ├── bitmap-fonts.js │ │ └── grid.js │ ├── index.js │ ├── objects │ │ ├── food.js │ │ └── snake.js │ └── scenes │ │ ├── game.js │ │ ├── index.js │ │ ├── loader.js │ │ ├── maze.js │ │ ├── menu.js │ │ └── scoreboard.js └── static │ ├── assets │ ├── body.png │ ├── font.png │ ├── food.png │ ├── frame.png │ └── title.png │ └── favicon.ico ├── gulpfile.esm.js ├── config │ ├── browsersync.js │ ├── paths.js │ └── webpack │ │ ├── index.js │ │ ├── plugins.js │ │ └── rules.js ├── index.js ├── lib │ ├── server.js │ ├── webpack-middlewares.js │ └── webpack.js └── tasks │ ├── clean.js │ ├── compile.js │ ├── dist.js │ └── serve.js ├── package.json ├── screenshot-1.png ├── screenshot-2.png └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | # General Options 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 2 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # JavaScript 14 | [*.js] 15 | indent_size = 2 16 | indent_style = space 17 | 18 | # Markdown 19 | [*.md] 20 | indent_size = 4 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | vendor/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ESLint rule-set 3 | * =============== 4 | * 5 | * The following rules are intended to be the least restrictive possible. Tweak 6 | * them as much as you want, or simply replace this file with another one you 7 | * have previously edited. 8 | * 9 | * Learn more about how to configure ESLint at 10 | * . 11 | */ 12 | 13 | exports.root = true; 14 | 15 | exports.env = { 16 | // Enable ES2015+ features 17 | es6: true, 18 | 19 | // Enable Browser global variables 20 | browser: true 21 | }; 22 | 23 | exports.parserOptions = { 24 | // Validate ECMAScript 2015+ syntax 25 | ecmaVersion: 2017, 26 | 27 | // Allow ECMAScript modules 28 | sourceType: 'module' 29 | }; 30 | 31 | exports.globals = { 32 | PIXI: false, 33 | Phaser: false 34 | }; 35 | 36 | exports.extends = 'xo'; 37 | 38 | exports.rules = { 39 | // Indent code with 2 spaces 40 | 'indent': ['error', 2], 41 | 42 | // Prefer single quotes for strings and occasionally template literals 43 | 'quotes': [ 44 | 'error', 45 | 'single', 46 | { 47 | avoidEscape: true, 48 | allowTemplateLiterals: true 49 | } 50 | ], 51 | 52 | // Customize brace style 53 | 'brace-style': ['error', 'stroustrup', { 54 | allowSingleLine: false 55 | }], 56 | 57 | // Favor Unix-style line endings 58 | 'linebreak-style': ['error', 'unix'], 59 | 60 | // End lines with semicolons 61 | 'semi': ['error', 'always'], 62 | 63 | // Do not complain about comments beginning with lowercase letters. 64 | 'capitalized-comments': ['off'], 65 | 66 | // Fail on capitalized names not preceded by new but relax restriction for 67 | // Phaser function names. 68 | 'new-cap': ['error', { 69 | capIsNewExceptionPattern: '^Phaser\..' 70 | }] 71 | }; 72 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | Thumbs.db 4 | Session.vim 5 | 6 | dist/ 7 | node_modules/ 8 | 9 | !.git* 10 | !.babelrc 11 | !.eslint* 12 | !.yo-rc.json 13 | !.editorconfig 14 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-phaser-plus": { 3 | "meta": { 4 | "createdWith": "2.3.1", 5 | "creationDate": "2017-06-30T14:59:45.869Z" 6 | }, 7 | "objects": { 8 | "dest": "app/scripts/objects/" 9 | }, 10 | "plugins": { 11 | "dest": "app/scripts/plugins/" 12 | }, 13 | "scenes": { 14 | "dest": "app/scripts/scenes/", 15 | "index": { 16 | "name": "app/scripts/scenes/index.js", 17 | "requirePath": "./" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Rafael Barbosa Lopes 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Snake Game: A Phaser 3 Demo][game] [![dependencies status badge](https://flat.badgen.net/david/dep/rblopes/phaser-3-snake-game)](https://david-dm.org/rblopes/phaser-3-snake-game) [![development dependencies status badge](https://flat.badgen.net/david/dev/rblopes/phaser-3-snake-game)](https://david-dm.org/rblopes/phaser-3-snake-game?type=dev) 2 | 3 | A [Phaser 3][phsr] demonstration project powered by [Gulp][gulp] and [Webpack][wbpk], based on the tutorial titled "Snake Plissken", published on [Phaser World #85][pw85]. 4 | 5 | > NOTE: This repository is **not** a Phaser 3 project template. If you're looking for a easier way to start a new game project from scratch, [`generator-phaser-plus`][gpp_] is your best bet. 6 | 7 |
8 | Screenshots 9 |
Screenshot
10 |
Screenshot
11 |
12 | 13 | 14 | ## Game Controls 15 | 16 | Use the left () and right () arrow keys to rotate and change direction. 17 | 18 | For every five pieces of food eaten, the speed increases. 19 | 20 | 21 | ## Instructions for Developers 22 | 23 | > NOTE: It is highly recommended that you have the latest [long-term support](https://nodejs.org/en/about/releases/) (LTS) release of Node.js installed to use this project. [Yarn](https://yarnpkg.com/) is the preferred package manager to manage dependencies. 24 | 25 | Clone this repository and install the project dependencies using `yarn`. To play the sample game, just run `yarn start`. 26 | 27 | 28 | ## License 29 | 30 | Distributed under the terms of the [MIT License](LICENSE.md). Portions of the game code are derived from a previous work by Richard Davey ([@photonstorm](https://github.com/photonstorm)). 31 | 32 | [wbpk]: https://webpack.js.org/ 33 | [pw85]: https://madmimi.com/p/03594a 34 | [gulp]: https://github.com/gulpjs/gulp 35 | [phsr]: https://github.com/photonstorm/phaser 36 | [game]: https://rblopes.github.io/phaser-3-snake-game/ 37 | [gpp_]: https://github.com/rblopes/generator-phaser-plus 38 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= htmlWebpackPlugin.options.title %> 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/scripts/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `config` module 3 | * =============== 4 | * 5 | * The game instance settings. 6 | */ 7 | 8 | // Import created game scenes. 9 | import * as scenes from '@/scenes'; 10 | 11 | // HINT: Import plugins, custom or trusted third-party ones, here. 12 | // import ExamplePlugin from 'example-plugin'; 13 | // import ExampleScenePlugin from '@/plugins/example-scene-plugin'; 14 | 15 | /** 16 | * Game canvas width. 17 | */ 18 | export const width = 640; 19 | 20 | /** 21 | * Game canvas height. 22 | */ 23 | export const height = 360; 24 | 25 | /** 26 | * Adjust zoom factor. 27 | */ 28 | export const zoom = 1; 29 | 30 | /** 31 | * Adjust pixel density of game graphics. 32 | */ 33 | export const resolution = 1; 34 | 35 | /** 36 | * Choose a rendering method. 37 | * 38 | * Available options are: 39 | * 40 | * - `WEBGL`: Use WebGL rendering; 41 | * - `CANVAS`: Use 'context2D' API rendering method; 42 | * - `AUTO`: Phaser will choose, based on device capabilities, the best 43 | * rendering method to be used. 44 | */ 45 | export const type = Phaser.AUTO; 46 | 47 | /** 48 | * Whether to disable antialiasing or not. Great for pixel art. 49 | */ 50 | export const pixelArt = false; 51 | 52 | /** 53 | * Whether to enable canvas transparency or not. 54 | */ 55 | export const transparent = false; 56 | 57 | /** 58 | * Apply some style to the canvas element. 59 | */ 60 | export const canvasStyle = 'display: block; margin: 0 auto;'; 61 | 62 | /** 63 | * Define a default a background color. 64 | */ 65 | export const backgroundColor = '#bfcc00'; 66 | 67 | /** 68 | * Configure physics engines global parameters. 69 | * 70 | * Available systems are: 71 | * 72 | * - `arcade`: Phaser Arcade Physics 2; 73 | * - `matter`: Liam Brummitt's (@liabru) Matter.js; 74 | * - `impact`: ImpactJS Physics Engine. 75 | */ 76 | export const physics = { 77 | /** 78 | * Phaser Arcade Physics 2 parameters. 79 | * 80 | * This engine becomes available under a `physics` property on game scenes. 81 | */ 82 | // arcade: { 83 | // }, 84 | 85 | /** 86 | * Matter.js parameters. 87 | * 88 | * This engine becomes available under a `matter` property on game scenes. 89 | */ 90 | // matter: { 91 | // }, 92 | 93 | /** 94 | * Impact Physics Engine parameters. 95 | * 96 | * This engine becomes available under a `impact` property on game scenes. 97 | */ 98 | // impact: { 99 | // }, 100 | 101 | /** 102 | * Enable a physics engine by default on all game scenes. 103 | */ 104 | default: false 105 | }; 106 | 107 | /** 108 | * Global parameters of the asset manager. 109 | */ 110 | export const loader = { 111 | // HINT: Put all your game assets in the `app/static/assets/` directory. 112 | path: 'assets/' 113 | }; 114 | 115 | /** 116 | * Declare custom Phaser plugins. 117 | * 118 | * There are two kinds of plugins: Global Plugins and Scene Plugins. 119 | * 120 | * Global plugins are instantiated once per game and persist throughout the 121 | * whole session. 122 | * 123 | * Scene plugins are instantiated with each scene and are stored in the 124 | * `Systems` class, rather than the global plugin manager. Scene plugins are 125 | * tied to the scene life cycle. 126 | */ 127 | export const plugins = { 128 | global: [ 129 | // { 130 | // // An identifier to associate this plugin instance within Phaser's 131 | // // plugin manager cache. It must be unique to avoid naming clashes 132 | // // with other plugins. 133 | // key: 'ExamplePlugin', 134 | // 135 | // // The imported plugin class. 136 | // plugin: ExamplePlugin, 137 | // 138 | // // The property name your plugin will be aliased to. This plugin 139 | // // will be exposed as a property of your scene context, for example, 140 | // // `this.myPlugin`. 141 | // mapping: 'myPlugin', 142 | // 143 | // // Whether to start up or not this plugin on game's initialization. 144 | // // If omitted or set to `false`, you must request the plugin manager 145 | // // to start up the plugin on a game scene using the method 146 | // // `this.plugins.start('')`. 147 | // start: true 148 | // }, 149 | ], 150 | 151 | scene: [ 152 | // { 153 | // // An identifier for reference in the scene `Systems` class. It must 154 | // // be unique to avoid naming clashes with other plugins. 155 | // key: 'ExampleScenePlugin', 156 | // 157 | // // The imported plugin class. 158 | // plugin: ExampleScenePlugin, 159 | // 160 | // // The property name your plugin will be aliased to. This plugin 161 | // // will be exposed as a property of your scene context, for example, 162 | // // `this.myPlugin`. 163 | // mapping: 'myPlugin' 164 | // } 165 | ] 166 | }; 167 | 168 | /** 169 | * Export the game title, version and Web address, as defined in the 170 | * project package metadata file (`package.json`). 171 | * 172 | * These properties can be accessed in the game configuration object 173 | * (`scene.sys.game.config`), under the keys `gameTitle`, `gameVersion` and 174 | * `gameURL`, respectively. 175 | */ 176 | export {title, version, url} from '@/../../package.json'; 177 | 178 | /** 179 | * Export created game scenes. 180 | */ 181 | export const scene = Object.values(scenes); 182 | -------------------------------------------------------------------------------- /app/scripts/constants/bitmap-fonts.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `bitmap-fonts` 3 | * ============== 4 | * 5 | * Bitmap fonts definitions. 6 | */ 7 | 8 | export default { 9 | image: 'font', 10 | width: 16, 11 | height: 16, 12 | chars: '0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ', 13 | offset: {x: 0, y: 0}, 14 | spacing: {x: 0, y: 0}, 15 | charsPerRow: 13 16 | }; 17 | -------------------------------------------------------------------------------- /app/scripts/constants/grid.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `grid` 3 | * ====== 4 | * 5 | * Grid dimensions. 6 | */ 7 | 8 | /** 9 | * The width of the grid. 10 | */ 11 | export const WIDTH = 39; 12 | 13 | /** 14 | * The height of the grid. 15 | */ 16 | export const HEIGHT = 20; 17 | 18 | /** 19 | * The lenght of each grid unit, in pixels. 20 | */ 21 | export const LENGTH = 16; 22 | -------------------------------------------------------------------------------- /app/scripts/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `app` module 3 | * ============ 4 | * 5 | * Provides the game initialization routine. 6 | */ 7 | 8 | // Import game instance configuration. 9 | import * as config from '@/config'; 10 | 11 | // Boot the game. 12 | export function boot() { 13 | return new Phaser.Game(config); 14 | } 15 | 16 | boot(); 17 | -------------------------------------------------------------------------------- /app/scripts/objects/food.js: -------------------------------------------------------------------------------- 1 | import {WIDTH, HEIGHT, LENGTH} from '@/constants/grid'; 2 | 3 | export default class Food extends Phaser.GameObjects.Image { 4 | /** 5 | * The food to be eaten by the snake. 6 | * 7 | * @param {Phaser.Scene} scene - The scene that owns this image. 8 | * @param {number} x - The horizontal coordinate relative to the scene viewport. 9 | * @param {number} y - The vertical coordinate relative to the scene viewport. 10 | * @extends Phaser.GameObjects.Image 11 | */ 12 | constructor(scene, x, y) { 13 | super(scene, x * LENGTH, y * LENGTH, 'food').setOrigin(0); 14 | 15 | scene.children.add(this); 16 | } 17 | 18 | /** 19 | * We can place the food anywhere in our grid *except* on-top of the snake, 20 | * so we need to filter those out of the possible food locations. If there 21 | * aren't any locations left, they've won! 22 | * 23 | * @public 24 | * @param {Snake} snake - The snake object. 25 | * @returns {boolean} true if the food was placed, otherwise false. 26 | */ 27 | reposition(snake) { 28 | // First, create a grid that assumes all positions are valid for the new 29 | // piece of food. 30 | const testGrid = Array.from( 31 | {length: HEIGHT}, 32 | () => Array.from({length: WIDTH}, () => true) 33 | ); 34 | 35 | snake.updateGrid(testGrid); 36 | 37 | // Purge out false positions. 38 | const validLocations = []; 39 | 40 | for (let y = 0; y < HEIGHT; y++) { 41 | for (let x = 0; x < WIDTH; x++) { 42 | if (testGrid[y][x] === true) { 43 | // Is this position valid for food? If so, add it here... 44 | validLocations.push({x, y}); 45 | } 46 | } 47 | } 48 | 49 | if (validLocations.length > 0) { 50 | // Use the RNG to pick a random food position. 51 | let pos = Phaser.Math.RND.pick(validLocations); 52 | 53 | // And place it. 54 | this.setPosition(pos.x * LENGTH, pos.y * LENGTH); 55 | 56 | return true; 57 | } 58 | 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/scripts/objects/snake.js: -------------------------------------------------------------------------------- 1 | import {WIDTH, HEIGHT, LENGTH} from '@/constants/grid'; 2 | 3 | export default class Snake { 4 | /** 5 | * Handles the logic and appearance of the snake in the maze. 6 | * 7 | * @param {Phaser.Scene} scene - The scene that owns this object. 8 | * @param {number} x - The horizontal coordinate relative to the scene viewport. 9 | * @param {number} y - The vertical coordinate relative to the scene viewport. 10 | */ 11 | constructor(scene, x, y) { 12 | this.body = scene.add.group({ 13 | defaultKey: 'body', 14 | createCallback: o => o.setOrigin(0) 15 | }); 16 | 17 | this.head = this.body.create(x * LENGTH, y * LENGTH); 18 | 19 | this.direction = new Phaser.Geom.Point(LENGTH, 0); 20 | this.headPosition = new Phaser.Geom.Point(0, 0); 21 | this.tailPosition = new Phaser.Geom.Point(0, 0); 22 | 23 | this.alive = true; 24 | this.updated = true; 25 | this.moveTime = 0; 26 | this.moveDelay = 100; 27 | } 28 | 29 | /** 30 | * Updates the snake segments in the maze. 31 | * 32 | * @public 33 | * @param {number} time - The current game clock value. 34 | * @returns {boolean} Whether the snake updated or not. 35 | */ 36 | update(time) { 37 | if (time >= this.moveTime) { 38 | this.updated = true; 39 | return this.move(time); 40 | } 41 | 42 | return false; 43 | } 44 | 45 | /** 46 | * Makes the snake rotate counter clockwise on the next update. 47 | * 48 | * @public 49 | */ 50 | turnLeft() { 51 | if (this.updated) { 52 | this.direction.setTo(this.direction.y, -this.direction.x); 53 | 54 | this.updated = false; 55 | } 56 | } 57 | 58 | /** 59 | * Makes the snake rotate clockwise on the next update. 60 | * 61 | * @public 62 | */ 63 | turnRight() { 64 | if (this.updated) { 65 | this.direction.setTo(-this.direction.y, this.direction.x); 66 | 67 | this.updated = false; 68 | } 69 | } 70 | 71 | /** 72 | * Tells whether the snake run over its body or not. 73 | * 74 | * @private 75 | * @returns {boolean} True if the snake collided with itself. 76 | */ 77 | hitBody() { 78 | return Phaser.Actions.GetFirst( 79 | this.body.children.entries, 80 | {x: this.head.x, y: this.head.y}, 81 | 1 82 | ); 83 | } 84 | 85 | /** 86 | * Moves the snake segments around the maze. 87 | * 88 | * @private 89 | * @param {number} time - The current game clock value. 90 | * @returns {boolean} Whether the snake has moved or not. 91 | */ 92 | move(time) { 93 | // Update the snake position according to the direction the player wants 94 | // it to move to. The `Math.Wrap` function call allows the snake to wrap 95 | // around the screen edges, so when it goes off any side it should 96 | // re-appear on the opposite side. 97 | this.headPosition.setTo( 98 | Phaser.Math.Wrap(this.head.x + this.direction.x, 0, WIDTH * LENGTH), 99 | Phaser.Math.Wrap(this.head.y + this.direction.y, 0, HEIGHT * LENGTH) 100 | ); 101 | 102 | // Update the body segments and place the last coordinate into 103 | // `this.tailPosition`. 104 | Phaser.Actions.ShiftPosition( 105 | this.body.children.entries, 106 | this.headPosition.x, 107 | this.headPosition.y, 108 | 1, 109 | this.tailPosition 110 | ); 111 | 112 | // Check to see if any of the body pieces have the same x/y as the head. 113 | // If they do, the head ran into the body. 114 | if (this.hitBody()) { 115 | // Game Over! 116 | this.alive = false; 117 | return false; 118 | } 119 | 120 | // Update the timer ready for the next movement. 121 | this.moveTime = time + this.moveDelay; 122 | 123 | return true; 124 | } 125 | 126 | /** 127 | * Adds a new segment to the snake. 128 | * 129 | * @private 130 | */ 131 | grow() { 132 | this.body.create(this.tailPosition.x, this.tailPosition.y); 133 | } 134 | 135 | /** 136 | * Checks if the snake has collided with a piece of food. 137 | * 138 | * @public 139 | * @param {Food} food - A food sprite. 140 | * @param {number} points - The player scored points. 141 | * @returns {boolean} True if the snake collided, false otherwise. 142 | */ 143 | collideWithFood(food, points) { 144 | if (this.head.x === food.x && this.head.y === food.y) { 145 | this.grow(); 146 | 147 | // For every 5 pieces of food eaten we'll increase the snake speed a 148 | // little. 149 | if (this.moveDelay > 20 && points % 25 === 0) { 150 | this.moveDelay -= 5; 151 | } 152 | 153 | return true; 154 | } 155 | 156 | return false; 157 | } 158 | 159 | /** 160 | * Validates the positions on the grid where a new piece of food can be 161 | * placed. 162 | * 163 | * @protected 164 | * @param {boolean.} grid - A grid of positions to validate. 165 | * @returns {boolean.} The updated grid. 166 | */ 167 | updateGrid(grid) { 168 | // Remove all body pieces from valid positions list. 169 | for (const segment of this.body.getChildren()) { 170 | const x = segment.x / LENGTH; 171 | const y = segment.y / LENGTH; 172 | 173 | grid[y][x] = false; 174 | } 175 | 176 | return grid; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /app/scripts/scenes/game.js: -------------------------------------------------------------------------------- 1 | export default class Game extends Phaser.Scene { 2 | /** 3 | * The main game scene. It spawns the other two game scenes in parallel. 4 | * One is the score board, showing the player points and the eventual 'GAME 5 | * OVER' message. The other is the maze where the actual game action 6 | * happens. Player input and game logic updates are handled here. 7 | * 8 | * This scene emits two events: 9 | * - `food-eaten`: When a food gets eaten by the snake. 10 | * - `snake-died`: When the snake collides with itself. 11 | * 12 | * Those events are used to update the score board. 13 | * 14 | * @extends Phaser.Scene 15 | */ 16 | constructor() { 17 | super({key: 'Game'}); 18 | 19 | /** 20 | * Keep the last high score registered. 21 | */ 22 | this.highScore = 0; 23 | } 24 | 25 | /** 26 | * Called when this scene is initialized. 27 | * 28 | * @protected 29 | * @param {object} [data={}] - Initialization parameters. 30 | */ 31 | init(/* data */) { 32 | /** 33 | * Current game score. 34 | */ 35 | this.points = 0; 36 | } 37 | 38 | /** 39 | * Responsible for setting up game objects on the screen. 40 | * 41 | * @protected 42 | * @param {object} [data={}] - Initialization parameters. 43 | */ 44 | create(/* data */) { 45 | // Put the frame behind the maze. 46 | this.add.image(0, 0, 'frame').setOrigin(0, 0); 47 | 48 | // Get a reference of the scenes to start. 49 | const scoreboard = this.scene.get('Scoreboard'); 50 | const maze = this.scene.get('Maze'); 51 | 52 | // Run both scenes in parallel. 53 | this.scene 54 | .launch(scoreboard, {gameScene: this}) 55 | .launch(maze); 56 | 57 | // Add the game objects to the maze scene. 58 | this.food = maze.addFood(3, 4); 59 | this.snake = maze.addSnake(8, 8); 60 | 61 | // Create our keyboard controls. 62 | this.cursors = this.input.keyboard.addKeys({ 63 | leftKey: Phaser.Input.Keyboard.KeyCodes.LEFT, 64 | rightKey: Phaser.Input.Keyboard.KeyCodes.RIGHT 65 | }); 66 | } 67 | 68 | /** 69 | * Handles updates to game logic, physics and game objects. 70 | * 71 | * @protected 72 | * @param {number} time - Current internal clock time. 73 | * @param {number} delta - Time elapsed since last update. 74 | */ 75 | update(time) { 76 | if (this.snake.alive) { 77 | this.updateInput(); 78 | this.updateLogic(time); 79 | } 80 | } 81 | 82 | // ------------------------------------------------------------------------ 83 | 84 | /** 85 | * Handles user input. 86 | * 87 | * @private 88 | */ 89 | updateInput() { 90 | const {leftKey, rightKey} = this.cursors; 91 | 92 | // Check which key was just pressed down, then change the direction the 93 | // snake is heading. 94 | if (Phaser.Input.Keyboard.JustDown(leftKey)) { 95 | this.snake.turnLeft(); 96 | } 97 | else if (Phaser.Input.Keyboard.JustDown(rightKey)) { 98 | this.snake.turnRight(); 99 | } 100 | } 101 | 102 | /** 103 | * Updates game logic. 104 | * 105 | * @private 106 | * @param {number} time - Current internal clock time. 107 | */ 108 | updateLogic(time) { 109 | const {food, snake} = this; 110 | 111 | if (snake.update(time)) { 112 | // If the snake updated, we need to check for collision against food. 113 | if (snake.collideWithFood(food, this.points)) { 114 | this.updatePoints(); 115 | food.reposition(snake); 116 | } 117 | } 118 | 119 | if (!snake.alive) { 120 | this.endGame(); 121 | } 122 | } 123 | 124 | /** 125 | * Announces game over. 126 | * 127 | * @private 128 | */ 129 | endGame() { 130 | this.events.emit('snake-died'); 131 | 132 | // Update the high score. 133 | this.highScore = Math.max(this.points, this.highScore); 134 | 135 | // Wait for a moment and go back to the menu screen. 136 | this.time.delayedCall(2500, () => { 137 | this.scene 138 | .stop('Scoreboard') 139 | .stop('Maze') 140 | .start('Menu'); 141 | }); 142 | } 143 | 144 | /** 145 | * Updates score points. 146 | * 147 | * @private 148 | */ 149 | updatePoints() { 150 | this.points += 5; 151 | this.events.emit('food-eaten', this.points); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/scripts/scenes/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `states` module 3 | * =============== 4 | * 5 | * Declares all present game states. 6 | */ 7 | 8 | export {default as Loader} from './loader'; 9 | export {default as Menu} from './menu'; 10 | export {default as Game} from './game'; 11 | export {default as Maze} from './maze'; 12 | export {default as Scoreboard} from './scoreboard'; 13 | -------------------------------------------------------------------------------- /app/scripts/scenes/loader.js: -------------------------------------------------------------------------------- 1 | import fontConfig from '@/constants/bitmap-fonts'; 2 | 3 | export default class Loader extends Phaser.Scene { 4 | /** 5 | * Takes care of loading the main game assets. 6 | * 7 | * @extends Phaser.Scene 8 | */ 9 | constructor() { 10 | super({key: 'Loader'}); 11 | } 12 | 13 | /** 14 | * Declare which game assets need to be loaded. 15 | * 16 | * @protected 17 | */ 18 | preload() { 19 | this.load.image(['body', 'food', 'font', 'frame', 'title']); 20 | } 21 | 22 | /** 23 | * Set up and launch the main scene. 24 | * 25 | * @protected 26 | * @param {object} [data={}] - Initialization parameters. 27 | */ 28 | create(/* data */) { 29 | // Register our custom bitmap font in he game system cache. 30 | this.cache.bitmapFont.add( 31 | fontConfig.image, 32 | Phaser.GameObjects.RetroFont.Parse(this, fontConfig) 33 | ); 34 | 35 | // We are done here. Launch the game menu. 36 | this.scene.start('Menu'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/scripts/scenes/maze.js: -------------------------------------------------------------------------------- 1 | import {WIDTH, HEIGHT, LENGTH} from '@/constants/grid'; 2 | import Food from '@/objects/food'; 3 | import Snake from '@/objects/snake'; 4 | 5 | export default class Maze extends Phaser.Scene { 6 | /** 7 | * Where the actual game play happens. Control and logic updates are 8 | * handled by the Game scene, though. 9 | * 10 | * @extends Phaser.Scene 11 | */ 12 | constructor() { 13 | super({ 14 | key: 'Maze', 15 | 16 | // Make the viewport an exact fit of the game board, giving a margin of 17 | // half the grid length (8px) around its edges. 18 | cameras: [{ 19 | x: LENGTH / 2, 20 | y: 2 * LENGTH, 21 | width: WIDTH * LENGTH, 22 | height: HEIGHT * LENGTH 23 | }] 24 | }); 25 | } 26 | 27 | // ------------------------------------------------------------------------ 28 | 29 | /** 30 | * Add the food sprite at the given grid coordinates. 31 | * 32 | * @protected 33 | * @param {number} [x=0] - The horizontal grid coordinate. 34 | * @param {number} [y=x] - The vetical grid coordinate. 35 | * @return {Food} The food sprite. 36 | */ 37 | addFood(x = 0, y = x) { 38 | return new Food(this, x, y); 39 | } 40 | 41 | /** 42 | * Add the snake group at the given grid coordinates. 43 | * 44 | * @protected 45 | * @param {number} [x=0] - The horizontal grid coordinate. 46 | * @param {number} [y=x] - The vetical grid coordinate. 47 | * @return {Snake} The snake sprite. 48 | */ 49 | addSnake(x = 0, y = x) { 50 | return new Snake(this, x, y); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/scripts/scenes/menu.js: -------------------------------------------------------------------------------- 1 | export default class Menu extends Phaser.Scene { 2 | /** 3 | * Show the game title and menu. 4 | * 5 | * @extends Phaser.Scene 6 | */ 7 | constructor() { 8 | super({key: 'Menu'}); 9 | } 10 | 11 | /** 12 | * Responsible for setting up the game objects on the screen. 13 | * 14 | * @protected 15 | * @param {object} [data={}] - Initialization parameters. 16 | */ 17 | create(/* data */) { 18 | // Save viewport center coordinates for reference. 19 | const x = this.cameras.main.width / 2; 20 | const y = this.cameras.main.height / 2; 21 | 22 | // Place the Title image above the middle of the screen. 23 | this.add.image(x, y - 80, 'title'); 24 | 25 | // Use a bitmap text object as the face of our start button. 26 | const startButton = this.add.bitmapText(x - 160, y + 80, 'font', 'START') 27 | .setOrigin(0.5, 1); 28 | 29 | // Apply a blink effect to the button using a custom easing function. 30 | this.add.tween({ 31 | targets: [startButton], 32 | ease: k => k < 0.5 ? 0 : 1, 33 | duration: 250, 34 | yoyo: true, 35 | repeat: -1, 36 | alpha: 0 37 | }); 38 | 39 | // Handle the click or tap of the button using an input zone slightly 40 | // bigger than the text object. 41 | this.add.zone( 42 | startButton.x - (startButton.width * startButton.originX) - 16, 43 | startButton.y - (startButton.height * startButton.originY) - 16, 44 | startButton.width + 32, 45 | startButton.height + 32 46 | ) 47 | .setOrigin(0, 0) 48 | .setInteractive() 49 | .once('pointerup', () => this.scene.start('Game')); 50 | 51 | // Get the last game high score. 52 | const {highScore} = this.scene.get('Game'); 53 | 54 | // Display the registered highest score of the game. 55 | this.add.bitmapText(x + 160, y + 80, 'font', 'HIGH SCORE') 56 | .setOrigin(0.5, 1); 57 | this.add.bitmapText(x + 160, y + 81, 'font', `${highScore} POINTS`) 58 | .setOrigin(0.5, 0); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/scripts/scenes/scoreboard.js: -------------------------------------------------------------------------------- 1 | import {WIDTH, LENGTH} from '@/constants/grid'; 2 | import fontConfig from '@/constants/bitmap-fonts'; 3 | 4 | export default class Scoreboard extends Phaser.Scene { 5 | /** 6 | * Shows the player scored points and the 'GAME OVER' message. 7 | * 8 | * Upon initialization, it receives the host scene object and binds to its 9 | * emitted events to receive updates. 10 | * 11 | * @extends Phaser.Scene 12 | */ 13 | constructor() { 14 | super({ 15 | key: 'Scoreboard', 16 | 17 | // Align the scene viewport to the top of the screen, with a margin of 18 | // half the length of the grid unit (8px) around its edges. 19 | cameras: [{ 20 | x: LENGTH / 2, 21 | y: LENGTH / 2, 22 | width: WIDTH * LENGTH, 23 | height: LENGTH 24 | }] 25 | }); 26 | } 27 | 28 | /** 29 | * Called when this scene is initialized. 30 | * 31 | * @protected 32 | * @param {object} data - Initialization parameters. 33 | * @param {Game} data.gameScene - The host scene. 34 | */ 35 | init({gameScene}) { 36 | // Bind the maze events to update the score board. 37 | gameScene.events 38 | .on('food-eaten', points => this.setScore(points)) 39 | .on('snake-died', () => this.showGameOver()); 40 | } 41 | 42 | /** 43 | * Responsible for setting up game objects on the screen. 44 | * 45 | * @protected 46 | * @param {object} [data={}] - Initialization parameters. 47 | */ 48 | create(/* data */) { 49 | // Add the score numerals label. 50 | this.scoreLabel = this.add.bitmapText(0, 0, fontConfig.image, '0'); 51 | 52 | // Align this label to the right side. 53 | this.gameOverLabel = 54 | this.add.bitmapText(WIDTH * LENGTH, 0, fontConfig.image, 'GAME OVER') 55 | .setOrigin(1, 0) 56 | .setVisible(false); 57 | } 58 | 59 | // ------------------------------------------------------------------------- 60 | 61 | /** 62 | * Updates the displayed game score. 63 | * 64 | * @param {number} points - How many points the player scored. 65 | * @private 66 | */ 67 | setScore(points) { 68 | this.scoreLabel.setText(String(points)); 69 | } 70 | 71 | /** 72 | * Displays the 'GAME OVER' message. 73 | * 74 | * @private 75 | */ 76 | showGameOver() { 77 | this.gameOverLabel.setVisible(true); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/static/assets/body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/app/static/assets/body.png -------------------------------------------------------------------------------- /app/static/assets/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/app/static/assets/font.png -------------------------------------------------------------------------------- /app/static/assets/food.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/app/static/assets/food.png -------------------------------------------------------------------------------- /app/static/assets/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/app/static/assets/frame.png -------------------------------------------------------------------------------- /app/static/assets/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/app/static/assets/title.png -------------------------------------------------------------------------------- /app/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/app/static/favicon.ico -------------------------------------------------------------------------------- /gulpfile.esm.js/config/browsersync.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Browsersync Settings 3 | * ==================== 4 | * 5 | * Basic settings for Browsersync Web server. 6 | * 7 | * Reference: 8 | * 9 | */ 10 | 11 | import {dirs} from './paths'; 12 | 13 | export default { 14 | ui: false, 15 | notify: false, 16 | ghostMode: false, 17 | server: { 18 | baseDir: [dirs.static] 19 | }, 20 | middleware: [], 21 | watchOptions: { 22 | ignoreInitial: true 23 | }, 24 | 25 | // Always reload the application page when files in these paths change. 26 | files: [`${dirs.static}/**`, `${dirs.scripts}/**`] 27 | }; 28 | -------------------------------------------------------------------------------- /gulpfile.esm.js/config/paths.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `paths` module 3 | * ============== 4 | * 5 | * Describes the project working tree for its consumer modules. 6 | */ 7 | 8 | import {resolve} from 'path'; 9 | 10 | // Exports the root directory path, hosting the project contents. 11 | export const root = process.cwd(); 12 | 13 | // Where application resources are kept. 14 | export const src = resolve(root, 'app/'); 15 | 16 | // The destination diretory of production builds. 17 | export const dest = resolve(root, 'dist/'); 18 | 19 | // Paths to specific directories. 20 | export const dirs = { 21 | // From where static assets should be served during development and copied 22 | // from in distribution builds. 23 | static: resolve(src, 'static/'), 24 | 25 | // Where application modules that require compilation are kept. 26 | scripts: resolve(src, 'scripts/') 27 | }; 28 | -------------------------------------------------------------------------------- /gulpfile.esm.js/config/webpack/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack Configuration 3 | * ===================== 4 | * 5 | * Defines the Webpack bundling configuration optimized for `development` or 6 | * `production` modes. 7 | * 8 | * For reference on configuration options, see the Webpack at 9 | * 10 | */ 11 | 12 | import {src, dirs, dest} from '../paths'; 13 | import rules from './rules'; 14 | import plugins from './plugins'; 15 | 16 | export default (env = 'development') => ({ 17 | // Enables Webpack optimizations for `development` or `production` modes. 18 | mode: env, 19 | 20 | // The base path where to resolve entry points. 21 | context: src, 22 | 23 | // Entry points of the application. Vendor libraries (e.g.: Phaser) are 24 | // declared first to become available globally. 25 | entry: { 26 | vendor: ['phaser'], 27 | app: [dirs.scripts] 28 | }, 29 | 30 | // Options instructing Webpack how and where to write compiled bundles. 31 | output: { 32 | filename: 33 | env === 'production' ? 34 | '[name]-[chunkhash].bundle.js' : 35 | '[name].bundle.js', 36 | path: dest 37 | }, 38 | 39 | // Controls how Webpack looks up for modules on the project. 40 | resolve: { 41 | // The file extensions Webpack will be looking up when using `import` 42 | // statements. 43 | extensions: ['.js'], 44 | 45 | alias: { 46 | // For convenience, makes '@' an alias of the source directory. 47 | '@': dirs.scripts 48 | } 49 | }, 50 | 51 | // How source files are processed by Webpack. The rules configuration was 52 | // split into its own `rules.js` module. 53 | module: { 54 | rules 55 | }, 56 | 57 | // Plugins used during bundle processing. Check the `plugins.js` module to 58 | // see which and how plugins are configured. 59 | plugins: plugins(env), 60 | 61 | // Defines the type of source maps written in each compilation mode. 62 | devtool: env === 'development' ? 'eval-source-map' : 'source-map', 63 | 64 | // Turn performance hints off. 65 | performance: { 66 | hints: false 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /gulpfile.esm.js/config/webpack/plugins.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack Plugins 3 | * =============== 4 | * 5 | * Which plugins are used by Webpack to compile the application bundle. 6 | */ 7 | 8 | import webpack from 'webpack'; 9 | import rpu from 'read-pkg-up'; 10 | import HTML from 'html-webpack-plugin'; 11 | import Copy from 'copy-webpack-plugin'; 12 | import {dirs, dest} from '../paths'; 13 | 14 | const {package: pkg} = rpu.sync(); 15 | 16 | // eslint-disable-next-line no-unused-vars 17 | export default (env = 'development') => 18 | [ 19 | // Webpack Define plugin 20 | // --------------------- 21 | // 22 | // Defines global constants at compile time. 23 | // 24 | // Reference: 25 | // - 26 | new webpack.DefinePlugin({ 27 | 'typeof CANVAS_RENDERER': true, 28 | 'typeof WEBGL_RENDERER': true, 29 | 'typeof EXPERIMENTAL': false, 30 | 'typeof FEATURE_SOUND': true, 31 | 'typeof PLUGIN_CAMERA3D': false, 32 | 'typeof PLUGIN_FBINSTANT': false 33 | }), 34 | 35 | // HTML Plugin 36 | // ----------- 37 | // 38 | // Simplifies creation of HTML files to serve Webpack bundles. 39 | // 40 | // Reference: 41 | // - 42 | new HTML({ 43 | title: pkg.title, 44 | description: pkg.description, 45 | template: 'index.html' 46 | }), 47 | 48 | // Copy Plugin 49 | // ----------- 50 | // 51 | // Copies application assets into the bundle. 52 | // 53 | // Reference: 54 | // 55 | new Copy([{ 56 | from: dirs.static, 57 | to: dest 58 | }]) 59 | ].filter(Boolean); 60 | -------------------------------------------------------------------------------- /gulpfile.esm.js/config/webpack/rules.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack Rules 3 | * ============= 4 | * 5 | * How sources are processed by Webpack. 6 | */ 7 | 8 | import {dirs} from '../paths'; 9 | 10 | export default [ 11 | // ESLint 12 | // ------ 13 | // 14 | // Run ESLint over application modules to detect issues. Issues are 15 | // reported in the terminal. 16 | // 17 | // Reference: 18 | // - 19 | { 20 | test: /\.js$/, 21 | include: dirs.scripts, 22 | enforce: 'pre', 23 | loader: 'eslint-loader', 24 | options: { 25 | emitError: true, 26 | emitWarning: true 27 | } 28 | } 29 | ]; 30 | -------------------------------------------------------------------------------- /gulpfile.esm.js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `gulpfile.js` index module 3 | * ========================== 4 | * 5 | * Rather than managing one big file containing multiple Gulp tasks and 6 | * helper functions, the whole collection has been split and organized under 7 | * the `tasks/` directory. User tasks are exposed below. 8 | */ 9 | 10 | // Expose user tasks and make `serve` the default task. 11 | export {clean} from './tasks/clean'; 12 | export {compile} from './tasks/compile'; 13 | export {dist} from './tasks/dist'; 14 | export {serve as default} from './tasks/serve'; 15 | -------------------------------------------------------------------------------- /gulpfile.esm.js/lib/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A module containing a reusable Browsersync instance. 3 | */ 4 | 5 | import browsersync from 'browser-sync'; 6 | 7 | export default browsersync.create(); 8 | -------------------------------------------------------------------------------- /gulpfile.esm.js/lib/webpack-middlewares.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack Middlewares 3 | * =================== 4 | * 5 | * Configures Webpack Middlewares for use with Browsersync. 6 | */ 7 | 8 | import devMiddleware from 'webpack-dev-middleware'; 9 | 10 | export default compiler => [ 11 | devMiddleware(compiler, { 12 | quiet: true, 13 | stats: { 14 | colors: true, 15 | modules: false 16 | } 17 | }) 18 | ]; 19 | -------------------------------------------------------------------------------- /gulpfile.esm.js/lib/webpack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Creates a configured Webpack instance. 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import config from '../config/webpack'; 7 | 8 | export default (env = 'development') => webpack(config(env)); 9 | -------------------------------------------------------------------------------- /gulpfile.esm.js/tasks/clean.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `clean` task 3 | * ============ 4 | * 5 | * Uses 'del' to dispose the contents found in the `dest` directory before 6 | * creating a fresh distribution build. 7 | */ 8 | 9 | import del from 'del'; 10 | import {dest} from '../config/paths'; 11 | 12 | export const clean = () => del([dest]); 13 | clean.description = `Dispose of contents from '${dest}' directory.`; 14 | -------------------------------------------------------------------------------- /gulpfile.esm.js/tasks/compile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `compile` task 3 | * ============== 4 | * 5 | * Compile scripts in production mode using Webpack. 6 | */ 7 | 8 | import log from 'fancy-log'; 9 | import PluginError from 'plugin-error'; 10 | import webpack from '../lib/webpack'; 11 | 12 | export const compile = done => { 13 | return webpack('production').run((err, stats) => { 14 | if (err) { 15 | throw new PluginError('webpack', err); 16 | } 17 | 18 | log.info( 19 | `[webpack]\n${stats.toString({ 20 | colors: true, 21 | modules: false 22 | })}` 23 | ); 24 | done(); 25 | }); 26 | }; 27 | 28 | compile.description = `Compile scripts in production mode using Webpack.`; 29 | -------------------------------------------------------------------------------- /gulpfile.esm.js/tasks/dist.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `dist` task 3 | * =========== 4 | * 5 | * Bundle the application contents for distribution. 6 | */ 7 | 8 | import {series} from 'gulp'; 9 | import {clean} from './clean'; 10 | import {compile} from './compile'; 11 | 12 | export const dist = series(clean, compile); 13 | dist.description = `Bundle application contents for distribution.`; 14 | -------------------------------------------------------------------------------- /gulpfile.esm.js/tasks/serve.js: -------------------------------------------------------------------------------- 1 | /* 2 | * `server` task 3 | * ============= 4 | * 5 | * Creates a Browsersync Web server instance for live development. Makes use 6 | * of some Webpack middlewares to enable live reloading features. 7 | */ 8 | 9 | import config from '../config/browsersync'; 10 | import server from '../lib/server'; 11 | import webpack from '../lib/webpack'; 12 | import webpackMiddlewares from '../lib/webpack-middlewares'; 13 | 14 | export const serve = () => { 15 | const compiler = webpack('development'); 16 | 17 | config.middleware.push(...webpackMiddlewares(compiler)); 18 | 19 | server.init(config); 20 | }; 21 | 22 | serve.description = `Create a Browsersync instance for live development.`; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Phaser 3 Demo Project", 3 | "description": "A Phaser 3 demo project based on the \"Snake Plissken\" tutorial published on Phaser World #85.", 4 | "version": "1.0.0", 5 | "url": "https://github.com/rblopes", 6 | "scripts": { 7 | "clean": "gulp clean", 8 | "dist": "gulp dist", 9 | "format": "eslint \"{app,gulpfile.js}/**/*.js\" --fix", 10 | "lint": "eslint \"{app,gulpfile.js}/**/*.js\"", 11 | "start": "gulp" 12 | }, 13 | "devDependencies": { 14 | "browser-sync": "^2.26.7", 15 | "copy-webpack-plugin": "^5.0.3", 16 | "del": "^5.1.0", 17 | "eslint": "^6.3.0", 18 | "eslint-config-xo": "^0.26.0", 19 | "eslint-loader": "^3.0.0", 20 | "esm": "^3.2.25", 21 | "fancy-log": "^1.3.3", 22 | "gulp": "^4.0.2", 23 | "html-webpack-plugin": "^3.2.0", 24 | "plugin-error": "^1.0.1", 25 | "read-pkg-up": "^6.0.0", 26 | "webpack": "^4.39.3", 27 | "webpack-dev-middleware": "^3.7.1" 28 | }, 29 | "dependencies": { 30 | "phaser": "^3.19.0" 31 | }, 32 | "esm": { 33 | "mode": "all" 34 | }, 35 | "private": true 36 | } 37 | -------------------------------------------------------------------------------- /screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/screenshot-1.png -------------------------------------------------------------------------------- /screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rblopes/phaser-3-snake-game/9ed12795829b59f263b9f68ebe5e58822acd8c59/screenshot-2.png --------------------------------------------------------------------------------