Add a Bootstrap navbar to your HTML5 Phaser games and let it manage the UI

Read all posts about "" game

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://www.emanueleferonato.com/banners/emanueleferonato.png" srcset="https://www.emanueleferonato.com/banners/emanueleferonato.png 1x, https://www.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://www.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.

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