Build a HTML5 endless runner with Phaser in a few lines of code using Arcade physics and featuring object pooling
Talking about Endless Runner game, Game development, HTML5, Javascript and Phaser.
Did you like the way you can build endless runner with Unity and the Infinite Runner Engine?
Today we’ll see how to build a basic HTML5 endless runner using Phaser 3, Arcade physics and object pooling. Thanks to object pooling, you won’t need to create more than 5 or 6 platforms during the entire game, although it seems they are infinite.
Moreover, like all endless runners, the player is not running, it’s the entire environement which moves towards the player, giving the feeling the player is running.
Have a look at the result:
Click or tap to jump. You can also double jump and you can set the game to make any number of jumps. Fall down and it’s game over.
This is the source code, with some comments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | let game; // global game options let gameOptions = { platformStartSpeed: 350, spawnRange: [100, 350], platformSizeRange: [50, 250], playerGravity: 900, jumpForce: 400, playerStartPosition: 200, jumps: 2 } window.onload = function () { // object containing configuration options let gameConfig = { type: Phaser.AUTO, width: 1334, height: 750, scene: playGame, backgroundColor: 0x444444, // physics settings physics: { default : "arcade" } } game = new Phaser.Game(gameConfig); window.focus(); resize(); window.addEventListener( "resize" , resize, false ); } // playGame scene class playGame extends Phaser.Scene{ constructor(){ super ( "PlayGame" ); } preload(){ this .load.image( "platform" , "platform.png" ); this .load.image( "player" , "player.png" ); } create(){ // group with all active platforms. this .platformGroup = this .add.group({ // once a platform is removed, it's added to the pool removeCallback: function (platform){ platform.scene.platformPool.add(platform) } }); // pool this .platformPool = this .add.group({ // once a platform is removed from the pool, it's added to the active platforms group removeCallback: function (platform){ platform.scene.platformGroup.add(platform) } }); // number of consecutive jumps made by the player this .playerJumps = 0; // adding a platform to the game, the arguments are platform width and x position this .addPlatform(game.config.width, game.config.width / 2); // adding the player; this .player = this .physics.add.sprite(gameOptions.playerStartPosition, game.config.height / 2, "player" ); this .player.setGravityY(gameOptions.playerGravity); // setting collisions between the player and the platform group this .physics.add.collider( this .player, this .platformGroup); // checking for input this .input.on( "pointerdown" , this .jump, this ); } // the core of the script: platform are added from the pool or created on the fly addPlatform(platformWidth, posX){ let platform; if ( this .platformPool.getLength()){ platform = this .platformPool.getFirst(); platform.x = posX; platform.active = true ; platform.visible = true ; this .platformPool.remove(platform); } else { platform = this .physics.add.sprite(posX, game.config.height * 0.8, "platform" ); platform.setImmovable( true ); platform.setVelocityX(gameOptions.platformStartSpeed * -1); this .platformGroup.add(platform); } platform.displayWidth = platformWidth; this .nextPlatformDistance = Phaser.Math.Between(gameOptions.spawnRange[0], gameOptions.spawnRange[1]); } // the player jumps when on the ground, or once in the air as long as there are jumps left and the first jump was on the ground jump(){ if ( this .player.body.touching.down || ( this .playerJumps > 0 && this .playerJumps < gameOptions.jumps)){ if ( this .player.body.touching.down){ this .playerJumps = 0; } this .player.setVelocityY(gameOptions.jumpForce * -1); this .playerJumps ++; } } update(){ // game over if ( this .player.y > game.config.height){ this .scene.start( "PlayGame" ); } this .player.x = gameOptions.playerStartPosition; // recycling platforms let minDistance = game.config.width; this .platformGroup.getChildren().forEach( function (platform){ let platformDistance = game.config.width - platform.x - platform.displayWidth / 2; minDistance = Math.min(minDistance, platformDistance); if (platform.x < - platform.displayWidth / 2){ this .platformGroup.killAndHide(platform); this .platformGroup.remove(platform); } }, this ); // adding new platforms if (minDistance > this .nextPlatformDistance){ var nextPlatformWidth = Phaser.Math.Between(gameOptions.platformSizeRange[0], gameOptions.platformSizeRange[1]); this .addPlatform(nextPlatformWidth, game.config.width + nextPlatformWidth / 2); } } }; function resize(){ let canvas = document.querySelector( "canvas" ); let windowWidth = window.innerWidth; let windowHeight = window.innerHeight; let windowRatio = windowWidth / windowHeight; let gameRatio = game.config.width / game.config.height; if (windowRatio < gameRatio){ canvas.style.width = windowWidth + "px" ; canvas.style.height = (windowWidth / gameRatio) + "px" ; } else { canvas.style.width = (windowHeight * gameRatio) + "px" ; canvas.style.height = windowHeight + "px" ; } } |
The core of the script is the part where group are defined with callbacks to move platform from active to pooling group and from pooling group to active group.
We can add a lot more options, but I am showing you how to make the game look more interesting next time, meanwhile download the source code.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.