├── .gitignore ├── favicon.ico ├── assets └── images │ ├── logo.png │ ├── quit.png │ ├── sound.png │ ├── button.png │ ├── fullscreen.png │ ├── 540x960-guide.png │ └── 640x960-guide.png ├── README.md ├── utils ├── buttons.js └── screen.js ├── package.json ├── index.html ├── scenes ├── title.js ├── handler.js ├── preload.js └── hub.js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/favicon.ico -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/quit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/assets/images/quit.png -------------------------------------------------------------------------------- /assets/images/sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/assets/images/sound.png -------------------------------------------------------------------------------- /assets/images/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/assets/images/button.png -------------------------------------------------------------------------------- /assets/images/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/assets/images/fullscreen.png -------------------------------------------------------------------------------- /assets/images/540x960-guide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/assets/images/540x960-guide.png -------------------------------------------------------------------------------- /assets/images/640x960-guide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimozurdo/mobile-game-base-phaser3/HEAD/assets/images/640x960-guide.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mobile game with phaser 3 2 | Base project for make a mobile game using Phaser 3 3 | 4 | [![image](https://res.cloudinary.com/shimozurdo/image/upload/v1604285968/markdown/mobile_bc8e0a.gif)]() 5 | 6 | If you use this code in one of your games and you need to convert to an old javascript version using babel and webpack, check out my other game project. 7 | 8 | https://github.com/shimozurdo/math-attack 9 | 10 | -------------------------------------------------------------------------------- /utils/buttons.js: -------------------------------------------------------------------------------- 1 | function pointerOver(gameObjet, hex = 0xEFF0F1) { 2 | gameObjet.on('pointerover', function () { 3 | this.setTint(hex); 4 | }); 5 | pointerOut(gameObjet); 6 | } 7 | 8 | function pointerOut(gameObjet) { 9 | gameObjet.on('pointerout', function () { 10 | this.clearTint(); 11 | }); 12 | } 13 | 14 | function pointerUp(res = () => { }, gameObjet) { 15 | gameObjet.on('pointerup', () => { 16 | res(); 17 | }); 18 | } 19 | 20 | export { 21 | pointerOver, 22 | pointerOut, 23 | pointerUp 24 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobile-game-base-phaser3", 3 | "version": "1.0.0", 4 | "description": "Base project for make a mobile game using Phaser 3", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "live-server --open=index.html", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/shimozurdo/mobile-game-base-phaser3.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/shimozurdo/mobile-game-base-phaser3/issues" 19 | }, 20 | "homepage": "https://github.com/shimozurdo/mobile-game-base-phaser3#readme", 21 | "dependencies": { 22 | "live-server": "^1.2.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 22 | Game 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /scenes/title.js: -------------------------------------------------------------------------------- 1 | export default class Title extends Phaser.Scene { 2 | 3 | // Vars 4 | handlerScene = false 5 | sceneStopped = false 6 | 7 | constructor() { 8 | super({ key: 'title' }) 9 | } 10 | 11 | preload() { 12 | this.sceneStopped = false 13 | this.width = this.game.screenBaseSize.width 14 | this.height = this.game.screenBaseSize.height 15 | this.handlerScene = this.scene.get('handler') 16 | this.handlerScene.sceneRunning = 'title' 17 | } 18 | 19 | create() { 20 | const { width, height } = this 21 | // CONFIG SCENE 22 | this.handlerScene.updateResize(this) 23 | if (this.game.debugMode) 24 | this.add.image(0, 0, 'guide').setOrigin(0).setDepth(1) 25 | // CONFIG SCENE 26 | 27 | // GAME OBJECTS 28 | this.playBtn = this.add.image(width / 2, height / 2, 'button').setOrigin(.5) 29 | // GAME OBJECTS 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /utils/screen.js: -------------------------------------------------------------------------------- 1 | function fullScreen() { 2 | this.scale.fullscreenTarget = document.getElementById('game') 3 | let F11Key = this.input.keyboard.addKey('F11') 4 | F11Key.on('down', () => { 5 | if (this.scale.isFullscreen) { 6 | this.scale.stopFullscreen() 7 | console.log('Stop fullscreen') 8 | } 9 | else { 10 | this.scale.startFullscreen() 11 | console.log('Start fullscreen') 12 | } 13 | }) 14 | 15 | document.addEventListener('fullscreenchange', exitHandler) 16 | document.addEventListener('webkitfullscreenchange', exitHandler) 17 | document.addEventListener('mozfullscreenchange', exitHandler) 18 | document.addEventListener('MSFullscreenChange', exitHandler) 19 | 20 | function exitHandler() { 21 | if (!document.fullscreenElement && !document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement) { 22 | console.log('Catch key escape event') 23 | } 24 | } 25 | } 26 | 27 | export { 28 | fullScreen 29 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import Handler from './scenes/handler.js' 2 | import Title from './scenes/title.js' 3 | import Preload from './scenes/preload.js' 4 | import Hub from './scenes/hub.js' 5 | 6 | // Aspect Ratio 16:9 - Portrait 7 | const MAX_SIZE_WIDTH_SCREEN = 1920 8 | const MAX_SIZE_HEIGHT_SCREEN = 1080 9 | const MIN_SIZE_WIDTH_SCREEN = 270 10 | const MIN_SIZE_HEIGHT_SCREEN = 480 11 | const SIZE_WIDTH_SCREEN = 540 12 | const SIZE_HEIGHT_SCREEN = 960 13 | 14 | const config = { 15 | type: Phaser.AUTO, 16 | scale: { 17 | mode: Phaser.Scale.RESIZE, 18 | parent: 'game', 19 | width: SIZE_WIDTH_SCREEN, 20 | height: SIZE_HEIGHT_SCREEN, 21 | min: { 22 | width: MIN_SIZE_WIDTH_SCREEN, 23 | height: MIN_SIZE_HEIGHT_SCREEN 24 | }, 25 | max: { 26 | width: MAX_SIZE_WIDTH_SCREEN, 27 | height: MAX_SIZE_HEIGHT_SCREEN 28 | } 29 | }, 30 | dom: { 31 | createContainer: true 32 | }, 33 | scene: [Handler, Hub, Preload, Title] 34 | 35 | } 36 | 37 | const game = new Phaser.Game(config) 38 | 39 | // Global 40 | game.debugMode = true 41 | game.embedded = false // game is embedded into a html iframe/object 42 | 43 | game.screenBaseSize = { 44 | maxWidth: MAX_SIZE_WIDTH_SCREEN, 45 | maxHeight: MAX_SIZE_HEIGHT_SCREEN, 46 | minWidth: MIN_SIZE_WIDTH_SCREEN, 47 | minHeight: MIN_SIZE_HEIGHT_SCREEN, 48 | width: SIZE_WIDTH_SCREEN, 49 | height: SIZE_HEIGHT_SCREEN 50 | } 51 | 52 | game.orientation = "portrait" -------------------------------------------------------------------------------- /scenes/handler.js: -------------------------------------------------------------------------------- 1 | export default class Handler extends Phaser.Scene { 2 | 3 | // Vars 4 | sceneRunning = null 5 | 6 | constructor() { 7 | super('handler') 8 | } 9 | 10 | create() { 11 | this.cameras.main.setBackgroundColor('#FFF') 12 | this.launchScene('preload') 13 | this.launchScene('hub') 14 | } 15 | 16 | launchScene(scene, data) { 17 | this.scene.launch(scene, data) 18 | this.gameScene = this.scene.get(scene) 19 | } 20 | 21 | updateResize(scene) { 22 | scene.scale.on('resize', this.resize, scene) 23 | 24 | const scaleWidth = scene.scale.gameSize.width 25 | const scaleHeight = scene.scale.gameSize.height 26 | 27 | scene.parent = new Phaser.Structs.Size(scaleWidth, scaleHeight) 28 | scene.sizer = new Phaser.Structs.Size(scene.width, scene.height, Phaser.Structs.Size.FIT, scene.parent) 29 | 30 | scene.parent.setSize(scaleWidth, scaleHeight) 31 | scene.sizer.setSize(scaleWidth, scaleHeight) 32 | 33 | this.updateCamera(scene) 34 | } 35 | 36 | resize(gameSize) { 37 | // 'this' means to the current scene that is running 38 | if (!this.sceneStopped) { 39 | const width = gameSize.width 40 | const height = gameSize.height 41 | 42 | this.parent.setSize(width, height) 43 | this.sizer.setSize(width, height) 44 | 45 | // updateCamera - TO DO: Improve the next code because it is duplicated 46 | const camera = this.cameras.main 47 | const scaleX = this.sizer.width / this.game.screenBaseSize.width 48 | const scaleY = this.sizer.height / this.game.screenBaseSize.height 49 | 50 | camera.setZoom(Math.max(scaleX, scaleY)) 51 | camera.centerOn(this.game.screenBaseSize.width / 2, this.game.screenBaseSize.height / 2) 52 | } 53 | } 54 | 55 | updateCamera(scene) { 56 | const camera = scene.cameras.main 57 | const scaleX = scene.sizer.width / this.game.screenBaseSize.width 58 | const scaleY = scene.sizer.height / this.game.screenBaseSize.height 59 | 60 | camera.setZoom(Math.max(scaleX, scaleY)) 61 | camera.centerOn(this.game.screenBaseSize.width / 2, this.game.screenBaseSize.height / 2) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /scenes/preload.js: -------------------------------------------------------------------------------- 1 | export default class Preload extends Phaser.Scene { 2 | 3 | width = null 4 | height = null 5 | handlerScene = null 6 | sceneStopped = false 7 | 8 | constructor() { 9 | super({ key: 'preload' }) 10 | } 11 | 12 | preload() { 13 | // Images 14 | this.load.image('logo', 'assets/images/logo.png') 15 | this.load.image('guide', 'assets/images/540x960-guide.png') 16 | this.load.image('button', 'assets/images/button.png') 17 | //----------------------------------------------------------------------> 18 | this.canvasWidth = this.sys.game.canvas.width 19 | this.canvasHeight = this.sys.game.canvas.height 20 | 21 | this.width = this.game.screenBaseSize.width 22 | this.height = this.game.screenBaseSize.height 23 | 24 | this.handlerScene = this.scene.get('handler') 25 | this.handlerScene.sceneRunning = 'preload' 26 | this.sceneStopped = false 27 | 28 | let progressBox = this.add.graphics() 29 | progressBox.fillStyle(0x000, 0.8) 30 | progressBox.fillRect((this.canvasWidth / 2) - (210 / 2), (this.canvasHeight / 2) - 5, 210, 30) 31 | let progressBar = this.add.graphics() 32 | 33 | this.load.on('progress', (value) => { 34 | progressBar.clear() 35 | progressBar.fillStyle(0xFF5758, 1) 36 | progressBar.fillRect((this.canvasWidth / 2) - (200 / 2), (this.canvasHeight / 2), 200 * value, 20) 37 | }) 38 | 39 | this.load.on('complete', () => { 40 | progressBar.destroy() 41 | progressBox.destroy() 42 | this.time.addEvent({ 43 | delay: this.game.debugMode ? 3000 : 4000, 44 | callback: () => { 45 | this.sceneStopped = true 46 | this.scene.stop('preload') 47 | this.handlerScene.cameras.main.setBackgroundColor("#020079") 48 | this.handlerScene.launchScene('title') 49 | }, 50 | loop: false 51 | }) 52 | }) 53 | } 54 | 55 | create() { 56 | const { width, height } = this 57 | // CONFIG SCENE 58 | this.handlerScene.updateResize(this) 59 | if (this.game.debugMode) 60 | this.add.image(0, 0, 'guide').setOrigin(0).setDepth(1) 61 | // CONFIG SCENE 62 | 63 | // GAME OBJECTS 64 | this.add.image(width / 2, height / 2, 'logo').setOrigin(.5) 65 | // GAME OBJECTS 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /scenes/hub.js: -------------------------------------------------------------------------------- 1 | import { fullScreen } from '../utils/screen.js' 2 | import { pointerUp } from '../utils/buttons.js' 3 | 4 | export default class Hub extends Phaser.Scene { 5 | 6 | // Vars 7 | handlerScene = null 8 | 9 | constructor() { 10 | super('hub') 11 | } 12 | 13 | preload() { 14 | // Images 15 | this.load.image('quit', 'assets/images/quit.png') 16 | this.load.spritesheet('fullscreen', 'assets/images/fullscreen.png', { frameWidth: 48, frameHeight: 48 }) 17 | this.load.spritesheet("sound", "assets/images/sound.png", { frameWidth: 48, frameHeight: 48 }) 18 | //----------------------------------------------------------------------> 19 | this.canvasWidth = this.sys.game.canvas.width 20 | this.canvasHeight = this.sys.game.canvas.height 21 | this.handlerScene = this.scene.get('handler') 22 | //Orientation 23 | this.scale.lockOrientation(this.game.orientation) 24 | 25 | // Bindings 26 | this.pointerUp = pointerUp.bind(this) 27 | if (!this.game.embedded) 28 | fullScreen.call(this) 29 | this.creditsTxt = this.add.text(this.canvasWidth / 2, this.canvasHeight - 22, 'Shimozurdo Games 2021', { fontFamily: 'Arial', fontSize: '18px', color: '#000', }).setOrigin(.5).setDepth(1) 30 | } 31 | 32 | create() { 33 | 34 | let posItemHubBase = 32 35 | this.quitBtn = this.add.image(posItemHubBase, posItemHubBase, "quit").setOrigin(.5).setDepth(1).setInteractive({ cursor: "pointer" }) 36 | this.quitBtn.visible = false 37 | 38 | this.pointerUp(() => { 39 | this.clickBackScene(this.handlerScene.sceneRunning) 40 | }, this.quitBtn) 41 | 42 | let multiplePosY = this.game.embedded ? 1 : 3 43 | this.soundBtn = this.add.image(this.canvasWidth - posItemHubBase, posItemHubBase * multiplePosY, "sound").setOrigin(.5).setDepth(1).setInteractive({ cursor: "pointer" }) 44 | this.soundBtn.visible = false 45 | 46 | if (this.game.debugMode) { 47 | 48 | this.soundBtn.setFrame(1) 49 | } else { 50 | //this.music.play() 51 | this.soundBtn.setFrame(0) 52 | } 53 | 54 | this.soundBtn.on("pointerup", () => { 55 | if (this.soundBtn.frame.name === 0) { 56 | this.soundBtn.setFrame(1) 57 | 58 | } 59 | else { 60 | this.soundBtn.setFrame(0) 61 | 62 | } 63 | }) 64 | 65 | if (!this.game.embedded) { 66 | multiplePosY = this.game.embedded ? 3 : 1 67 | this.fullscreenBtn = this.add.image(this.canvasWidth - posItemHubBase, posItemHubBase * multiplePosY, "fullscreen", 0).setOrigin(.5).setDepth(1).setInteractive({ cursor: "pointer" }) 68 | 69 | this.fullscreenBtn.on("pointerup", () => { 70 | if (this.scale.isFullscreen) { 71 | this.fullscreenBtn.setFrame(0) 72 | this.scale.stopFullscreen() 73 | } 74 | else { 75 | this.fullscreenBtn.setFrame(1) 76 | this.scale.startFullscreen() 77 | } 78 | }) 79 | } 80 | this.scale.on("resize", this.resize, this) 81 | } 82 | 83 | 84 | update() { 85 | if (this.handlerScene.sceneRunning === 'title') { 86 | this.soundBtn.visible = true 87 | this.quitBtn.visible = false 88 | this.creditsTxt.visible = false 89 | } 90 | } 91 | 92 | clickBackScene(sceneTxt) { 93 | const scene = this.scene.get(sceneTxt) 94 | let gotoScene 95 | let bgColorScene 96 | 97 | switch (sceneTxt) { 98 | case "title": 99 | this.creditsTxt.visible = false 100 | return 101 | } 102 | 103 | scene.sceneStopped = true 104 | scene.scene.stop(sceneTxt) 105 | this.handlerScene.cameras.main.setBackgroundColor(bgColorScene) 106 | this.handlerScene.launchScene(gotoScene) 107 | } 108 | 109 | resize() { 110 | if (!this.game.embedded) 111 | this.fullscreenBtn.x = this.scale.gameSize.width - 30 112 | this.soundBtn.x = this.scale.gameSize.width - 30 113 | this.creditsTxt.x = this.scale.gameSize.width / 2 114 | this.creditsTxt.y = this.scale.gameSize.height - 30 115 | } 116 | } --------------------------------------------------------------------------------