Endless physics random terrain with only a bunch of bodies for your HTML5 games using Phaser, Box2D by Planck.js and Simplify.js

Read all posts about "" game

The tutorial about the generation of a physics driven random terrain for your HTML5 games using Phaser, Box2D by Planck.js and Simplify.js has been quite successful, so I am going to show you how to generate an endless physics random terrain, using only a bunch of bodies.

Actually, only a bunch of edges, since I am using Box2d edges to build the terrain, have a look:

There is no interactivity: a terrain is generated slope by slope, and a ball is running on it.

The generation of the terrain slope by slope, together with Simplify.js, allowed me to use only from 30 to 40 edges to handle an endless terrain.

And you also have a completely commented source code to study:

let game;
let gameOptions = {

    // starting terrain height, in % of game height
    startTerrainHeight: 0.5,

    // slope amplitude. The higher the value, the higher the hills
    slopeAmplitude: 120,

    // slope lenght range, in pixels
    slopeLengthRange: [100, 350],

    // amount of pixels in a meter, in Box2D world
    worldScale: 30
}
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 1334,
            height: 750
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class playGame extends Phaser.Scene {
    constructor() {
        super("PlayGame");
    }
    create() {

        // gravity vector
        let gravity = planck.Vec2(0, 4);

        // box2D world creation
        this.world = planck.World(gravity);

        // body to represent the ground
        this.ground = this.world.createBody();

        // slope start coordinates
        this.slopeStart = new Phaser.Math.Vector2(0, 0);

        // terrain generation
        this.generateTerrain();

        // graphics game object where to draw the debug draw
        this.debugDrawGraphics = this.add.graphics();

        // a text game object to display the number of edges used
        this.edgeText = this.add.text(10, game.config.height - 60, "", {
            fontFamily: "Arial",
            fontSize: 48,
            color: "#00ff00"
        });

        // method to add the ball
        this.addBall();
    }

    generateTerrain() {

        // while next slope starting X coordinate is less than game width and camera scroll...
        while (this.slopeStart.x < this.cameras.main.scrollX + game.config.width) {

            // ... generate a new slope
            this.generateSlope();
        }
    }

    addBall() {

        // ball body
        this.ball = this.world.createBody();

        // set the ball dynamic
        this.ball.setDynamic();

        // creation of a circle fixture to be assigned to the ball
        this.ball.createFixture(planck.Circle(1));

        // set ball position
        this.ball.setPosition(planck.Vec2(150 / gameOptions.worldScale, -100 / gameOptions.worldScale));

        // set ball mass data
        this.ball.setMassData({
            mass: 1,
            center: planck.Vec2(),
            I: 1
        });
    }

    generateSlope() {

        // array to store slope points
        let slopePoints = [];

        // slope start point
        let slopeStart = new Phaser.Math.Vector2(0, this.slopeStart.y);

        // set a random slope length
        let slopeLengthRange = Phaser.Math.Between(gameOptions.slopeLengthRange[0], gameOptions.slopeLengthRange[1]);

        // determine slope end point, with an exception if this is the firstslope: we want it to be flat
        let slopeEnd = (this.slopeStart.x == 0) ? new Phaser.Math.Vector2(slopeStart.x + gameOptions.slopeLengthRange[1] * 1.5, 0) : new Phaser.Math.Vector2(slopeStart.x + slopeLengthRange, Math.random());

        // current horizontal point
        let pointX = 0;

        // while the slope hans't been completely generated...
        while (pointX <= slopeEnd.x) {

            // slope interpolation value
            let interpolationVal = this.interpolate(slopeStart.y, slopeEnd.y, (pointX - slopeStart.x) / (slopeEnd.x - slopeStart.x));

            // current vertical point
            let pointY = game.config.height * gameOptions.startTerrainHeight + interpolationVal * gameOptions.slopeAmplitude;

            // add new point to slopePoints array
            slopePoints.push(new Phaser.Math.Vector2(pointX, pointY));

            // move on to next point
            pointX ++ ;
        }

        // simplify the slope
        let simpleSlope = simplify(slopePoints, 1, true);

        // loop through all simpleSlope points starting from the second
        for(let i = 1; i < simpleSlope.length; i++){

            // create a Box2D edge
            this.ground.createFixture(planck.Edge(planck.Vec2((simpleSlope[i - 1].x + this.slopeStart.x) / gameOptions.worldScale, simpleSlope[i - 1].y / gameOptions.worldScale), planck.Vec2((simpleSlope[i].x + this.slopeStart.x) / gameOptions.worldScale, simpleSlope[i].y / gameOptions.worldScale)), {
                density: 0,
                friction : 1
            });
        }

        // upldate next slope start point
        this.slopeStart.x += pointX - 1;
        this.slopeStart.y = slopeEnd.y;
    }

    update(t, dt) {

        // advance Box2D world simulation
        this.world.step(dt / 1000 * 2);

        // reset Box2D world forces
        this.world.clearForces();

        // get ball position
        let ballPosition = this.ball.getPosition();

        // scroll the camera accordingly
        this.cameras.main.scrollX = ballPosition.x * gameOptions.worldScale - 150;

        // set ball angular velocity
        this.ball.setAngularVelocity(10);

        // debug draw
        this.debugDraw();

        // keep generating terrain
        this.generateTerrain()

    }

    // below this line, only functions to represent objects on the screen or common math functions

    debugDraw() {
        this.debugDrawGraphics.clear();
        let edges = 0;
        for (let body = this.world.getBodyList(); body; body = body.getNext()) {
            for (let fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) {
                let shape = fixture.getShape();
                switch (fixture.getType()) {
                    case "edge": {
                        edges ++;
                        this.debugDrawGraphics.lineStyle(4, 0xff0000);
                        let v1 = shape.m_vertex1;
                        let v2 = shape.m_vertex2;
                        if(v2.x * gameOptions.worldScale < this.cameras.main.scrollX){
                            body.destroyFixture(fixture)
                        }
                        else{
                            this.debugDrawGraphics.beginPath();
                            this.debugDrawGraphics.moveTo(v1.x * gameOptions.worldScale, v1.y * gameOptions.worldScale);
                            this.debugDrawGraphics.lineTo(v2.x * gameOptions.worldScale, v2.y * gameOptions.worldScale);
                            this.debugDrawGraphics.strokePath();
                    }
                        break;
                    }
                    case "circle": {
                        let position = body.getPosition();
                        let angle = body.getAngle();
                        this.debugDrawGraphics.fillStyle(0x00ff00, 0.5);
                        this.debugDrawGraphics.fillCircle(position.x * gameOptions.worldScale, position.y * gameOptions.worldScale, shape.m_radius * gameOptions.worldScale);
                        this.debugDrawGraphics.lineStyle(2, 0x00ff00);
                        this.debugDrawGraphics.strokeCircle(position.x * gameOptions.worldScale, position.y * gameOptions.worldScale, shape.m_radius * gameOptions.worldScale);
                        this.debugDrawGraphics.beginPath();
                        this.debugDrawGraphics.moveTo(position.x * gameOptions.worldScale, position.y * gameOptions.worldScale);
                        this.debugDrawGraphics.lineTo(position.x * gameOptions.worldScale + 30 * Math.cos(angle), position.y * gameOptions.worldScale + 30 * Math.sin(angle));
                        this.debugDrawGraphics.strokePath();
                        break;
                    }
                }
            }
        }
        this.edgeText.x = this.cameras.main.scrollX + 10;
        this.edgeText.text = "Edges to generate terrain: " + edges;
    }
    interpolate(vFrom, vTo, delta) {
        let interpolation = (1 - Math.cos(delta * Math.PI)) * 0.5;
        return vFrom * (1 - interpolation) + vTo * interpolation;
    }
}

The core of the script is less than 50 lines of pure awesomeness, given the result. Maybe I am turning it into a full game, meanwhile download the source code and enjoy.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers