“CLOCKS – The Game” HTML5 playable game with 10 levels updated to Phaser 3 with commented source code

Read all posts about "" game

About 4 years ago I started the “Clocks – The Game” tutorial series to port the mobile hit which at the moment does not seem to be available on the app store.

Anyway, it’s an one button game where you have to destroy all clocks on the stage by hitting them with a ball which is fired from clocks’ hand.

The complete theory behind the prototype can be found in the first post of the series, and this is the Phaser 3 prototype with the first 10 levels of the game for you to play:

Click or tap to fire the ball from the highlighted clock, try to highlight all clocks to advance levels.

And here is the source code, updated to Phaser 3, optimized and completely commented:

let game;

let gameOptions = {

    // grid size, in pixels
    gridSize: 40,

    // level width, in tiles
    levelWidth: 8,

    // level height, in tiles
    levelHeight: 8,

    // ball speed, in pixels per second
    ballSpeed: 600,

    // starting level
    startingLevel: 0
}

window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor: 0x2babca,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 640,
            height: 960
        },
        physics: {
            default: "arcade"
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}

class playGame extends Phaser.Scene {
    constructor() {
        super("PlayGame");
    }
    preload() {
        this.load.image("smallclockface", "assets/sprites/smallclockface.png");
        this.load.image("bigclockface", "assets/sprites/bigclockface.png");
        this.load.image("ball", "assets/sprites/ball.png");
        this.load.spritesheet("smallclock", "assets/sprites/smallclock.png", {
            frameWidth: 70,
            frameHeight: 70
        });
        this.load.spritesheet("smallhand", "assets/sprites/smallhand.png", {
            frameWidth: 70,
            frameHeight: 70
        });
        this.load.spritesheet("bigclock", "assets/sprites/bigclock.png", {
            frameWidth: 140,
            frameHeight: 140
        });
        this.load.spritesheet("bighand", "assets/sprites/bighand.png", {
            frameWidth: 140,
            frameHeight: 140
        });
    }
    create() {

        // player can fire now
        this.canFire = true;

        // clocks reached so far, just one, the one we start from
        this.clocksReached = 1;

        // total clocks in the level, about to be loaded
        this.totalClocks = 0;

        // array containing all clocks
        this.clocksArray = [];

        // physics group which contains all clock hands
        this.handGroup = this.physics.add.group();

        // physics group which contains all clocks
        this.clockGroup = this.physics.add.group();

        // loop through all current level items
        for(let i = 0; i < levels[gameOptions.startingLevel].tiledOutput.length; i ++) {

            // switching among possible values
            switch(levels[gameOptions.startingLevel].tiledOutput[i]) {

                // small clock
                case 1:
                    this.clocksArray.push(this.placeClock(new Phaser.Math.Vector2(i % gameOptions.levelWidth * 2 + 1, Math.floor(i / gameOptions.levelHeight) * 2 + 1), "small"));
                    break;

                // big clock
                case 2:
                    this.clocksArray.push(this.placeClock(new Phaser.Math.Vector2(i % gameOptions.levelWidth * 2 + 2, Math.floor(i / gameOptions.levelHeight) * 2), "big"));
                    break;
            }
        }

        // pick a random clock and make it the active clock
        this.activeClock = Phaser.Utils.Array.GetRandom(this.clocksArray);

        // change active clock appearance
        this.activeClock.setFrame(1);
        this.activeClock.tint = 0x2babca;
        this.activeClock.face.visible = true;
        this.activeClock.hand.setFrame(1);
        this.activeClock.hand.tint = 0xffffff;

        // add the ball
        this.ball = this.physics.add.sprite(game.config.width / 2, game.config.height / 2, "ball");

        // the ball is not visible at the beginning of the game
        this.ball.visible = false;

        // set ball to collide with world bounds
        this.ball.body.collideWorldBounds = true;

        // set ball to listen to world bounds collision
        this.ball.body.onWorldBounds = true;

        // when something (the ball) collide with world bounds...
        this.physics.world.on("worldbounds", function() {

            // restart the game
            this.scene.start("PlayGame");
        }, this);

        // wait for player input then call "throwBall" method
        this.input.on("pointerdown", this.throwBall, this);

        // handle overlap between the ball and "clockGroup" group, then call "handleOverlap" method
        this.physics.add.overlap(this.ball, this.clockGroup, this.handleOverlap, null, this);
    }

    // method to place a clock on the stage, given the coordinates and a "small" or "big" prefix
    placeClock(clockCoordinates, prefix) {

        // clock creation
        let clockSprite = this.clockGroup.create(clockCoordinates.x * gameOptions.gridSize, clockCoordinates.y * gameOptions.gridSize, prefix + "clock");

        // faceSprite is the clock face
        let faceSprite = this.add.sprite(clockSprite.x, clockSprite.y, prefix + "clockface");

        // clock face is not visible by default
        faceSprite.visible = false;

        // clock face is stored as a custom clock property
        clockSprite.face = faceSprite;

        // hand sprite is the clock hand
        let handSprite = this.handGroup.create(clockSprite.x, clockSprite.y, prefix + "hand");

        // set clock hand tint
        handSprite.tint = 0x2babca;

        // set a random clock hand rotation
        handSprite.rotation = Phaser.Math.Angle.Random();

        // set a random angular velocity
        handSprite.body.angularVelocity = Phaser.Math.RND.between(levels[gameOptions.startingLevel].clockSpeed[0], levels[gameOptions.startingLevel].clockSpeed[1]) * Phaser.Math.RND.sign();

        // clock hand is stored as a custom clock property
        clockSprite.hand = handSprite;

        // increase totalCloks
        this.totalClocks ++;

        // return the clock itself
        return clockSprite;
    }

    // method to fire the ball. It's called throwBall rather than fireBall because the game is not a RPG :)
    throwBall() {

        // if we can fire...
        if(this.canFire) {

            // set canFire to false, we are already firing
            this.canFire = false;

            // get active clock hand rotation
            let handAngle = this.activeClock.hand.rotation;

            // place the ball at the same active clock position
            this.ball.x = this.activeClock.x;
            this.ball.y = this.activeClock.y;

            // set the ball visible
            this.ball.visible = true;

            // calculate velocity according to clock angle rotation
            let ballVelocity = this.physics.velocityFromRotation(handAngle, gameOptions.ballSpeed);

            // set ball velocity
            this.ball.body.setVelocity(ballVelocity.x, ballVelocity.y);

            // destroy active clock, clock face and clock hand
            this.activeClock.hand.destroy();
            this.activeClock.face.destroy();
            this.activeClock.destroy();
        }
    }

    // method to handle overlap between ball and clock
    handleOverlap(ball, clock) {

        // can't we fire? (this means: are we firing at the moment)
        if(!this.canFire) {

            // change clock frame and tint color to make it active
            clock.setFrame(1);
            clock.tint = 0x2babca;

            // show clock face
            clock.face.visible = true;

            // change clock hand frame tint and color to make it active
            clock.hand.setFrame(1);
            clock.hand.tint = 0xffffff;

            // now this clock is the active clock
            this.activeClock = clock;

            // hide the ball
            this.ball.visible = false;

            // stop the ball
            this.ball.setVelocity(0, 0);

            // we reached another clock
            this.clocksReached ++;

            // are there more clocks to reach?
            if(this.clocksReached < this.totalClocks) {

                // we can fire again
                this.canFire = true;
            }
            else {

                // advance by one level
                gameOptions.startingLevel = (gameOptions.startingLevel + 1) % levels.length;

                // wait one second and a half, then restart the game
                this.time.addEvent({
                    delay: 1500,
                    callbackScope: this,
                    callback: function() {
                        this.scene.start("PlayGame");
                    }
                });
            }
        }
    }
}

// the levels. 0: empty tile / 1: small clock / 2: big clock

let levels = [
// level 1
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 2
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 3
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 4
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 5
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 6
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 7
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 8
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
// level 9
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]
},
// level 10
{
     clockSpeed: [200, 450],
     tiledOutput: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0]
}
]

Since the game has disappeared from the store, it would be nice to retrieve the original levels and publish them as a HTML5 games, maybe I’ll manage to get the levels from the author, stay tuned and download the source code of the prototype.

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

214 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
// 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