Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Block it game, Box2D, Game development, HTML5, Javascript, Phaser and TypeScript.

I love to develop games, I am doing it for (way) more than a decade, but there’s still something I hate about developing games: the UI.

I hate to build “how to play” sections, in-game menus, buttons to toggle the sound on and off, panels to show stats and so on.

So I decided to add a Bootstrap navbar with all the stuff I hate. It’s way simpler than building it in Phaser, because I do not have to worry about bitmap fonts, events, listeners and other boring stuff, allowing me to focus on the thing I love the most: developing games.

It may not be a solution for complex games, but surely it is for hyper casual games, like the Block it prototype which I am about to release.

I already added Bootstrap in the Wordle prototype but in this example I added more integration.

Have a look at the game, which I suggest you to play directly from this link:

Tap the canvas to activate the walls when the ball is about to hit them, but don’t waste too much energy.

But most of all look at the Bootstrap navbar which includes an offcanvas content, a modal and a sound button.

In the offcanvas content you can see statistics generated by the game itself, while the sound/mute button turns on and off the sound effects in the game.

So the Bootstrap navbar isn’t just some HTML GUI, but it’s part of the game since it sends and receives information to and from the game.

Let’s see the basic principles of this way of using Bootstrap:

In index.html file there is the whole page with game container and Bootstrap components:

<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="initial-scale=1, maximum-scale=1">
        <link href="assets/bootstrap/bootstrap.min.css" rel="stylesheet">
        <script src="assets/bootstrap/bootstrap.bundle.min.js"></script>
        <link rel="stylesheet" href="style.css">
        <script src="main.js"></script> 
    </head>
    <body>
        <div class = "wrapper">
            <nav class="navbar navbar-expand navbar-dark bg-danger">
                <div class="container-fluid">
                    <div class="collapse navbar-collapse">
                        <ul class="navbar-nav me-auto">
                            <li class="nav-item">
                                <button type = "button" class="btn btn-danger" data-bs-toggle="offcanvas" data-bs-target="#offcanvascontent">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-list" viewBox="0 0 16 16">
                                        <path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"/>
                                    </svg>
                                </button>
                            </li>
                            <li class="nav-item">
                                <button type = "button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalcontent">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-question-circle" viewBox="0 0 16 16">
                                        <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
                                        <path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
                                    </svg>
                                </button>
                            </li>
                            <li class="nav-item">
                                <button type = "button" class="btn btn-danger" id="soundon">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-volume-up-fill" viewBox="0 0 16 16">
                                        <path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"/>
                                        <path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"/>
                                        <path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707zM6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06z"/>
                                    </svg>
                                </button>
                                <button type = "button" class="btn btn-danger" id="soundoff" style="display:none">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-volume-mute-fill" viewBox="0 0 16 16">
                                        <path d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zm7.137 2.096a.5.5 0 0 1 0 .708L12.207 8l1.647 1.646a.5.5 0 0 1-.708.708L11.5 8.707l-1.646 1.647a.5.5 0 0 1-.708-.708L10.793 8 9.146 6.354a.5.5 0 1 1 .708-.708L11.5 7.293l1.646-1.647a.5.5 0 0 1 .708 0z"/>
                                    </svg>
                                </button>
                            </li>
                        </ul>
                    </div>
                </div>
            </nav>
            <div id = "thegame"></div>
        </div>
        <div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvascontent">
            <div class="offcanvas-header">
                <h5 class="offcanvas-title">Block n' Bounce</h5>
                <button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
            </div>
            <div class="offcanvas-body">
                <table class="table">
                    <tr>
                        <td scope="row">Best score</td>
                        <td><span id = "bestscore"></span></td>
                    </tr>
                    <tr>
                        <td>Total plays</td>
                        <td><span id = "totalplays"></span></td>
                    </tr>
                    <tr>
                        <td>Total bounces</td>
                        <td><span id = "totalbounces"></span></td>
                    </tr>
                </table>
                <div class="card mb-3 border-0 mt-5">
                    <div class="row g-0">
                        <div class="col-md-4 text-center">
                            <img src="https://emanueleferonato.com/banners/emanueleferonato.png" srcset="https://emanueleferonato.com/banners/emanueleferonato.png 1x, https://emanueleferonato.com/banners/emanueleferonato.png 2x" class="img-fluid rounded-start" />
                        </div>
                        <div class="col-md-8">
                            <div class="card-body text-center">
                                <p class="card-text">Learn to build games with<br /><strong>Emanuele Feronato</strong></p>
                                <a class="btn btn-primary" href="https://emanueleferonato.com/" role="button" target = "_blank">Start learning</a>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="text-muted text-center">v1.0 2022-09-29</div>
            </div>
        </div>
        <div class="modal fade" id="modalcontent" tabindex="-1">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">How to play</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <ul>
                            <li>Tap and hold to activate walls and make the ball bounce</li>
                            <li>If the ball touches inactive borders, it's game over</li>
                            <li>Keeping walls active drains energy</li>
                            <li>Each time the ball bounces, you get a little energy bonus</li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

To make the navbar, which has a fixex height, and the canvas, which has a variable height, always fict perfectly into the web page I used the display and flex-direction CSS rules, in style.css file:

* {
    padding : 0;
    margin : 0;
}

body {
    background-color: #011025;    
}

canvas {
    touch-action : none;
    -ms-touch-action : none;
}

.wrapper, html, body {
    height : 100%;
    margin : 0;
}
 
.wrapper {
    display : flex;
    flex-direction : column;
}

Now in the main game file I can write content into Bootstrap page elements with writeHTML method:

writeHTML(id : string, content : any) : void {
    let element : HTMLElement = document.getElementById(id) as HTMLElement; 
    element.innerHTML = content.toString();
}

It’s just a function running some Vanilla JavaScript to change the content of the HTML in an element.

As for the sound on and sound off buttons, which are two distinct buttons with different display settings, I listen for clicks with these two lines:

let soundOnButton : HTMLElement = document.getElementById('soundon') as HTMLElement;
soundOnButton.addEventListener('click', this.muteSound.bind(this));

let soundOffButton : HTMLElement = document.getElementById('soundoff') as HTMLElement;
soundOffButton.addEventListener('click', this.enableSound.bind(this));

// at the beginning of a game, let's check display property of "soundon" button
this.soundOn = window.getComputedStyle(document.getElementById('soundon') as HTMLElement).display != 'none';

muteSound() :void {
    this.soundOn = false;
    let soundOnButton : HTMLElement = document.getElementById('soundon') as HTMLElement;
    soundOnButton.style.display = 'none';
    let soundOffButton : HTMLElement = document.getElementById('soundoff') as HTMLElement;
    soundOffButton.style.display = 'inline-block';
}

enableSound() :void {
    this.soundOn = true;
    let soundOnButton : HTMLElement = document.getElementById('soundon') as HTMLElement;
    soundOnButton.style.display = 'inline-block';
    let soundOffButton : HTMLElement = document.getElementById('soundoff') as HTMLElement;
    soundOffButton.style.display = 'none';
}

And the working integration of Bootstrap into HTML5 Phaser games has been completed.

I highly recommend you to use it in your hyper casual Phaser games.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.