# Phaser 3 Cheatsheet Originally inspired by woubuc/phaser-cheatsheet.md, this version has been almost completely rewritten and expanded with new notes and insights to better support everyone learning and using Phaser 3. ## Starting a new game All config is optional, but width, height and scene are recommended to add. ```jsx let config = { type: Phaser.AUTO, width: 1200, height: 800, scene: [ GameScene, EndScene ], parent: 'game-wrapper', //
physics: { default: 'arcade', arcade: { debug: true, gravity: { y: 50 } } }, scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH }, backgroundColor: '#000000', //Set canvas background transparent: true, //Set canvas to transparent pixelArt: true, //If required to build a pixel art game banner: false, // Disables the Phaser banner from console }; let game = new Phaser.Game(config); ``` ## Creating a game state object ```jsx class GameScene extends Phaser.Scene { constructor() { super({key: 'GameScene', active: true}); // In PauseScene should be: {key: 'PauseScene', active: false} }, init(data) { // init() is not necessary // Get the passed data from other scene } preload() { // Assets to be loaded before create() is called }, create() { // Adding sprites, sounds, etc... this.customFunc(); //Try customFunc.call(this) when this.func() not working }, update() { // Keep update on everytick // Game logic, collision, movement, etc... } customFunc() { // Create your custom function } } ``` ### Working with globals Reference: https://www.stephengarside.co.uk/blog/phaser-3-custom-config-global-letiables/ ```jsx let config = { type: Phaser.AUTO, width: 400, height: 300, ~~myCustomProperty: null,~~ // No need to set here }; let game = new Phaser.Game(config); game.config.myCustomProperty = true; //Just set in here console.log(game.config.myCustomProperty); ``` ### Fullscreen Reference: https://docs.phaser.io/phaser/concepts/scale-manager#full-screen ```jsx // Enter full screen mode scene.scale.startFullscreen(); // Exit full screen mode scene.scale.stopFullscreen(); // Toggle full screen mode scene.scale.toggleFullscreen(); // Check if current game is fullscreen mode let isFullscreen = scene.scale.isFullscreen; ``` ## Basic Usage ### Loading an image/music/asset ```jsx preload(){ /* Optional: set a custom path */ this.load.path = 'assets/images/'; this.load.image('background', 'background.png'); this.load.audio("bgm", ["bgm.mp3"]); this.load.spritesheet("guy", "guy-walk.png", { frameWidth: 50, frameHeight: 50 }); /* Preloading */ this.load.on('progress', function (value) { console.log(`Loading: ${parseInt(value*100)}%`); }); /* Preloading (With loading bar) */ let progressX = this.game.config.width / 2; let progressY = this.game.config.height / 2; let progressWidth = this.game.config.width * 0.8; let progressHeight = 30; let progressBox = this.add.rectangle(progressX, progressY, progressWidth, progressHeight, 0xffffff, 0.5).setOrigin(0.5, 0.5); let progressBar = this.add.rectangle(progressX, progressY, progressWidth, progressHeight, 0x00A758, 1).setOrigin(0.5, 0.5); this.load.on('progress', function (value) { progressBar.width = progressWidth * value; }); this.load.on('complete', function (value) { progressBar.destroy(); progressBox.destroy(); }); } ``` ### Adding an image **Static image** ```jsx preload() { this.load.image('background', 'background.png'); this.load.image('background_new', 'background_new.png'); create() { this.background = this.add.image(x, y, 'background'); // Change the texture this.background.setTexture('background_new'); } ``` **SVG** ```jsx preload() { this.load.svg('fireflower', 'https://cdn.phaserfiles.com/v355/assets/svg/fireflower.svg') } create() { this.add.image(150, 150, 'fireflower').setScale(0.3).setOrigin(0); } ``` ### Adding tileSprite / looping background ```jsx create() { this.background = this.add.tileSprite(0, 0, game.config.width, game.config.height, 'background').setOrigin(0, 0); } update() { this.background.tilePositionX -= 1; } ``` ### Adding text Reference: https://rexrainbow.github.io/phaser3-rex-notes/docs/site/text/#add-text-object ```jsx function create() { //Assigned for later use this.label = this.add.text(x, y, "John Doe", { fontSize: '12px', fontFamily: 'Arial, Sans-serif', color: '#CE721C', backgroundColor: '#F8D38A', padding: { left: 10, right: 50, top: 5, bottom: 5 }, stroke: '#000', strokeThickness: 5, align: 'center', lineSpacing: 20, //line-height wordWrap: { width: 450, useAdvancedWrap: true }, shadowColor: '#000', shadowOffset: { x: 10, y: 10 }, shadowBlur: 5, }); this.label.text = "I'm changing the text \n inside the label letiable!"; //Set text with multiple lines no need "\n' this.label.setText([ `Correct: ${this.correct}`, `Wrong: ${this.wrong}`, `Score: ${this.score}`, ]); //Set styles this.label.setShadow(3, 3, 'rgba(0,0,0,0.5)', 2); //Set letter spacing in 5px this.label.setLetterSpacing(5); //Word wrap when greater than 100px width this.label.setWordWrapWidth(100, false); //Set Origin this.label.setOrigin(1, 0); //Set Depth (z-index) this.label.setDepth(1); //Set visibility this.label.setVisible(false); //Center the text let screenCenterX = this.cameras.main.worldView.x + this.cameras.main.width / 2; let screenCenterY = this.cameras.main.worldView.y + this.cameras.main.height / 2; this.txt = this.add.text(screenCenterX, screenCenterY, "My Text").setOrigin(0.5); } ``` ### Adding Custom Font **Method 1 - Works with Phaser [v3.87](https://github.com/phaserjs/phaser/discussions/6943) or later** ```jsx preload() { this.load.font('Lato', 'assets/fonts/Lato.otf', 'opentype'); this.load.font('Roboto', 'assets/fonts/Roboto.ttf', 'truetype'); } create() { this.add.text(0, 0, 'Your Font Here', { fontFamily: 'Lato', fontSize: '32px', }); this.add.text(0, 80, 'Your Font Here', { fontFamily: 'Roboto', fontSize: 32, }); } ``` **Method 2 - (Legacy)** ```css /* style.css */ @font-face { font-family: "Roboto"; src: url('../font/Roboto.ttf'); } ``` ```html /* HTML */ ``` ```jsx this.add.text(0, 0, "Your Text Here", { fontFamily: 'Roboto' }); ``` ### Adding spritesheet animation **Load with PNG spritesheet (Recommend) - [Example 1](https://labs.phaser.io/edit.html?src=src\animation\create%20animation%20from%20sprite%20sheet.js), [Example 2](https://codesandbox.io/p/sandbox/sprite-sheet-animation-using-phaser-3-ui2w0?file=%2Findex.html%3A24%2C9)** ```jsx preload() { this.load.spritesheet("guy", "guy-walk.png", { frameWidth: 50, frameHeight: 50 }); } create() { this.anims.create({ key: "guyWalking", frames: this.anims.generateFrameNumbers("guy", { frames: [ 0, 1, 2, 3 ] }), // frames: this.anims.generateFrameNumbers("guy", { // start: 0, // end: 8 // }), frameRate: 12, repeat: -1 }); this.player = this.add.sprite(x, y, "guy"); this.player.play("guyWalking"); this.player.body.setSize(40, 40); // for better size adjustment (remove space) this.player.body.setOffset(5, 5); // for better size adjustment (remove space) } ``` **Load with PNG sequence -** [Example](https://labs.phaser.io/edit.html?src=src\\animation\\animation%20from%20png%20sequence.js) ```jsx preload() { /* Preload all image sequences */ this.load.image('explosion1', 'explosion1.png'); this.load.image('explosion2', 'explosion2.png'); this.load.image('explosion3', 'explosion3.png'); } create() { /* Create animation with all image sequences */ this.anims.create({ key: 'explosion', frames: [ { key: 'explosion1' }, { key: 'explosion2' }, { key: 'explosion3', duration: 50 } // You can set duration for each frame ], frameRate: 8, repeat: 1 }); /* Create object and play animation */ this.explosion = this.add.sprite(0, 0, 'explosion1'); this.explosion.play('explosion'); //Pause this.explosion.anims.pause(); } ``` **Add animation with atlas - [Generator](https://gammafp.com/tool/atlas-packer/)** ```jsx preload() { // Load the texture atlas this.load.atlas('myAtlas', 'atlas.png', 'atlas.json'); } create() { // Apply animation from the atlas const animationConfig = { key: 'walking', frames: this.anims.generateFrameNames('myAtlas', { prefix: 'walking_', start: 0, end: 5, zeroPad: 2 }), frameRate: 10, repeat: -1 }; this.anims.create(animationConfig); this.player = this.physics.add.sprite(400, 300, 'myAtlas', 'walking_00'); this.player.anims.play('walking'); } ``` ## Basic Styles ### Set Alpha(opacity) & Visible ```jsx this.player.visible = false; this.player.alpha = 0.5; ``` ### Change z-index / Bring to front Reference: https://phaser.io/examples/v3/view/depth-sorting/z-index ```jsx /* Bring object to front */ this.player.setDepth(1); ``` ## Transform Objects ### Objects origin (anchor) Reference: https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Components.Origin.html ```jsx //Objects have an origin property that goes from 0 (top left) to 1 (bottom right) //It is default in centerX(0.5) centerY(0.5) image.originX = 0.2; image.originY = 1; //This sets it in the 0 image.setOrigin(0, 0); ``` ### Scaling an object ```jsx //Objects have a scale property that defaults to 1 //Negative values essentially mirror it on the affected axis image.setScale(1.2, 1.2) ``` ### Rotate an object ```jsx //Objects rotation defaults is 0, but you can use Math to convert from degree image.rotation = 0; image.rotation = Phaser.Math.DegToRad(90); ``` ### Flipping an object Reference: https://phasergames.com/how-to-flip-a-sprite-in-phaser-3/ ```jsx let duck1 = this.add.image(150, 150, "duck"); let duck2 = this.add.image(350, 150, "duck"); duck2.flipX = true; gameObject.setFlip(true, false); gameObject.toggleFlipX(); gameObject.toggleFlipY(); gameObject.resetFlip(); ``` ## Animations ### Tweening You may learn about the [Ease functions](https://rexrainbow.github.io/phaser3-rex-notes/docs/site/ease-function/) ****such as `"Bounce"`, `"Back"`, `"Power3"` ```jsx // Adding an example sprite but you can tween pretty much anything this.player = game.add.sprite(100, 100, 'player'); this.tweens.add({ targets: this.player, x: 200, y: 200, scaleX: 0.8, scaleY: 0.8, //alpha: { from: 0, to: 1 }, //Use from to alpha: Phaser.Math.FloatBetween(0, 1), duration: Phaser.Math.Between(1500, 3000), ease: 'Sine.easeInOut', //repeat: 0, // -1=infinity; 0=no loop; 1=loop once; 2=loop twice... //repeatDelay: 0, // Add delay between repeats //yoyo: true, //paused: true }); // Tween for multiple objects this.tweens.add({ targets: [this.player, this.enemies, this.boss], alpha: 0, duration: 1500, }); ``` **Create a chain animation** ```jsx // Create Chain let myAnimation = this.tweens.chain({ targets: this.player, tweens: [ { angle: 0, scale: 0.5, duration: 500, onStart() { console.log('start 1')}, onComplete() { console.log('complete 1')} }, { angle: 360, scale: 1, ease: 'Power2', duration: 500, onStart() { console.log('start 2')}, onComplete() { console.log('complete 2')} } ], repeat: -1, // Set -1 for infinite loop delay: 5000 // Start animation after 5 seconds }); myAnimation.stop(); // Stop the animation ``` **Create a Timeline** ```jsx // Create Timeline let timeline = this.add.timeline([ { at: 0, // At 0 second tween: { targets: this.player, y: 400, duration: 2000, } }, { at: 2500, // At 2.5 seconds run() { this.player.setScale(1.5); } }, ]); timeline.play(); // Start playing the animation timeline.repeat().play(); // Play with repeat (loop) timeline.repeat(false); // Stop repeat // Create Timeline (Version 2) let timeline = this.add.timeline(); timeline.add({ targets: this.player, y: 400, duration: 300, ease: 'Linear', onComplete: () => { }, }); ``` **Number animation example** ```jsx let scoreText = this.add.text(400, 300, '0', { fontSize: '32px', color: '#ffffff', }); this.tweens.addCounter({ from: 0, to: 1200, duration: 3000, ease: 'Cubic.easeOut', onUpdate: (tween) => { const value = Math.round(tween.getValue()); //scoreText.setText(value); scoreText.setText(value.toLocaleString('en-US')); } }); ``` ### Use of shader (preFX / postFX) **Examples - [Online Notes](https://rexrainbow.github.io/phaser3-rex-notes/docs/site/shader-builtin/)** ```jsx /* NOTES: preFX only works with sprite */ this.player= this.add.sprite(0, 0, 'player') this.player.preFX.addShine( 0.5, // speed 0.5, // gradient position (0-1) 0.7, // width of the shine 0.6 // alpha ); this.player.preFX.addBlur( 4, // blur quality (1-8) 2.0 // blur strength ); this.player.preFX.addBloom( 0xff0000, // color 4, // outerStrength 0, // innerStrength false // knockout (Hide object but show only style) ); this.player.preFX.addGlow( 0xff0000, // color 0, // offset x 0, // offset y 2, // distance/strength 1, // alpha 16 // quality (steps) ); this.player.preFX.addShadow( 1, // x offset power, color, samples, intensity 1, // y offset 0.1, // decay (blur amount) 1, // power 0x000000, // color 12, // sample steps (quality) 1, //intensity ); this.player.preFX.addPixelate( 4 // amount (pixel size, higher = more pixelated) ); this.player.preFX.addVignette( 0.5, // x (horizontal position, 0-1) 0.5, // y (vertical position, 0-1) 0.7, // radius (size of the clear center area) 0.5, // strength (intensity of the effect) 0x000000 // color of the vignette ); this.player.preFX.addGradient( 0xff0000, // color1 0x00ff00, // color2 1, // alpha 0, // fromX 0, // fromY 200, // toX 200, // toY 0, // size (Low value would be smoother) ); this.player.preFX.addCircle( 0.5, // x (horizontal position, 0-1) 0.5, // y (vertical position, 0-1) 0.5, // radius 0x000000, // color (outside circle) 0 // alpha (0 = transparent, 1 = opaque) ); this.player.preFX.addBokeh( 3, // radius 1, // amount 0.1, // blend (0-1) 0xffffff // color ); ``` **Tweening the effects** ```jsx create() { let glowFX = this.player.preFX.addGlow(); //glowFX.setActive(false); //glowFX.setActive(true); glowFX.outerStrength = 8; glowFX.innerStrength = 8; this.tweens.add({ // you may also tween the glow effects targets: glowFX, outerStrength: 16, innerStrength: 16, ease: 'Sine.easeInOut', repeat: -1, yoyo: true, duration: 1500, }); } ``` **Displacement map** ```jsx preload() { // Load your displacement map (usually a black and white texture) this.load.image('displacement', 'displacement_map.png'); this.load.image('background', 'background.png'); } create() { const image = this.add.image(400, 300, 'background'); // Add displacement effect const fx = image.preFX.addDisplacement( 'displacement', // displacement map key 2.0, // intensity x 2.0 // intensity y ); this.tweens.add({ // Animate the displacement targets: fx, x: 100, // offset x y: 100, // offset y duration: 2000, yoyo: true, repeat: -1 }); } ``` ### Particles effects ```jsx preload() { this.load.image('bg', 'flares.png'); } create() { let emitter = this.add.particles(300, 200, 'flares', { frame: 'white', blendMode: 'ADD', lifespan: 1200, gravityY: -100, scale: { start: 0.3, end: 0 }, emitting: false }); emitter.start(); emitter.addEmitZone({ source: new Phaser.Geom.Circle(0, -20, 90) }); } ``` Use Case (Play once only) ```jsx preload () { this.load.image('particle', 'assets/img/particle_shape.png'); } create () { const particles = this.add.particles(100, 100, 'particle', { speed: { min: 100, max: 200 }, angle: { min: 0, max: 360 }, scale: { start: 1.5, end: 0 }, blendMode: 'NORMAL', lifespan: 500, gravityY: 50, quantity: 2, }); // Stop emitting after 100ms this.time.delayedCall(100, () => { particles.stop(); }); // Destroy the particle system after 2 seconds this.time.delayedCall(2000, () => { particles.destroy(); }); } ``` ## Sounds ### Playing music ```jsx preload() { this.bgm = this.load.audio("bgm", ["bgm.mp3"]); } create() { // Assign it so we can reference it this.bgm = this.sound.add("bgm", { loop: false }); this.bgm.loop = true; this.bgm.play(); } ``` ### Muted Sounds ```jsx create() { // Mute all sounds this.game.sound.mute = true; // Set volumn of a soundtrack this.bgm.volume = 0.5; } ``` ## User Inputs ### Set custom cursor ```jsx create(){ this.input.setDefaultCursor('url(img/cursor.cur), pointer'); } ``` ### Mouse & touch input ```jsx create(){ /* Set Multitouch limits (default: 1) */ this.input.addPointer(3); //3 multitouch /* Onclick event */ this.player.setInteractive(); this.player.on('pointerup', function(){ console.log('Clicked'); }); /* Disable events */ this.player.disableInteractive(); /* Custom clickable area */ this.player.setInteractive({ hitArea: new Phaser.Geom.Rectangle(-50, -60, 220, 360), hitAreaCallback: Phaser.Geom.Rectangle.Contains, useHandCursor: true, }); /* All other input properties */ this.player.setInteractive({ draggable: true, cursor: 'pointer', pixelPerfect: true, useHandCursor: true, hitArea: new Phaser.Geom.Rectangle(0, 0, 100, 100), dragStartCallback: function() { // Code to execute when drag starts }, dragEndCallback: function() { // Code to execute when drag ends } }); } ``` Example of Drag and Drop ```jsx create(){ const button = this.add.image(200, 100, 'button'); button.setInteractive({ draggable: true }) button.on('dragstart', function (pointer) { this.setTint(0xc1c1c1); }); button.on('drag', function (pointer, dragX, dragY) { console.log('drag', dragX, dragY) this.x = dragX; this.y = dragY; }); button.on('dragend', function (pointer) { this.clearTint(); }); } ``` ### Keyboard input ```jsx create() { /* Onclick event */ this.keyboard = this.input.keyboard.createCursorKeys(); } update() { if(this.keyboard.left.isDown){ this.player.x += -2; } if(this.keyboard.right.isDown){ this.player.x += 2; } if(this.keyboard.up.isDown){ this.player.y += -2; } if(this.keyboard.down.isDown){ this.player.y += 2; } } ``` ```jsx /* Logic */ this.input.keyboard.on('keydown-W', () => { // Event listener pattern: "keydown + {KEY}" }); /* Example */ this.input.keyboard.on('keydown-SPACE', () => { // Space key-down events }); this.input.keyboard.on('keyup-SPACE', () => { // Space key-up events }); ``` ## Add HTML into the game ### Adding DOM Elements - [Demo](https://labs.phaser.io/edit.html?src=src\game%20objects\dom%20element\css%20text.js) ```jsx const config = { type: Phaser.AUTO, width: 800, height: 600, parent: 'game-wrapper', // Must have a parent dom: { // Enable DOM plugin createContainer: true }, scene: MainScene }; // How to use create() { let heading = this.add.dom(400, 300).createFromHTML(`