How to create a Gameboy Advance Emulator (GBA) in the browser with JavaScript

How to create a Gameboy Advance Emulator (GBA) in the browser with JavaScript

As a kid, I didn't have the opportunity to play with a real Gameboy but in the Visual Boy Advance emulator available for Windows. I remember spending hours and hours playing awesome games like Pokemon, WarioLand, Castlevania, More Pokemon etc. Today, as a developer I would like to share with you in this article, how to create a GBA emulator in your web browser using the GBA.js library. GBA.js is a Game Boy Advance emulator written from scratch to employ HTML5 technologies like Canvas and Web Audio. It includes support for Audio, Savegames and Pause/Resume feature. It uses no plugins, and is designed to run on cutting edge web browsers. It is hosted on GitHub and is made available under the 2-clause BSD license. 

This library was written by Jeffrey Pfau and we thank him immensely for such a contribution to the open source world.

What you need to know

  • The Script by itself to emulate ROMs is completely legal, however the distribution of ROMs (game files) isn't legal at all. So please don't use this with commercial purposes or other bad things, specially in countries where this is totally dissaproved (for example Germany). The intentions of this article are purely educative.
  • You need to serve the HTML file either with http or https, file protocol isn't allowed as you won't be able to import any ROM to run.

In this case, we are going to show you how you can get started pretty easy with the emulator or a detailed step by step guide to implement the basic structure of it.

A. Easy implementation

If you don't want to know how to implement a GBA emulator step by step (which files to add etc) because you only want to test it, you can simply clone the repository using git in your computer:

git clone https://github.com/endrift/gbajs.git

Alternatively you can download the zip clone of the project and then extract the files into your desired folder. Then be sure to serve the folder of gbajs using some http/https local server either with Node.js, Apache etc because as previously mentioned, you can't access the index file of the emulator using the file:// protocol.

For example, we use Xampp which makes all the http story easy and we can access the gbajs folder with localhost and we'll be able to use the emulator:

Emulator GBA JavaScript - Pokemon

If you're interest on how all those things basically works and how to implement them by yourself, then follow the next point.

B. Step by step implementation

To create an emulator in the browser, we'll start in the same way you do with any kind of webpage, creating some markup, including some JS files and then opening it in the browser:

1. Create, download and import required assets

Create a basic HTML document with the required markup, namely a Canvas tag with the width and height of the original Gameboy Screen. This file in our case will be emulator.html, besides, you will obviously want to have the most basic action buttons that were available on the console as pause, the volume control etc:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>GBA Rocks</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <!-- Screen of the GBA.js -->
        <canvas id="screen" width="480" height="320"></canvas>

        <!-- Start Controls -->
        <div id="controls">
			<!-- Start App controls -->
			<h4>App Controls</h4>
			<div id="preload">
				<button id="select"> Select ROM file </button>
				<input id="loader" type="file" accept=".gba" />
				<button id="select-savegame-btn">Upload Savegame</button>
				<input id="saveloader" type="file" />
			</div>
			<!-- End App controls -->
			<br>
			<!-- Start ingame controls -->
			<h4>In-game controls</h4>
			<div id="ingame" class="hidden">
				<button id="pause">Pause game</button>
				<button id="reset-btn">Reset</button>
				<button id="download-savegame">Download Savegame File</button>

				<div id="sound">
					<p>Audio enabled</p>
					<input type="checkbox" id="audio-enabled-checkbox" checked="checked" />
					<p>Change sound level</p>
					<input id="volume-level-slider" type="range" min="0" max="1" value="1" step="any" />
				</div>
			</div>
			<!-- End ingame controls -->
		</div>
		<!-- End Controls -->
    </body>
</html>	

To make the emulator works, you will need to load around 17 JavaScript files that contain the required code (about 200KB without minification). Those files can be download from the official repository of GBA.js at Github here. Once you have the files, include them in your document. You can change the structure of folders as you want, this is just an example that uses the same structure of the original project, however is recommendable to keep it as there are other JS files that will be downloaded asynchronously later e.g in the js/video folder there needs to be the worker.js file, otherwise the emulator won't work:

<script src="js/util.js"></script>
<script src="js/core.js"></script>
<script src="js/arm.js"></script>
<script src="js/thumb.js"></script>
<script src="js/mmu.js"></script>
<script src="js/io.js"></script>
<script src="js/audio.js"></script>
<script src="js/video.js"></script>
<script src="js/video/proxy.js"></script>
<script src="js/video/software.js"></script>
<script src="js/irq.js"></script>
<script src="js/keypad.js"></script>
<script src="js/sio.js"></script>
<script src="js/savedata.js"></script>
<script src="js/gpio.js"></script>
<script src="js/gba.js"></script>
<!-- 
    This file is optional as it only is a function to load the ROM 
    But the function loadRom needs to exist !
-->
<script src="resources/xhr.js"></script>

You are free to minify the files if you want to decrease the load time of your page. As mentioned, the xhr.js file can be ommited and instead add the method inside of the file that allows you to load the ROM. The XMLHttpRequest will simply retrieve the file with the arraybuffer format in order to be processed with JavaScript later, so you can include it directly from another file or with a script tag in the document:

/**
 * Loads the ROM from a file using ajax
 * 
 * @param url 
 * @param callback 
 */
function loadRom(url, callback) {
	var xhr = new XMLHttpRequest();
	xhr.open('GET', url);
	xhr.responseType = 'arraybuffer';

    xhr.onload = function() { 
        callback(xhr.response) 
    };

	xhr.send();
}

2. Download the GBA bios.bin file

Then, in the same resources folder, be sure to include the bios.bin file of GBA (available on the repository), this file needs to be located in the resources directory as it will be included using an asynchronous request later. Obviously, you can change the path where the bios.bin file from where the file should be imported later once we add the emulator required scripts (step 3).

If you implement everything on the project and forget to include the bios.bin file, you'll begin to worry why it's not working and it's obvious why (maybe not so much). We need the bios.bin file of the GBA to emulate ROMs, think of it like you buy a car but you don't have the key to start it. The BIOS (the key), has a specific set of instructions that tell the Emulator (the car), how to start. So unless you've downloaded the BIOS, the emulator is not going to work.

3. Add emulator required scripts

The required scripts of the emulator are to handle the initialization, load the bios, change the volume, pause and resume the emulator etc. Most of them are meant to be used when the user clicks on some button (step 4) and others are used internally by other important functions:

var gba;
var runCommands = [];

// Setup the emulator
try {
    gba = new GameBoyAdvance();
    gba.keypad.eatInput = true;

    gba.setLogger(function (level, error) {
        console.error(error);
        
        gba.pause();
        
        var screen = document.getElementById('screen');
        
        if (screen.getAttribute('class') == 'dead') {
            console.log('We appear to have crashed multiple times without reseting.');
            return;
        }


        // Show error image in the emulator screen
        // The image can be retrieven from the repository
        var crash = document.createElement('img');
        crash.setAttribute('id', 'crash');
        crash.setAttribute('src', 'resources/crash.png');
        screen.parentElement.insertBefore(crash, screen);
        screen.setAttribute('class', 'dead');
    });
} catch (exception) {
    gba = null;
}

// Initialize emulator once the browser loads
window.onload = function () {
    if (gba && FileReader) {
        var canvas = document.getElementById('screen');
        gba.setCanvas(canvas);

        gba.logLevel = gba.LOG_ERROR;

        // Load the BIOS file of GBA (change the path according to yours)
        loadRom('resources/bios.bin', function (bios) {
            gba.setBios(bios);
        });

        if (!gba.audio.context) {
            // Remove the sound box if sound isn't available
            var soundbox = document.getElementById('sound');
            soundbox.parentElement.removeChild(soundbox);
        }

    } else {
        var dead = document.getElementById('controls');
        dead.parentElement.removeChild(dead);
    }
}

function fadeOut(id, nextId, kill) {
    var e = document.getElementById(id);
    var e2 = document.getElementById(nextId);
    if (!e) {
        return;
    }

    var removeSelf = function () {
        if (kill) {
            e.parentElement.removeChild(e);
        } else {
            e.setAttribute('class', 'dead');
            e.removeEventListener('webkitTransitionEnd', removeSelf);
            e.removeEventListener('oTransitionEnd', removeSelf);
            e.removeEventListener('transitionend', removeSelf);
        }
        if (e2) {
            e2.setAttribute('class', 'hidden');
            setTimeout(function () {
                e2.removeAttribute('class');
            }, 0);
        }
    }

    e.addEventListener('webkitTransitionEnd', removeSelf, false);
    e.addEventListener('oTransitionEnd', removeSelf, false);
    e.addEventListener('transitionend', removeSelf, false);
    e.setAttribute('class', 'hidden');
}

/**
 * Starts the emulator with the given ROM file
 * 
 * @param file 
 */
function run(file) {
    var dead = document.getElementById('loader');

    dead.value = '';
    
    var load = document.getElementById('select');
    load.textContent = 'Loading...';
    load.removeAttribute('onclick');
    
    var pause = document.getElementById('pause');
    pause.textContent = "PAUSE";
    
    gba.loadRomFromFile(file, function (result) {
        if (result) {
            for (var i = 0; i < runCommands.length; ++i) {
                runCommands[i]();
            }

            runCommands = [];
            fadeOut('preload', 'ingame');
            fadeOut('instructions', null, true);
            gba.runStable();
        } else {
            load.textContent = 'FAILED';

            setTimeout(function () {
                load.textContent = 'SELECT';
                
                load.onclick = function () {
                    document.getElementById('loader').click();
                };

            }, 3000);
        }
    });
}

/**
 * Resets the emulator
 * 
 */
function reset() {
    gba.pause();
    gba.reset();

    var load = document.getElementById('select');
    
    load.textContent = 'SELECT';

    var crash = document.getElementById('crash');

    if (crash) {
        var context = gba.targetCanvas.getContext('2d');
        context.clearRect(0, 0, 480, 320);
        gba.video.drawCallback();
        crash.parentElement.removeChild(crash);
        var canvas = document.getElementById('screen');
        canvas.removeAttribute('class');
    } else {
        lcdFade(gba.context, gba.targetCanvas.getContext('2d'), gba.video.drawCallback);
    }

    load.onclick = function () {
        document.getElementById('loader').click();
    };

    fadeOut('ingame', 'preload');

    // Clear the ROM
    gba.rom = null;
}

/**
 * Stores the savefile data in the emulator.
 * 
 * @param file 
 */
function uploadSavedataPending(file) {
    runCommands.push(function () { 
        gba.loadSavedataFromFile(file) 
    });
}

/**
 * Toggles the state of the game
 */
function togglePause() {
    var e = document.getElementById('pause');

    if (gba.paused) {
        gba.runStable();
        e.textContent = "PAUSE";
    } else {
        gba.pause();
        e.textContent = "UNPAUSE";
    }
}

/**
 * From a canvas context, creates an LCD animation that fades the content away.
 * 
 * @param context 
 * @param target 
 * @param callback 
 */
function lcdFade(context, target, callback) {
    var i = 0;

    var drawInterval = setInterval(function () {
        i++;

        var pixelData = context.getImageData(0, 0, 240, 160);

        for (var y = 0; y < 160; ++y) {
            for (var x = 0; x < 240; ++x) {
                var xDiff = Math.abs(x - 120);
                var yDiff = Math.abs(y - 80) * 0.8;
                var xFactor = (120 - i - xDiff) / 120;
                var yFactor = (80 - i - ((y & 1) * 10) - yDiff + Math.pow(xDiff, 1 / 2)) / 80;
                pixelData.data[(x + y * 240) * 4 + 3] *= Math.pow(xFactor, 1 / 3) * Math.pow(yFactor, 1 / 2);
            }
        }
        
        context.putImageData(pixelData, 0, 0);

        target.clearRect(0, 0, 480, 320);

        if (i > 40) {
            clearInterval(drawInterval);
        } else {
            callback();
        }
    }, 50);
}

/**
 * Set the volume of the emulator.
 * 
 * @param value 
 */
function setVolume(value) {
    gba.audio.masterVolume = Math.pow(2, value) - 1;
}

4. Add action scripts 

The action scripts are simply event listeners attached to the previously (step 1) created DOM elements as the button to pause, load ROM etc. They use obviously some of the scripts of the previous step, therefore they need to be loaded in the context where gba exists. So you can load the scripts from another file in your document or simply wrap them in a script tag in your document:

// If clicked, simulate click on the File Select input to load a ROM
document.getElementById("select").addEventListener("click", function(){
    document.getElementById("loader").click();
}, false);

// Run the emulator with the loaded ROM
document.getElementById("loader").addEventListener("change", function(){
    var ROM = this.files[0];
    run(ROM);
}, false);

// If clicked, simulate click on the File Select Input to load the savegame file
document.getElementById("select-savegame-btn").addEventListener("click", function(){
    document.getElementById('saveloader').click();
}, false);

// Load the savegame to the emulator
document.getElementById("saveloader").addEventListener("change", function(){
    var SAVEGAME = this.files[0];
    uploadSavedataPending(SAVEGAME);
}, false); 

// Pause/Resume game
document.getElementById("pause").addEventListener("click", function(){
        togglePause();
}, false);

// Reset game
document.getElementById("reset-btn").addEventListener("click", function(){
        reset();
}, false);

// Download the savegamefile
document.getElementById("download-savegame").addEventListener("click", function(){
        gba.downloadSavedata();
}, false);

// Mute/Unmute emulator
document.getElementById("audio-enabled-checkbox").addEventListener("change", function(){
    gba.audio.masterEnable = this.checked;
}, false);

// Handle volume level slider
document.getElementById("volume-level-slider").addEventListener("change", function(){
    var volumeLevel = this.value;
    setVolume(volumeLevel);
}, false);
document.getElementById("volume-level-slider").addEventListener("input", function(){
    var volumeLevel = this.value;
    setVolume(volumeLevel);
}, false); 

// In order to pause/resume the game when the user changes the website tab in the browser
// add the 2 following listeners to the window !
// 
// This feature is problematic/tricky to handle, so you can make it better if you need to
window.onblur = function () {
    if(gba.hasRom()){
        var e = document.getElementById('pause');

        if (!gba.paused) {
            gba.pause();
            e.textContent = "UNPAUSE";

            console.log("Window Focused: the game has been paused");
        }
    }
};

window.onfocus = function () {
    if(gba.hasRom()){
        var e = document.getElementById('pause');

        if (gba.paused) {
            gba.runStable();
            e.textContent = "PAUSE";

            console.log("Window Focused: the game has been resumed");
        }
    }
}; 

With this script, your application is finally available for its usage and you can start to test it. Run your local server and go to the emulator.html file and test it. For example, how it loads a savegame and we are able to continue playing a previously game match:

Savegame JavaScript GBA Emulator

As you can see, the second implementation doesn't add any style but helps to understand how the basics of the library work and how can they be easily used. The emulator by itself is pretty functional. Most games run and are playable, maybe some will still crash at times or lock up at specific points. Moreover, other minor bugs in graphics or sound are still present, and the developer works on fixing those bugs. A compatibility list can be found here. If you think you have found a bug, report it on the GitHub issue tracker.

Final example

The following file emulator.html shows how your file should look like including all the scripts within script tags:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>GBA Rocks</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <!-- Screen of the GBA.js -->
        <canvas id="screen" width="480" height="320"></canvas>

        <!-- Start Controls -->
        <div id="controls">
			<!-- Start App controls -->
			<h4>App Controls</h4>
			<div id="preload">
				<button id="select"> Select ROM file </button>
				<input id="loader" type="file" accept=".gba" />
				<button id="select-savegame-btn">Upload Savegame</button>
				<input id="saveloader" type="file" />
			</div>
			<!-- End App controls -->
			<br>
			<!-- Start ingame controls -->
			<h4>In-game controls</h4>
			<div id="ingame" class="hidden">
				<button id="pause">Pause game</button>
				<button id="reset-btn">Reset</button>
				<button id="download-savegame">Download Savegame File</button>

				<div id="sound">
					<p>Audio enabled</p>
					<input type="checkbox" id="audio-enabled-checkbox" checked="checked" />
					<p>Change sound level</p>
					<input id="volume-level-slider" type="range" min="0" max="1" value="1" step="any" />
				</div>
			</div>
			<!-- End ingame controls -->
		</div>
		<!-- End Controls -->
		 

	<script src="js/util.js"></script>
	<script src="js/core.js"></script>
	<script src="js/arm.js"></script>
	<script src="js/thumb.js"></script>
	<script src="js/mmu.js"></script>
	<script src="js/io.js"></script>
	<script src="js/audio.js"></script>
	<script src="js/video.js"></script>
	<script src="js/video/proxy.js"></script>
	<script src="js/video/software.js"></script>
	<script src="js/irq.js"></script>
	<script src="js/keypad.js"></script>
	<script src="js/sio.js"></script>
	<script src="js/savedata.js"></script>
	<script src="js/gpio.js"></script>
	<script src="js/gba.js"></script>
	<!-- 
		This file is optional as it only is a function to load the ROM 
		But the function loadRom needs to exist !
	-->
	<script src="resources/xhr.js"></script>


	<!-- Start APP Scripts -->
	<script>
		var gba;
		var runCommands = [];

		// Setup the emulator
		try {
			gba = new GameBoyAdvance();
			gba.keypad.eatInput = true;

			gba.setLogger(function (level, error) {
				console.error(error);
				
				gba.pause();
				
				var screen = document.getElementById('screen');
				
				if (screen.getAttribute('class') == 'dead') {
					console.log('We appear to have crashed multiple times without reseting.');
					return;
				}


				// Show error image in the emulator screen
				// The image can be retrieven from the repository
				var crash = document.createElement('img');
				crash.setAttribute('id', 'crash');
				crash.setAttribute('src', 'resources/crash.png');
				screen.parentElement.insertBefore(crash, screen);
				screen.setAttribute('class', 'dead');
			});
		} catch (exception) {
			gba = null;
		}

		// Initialize emulator once the browser loads
		window.onload = function () {
			if (gba && FileReader) {
				var canvas = document.getElementById('screen');
				gba.setCanvas(canvas);

				gba.logLevel = gba.LOG_ERROR;

				// Load the BIOS file of GBA (change the path according to yours)
				loadRom('resources/bios.bin', function (bios) {
					gba.setBios(bios);
				});

				if (!gba.audio.context) {
					// Remove the sound box if sound isn't available
					var soundbox = document.getElementById('sound');
					soundbox.parentElement.removeChild(soundbox);
				}

			} else {
				var dead = document.getElementById('controls');
				dead.parentElement.removeChild(dead);
			}
		}

		function fadeOut(id, nextId, kill) {
			var e = document.getElementById(id);
			var e2 = document.getElementById(nextId);
			if (!e) {
				return;
			}

			var removeSelf = function () {
				if (kill) {
					e.parentElement.removeChild(e);
				} else {
					e.setAttribute('class', 'dead');
					e.removeEventListener('webkitTransitionEnd', removeSelf);
					e.removeEventListener('oTransitionEnd', removeSelf);
					e.removeEventListener('transitionend', removeSelf);
				}
				if (e2) {
					e2.setAttribute('class', 'hidden');
					setTimeout(function () {
						e2.removeAttribute('class');
					}, 0);
				}
			}

			e.addEventListener('webkitTransitionEnd', removeSelf, false);
			e.addEventListener('oTransitionEnd', removeSelf, false);
			e.addEventListener('transitionend', removeSelf, false);
			e.setAttribute('class', 'hidden');
		}

		/**
		 * Starts the emulator with the given ROM file
		 * 
		 * @param file 
		 */
		function run(file) {
			var dead = document.getElementById('loader');

			dead.value = '';
			
			var load = document.getElementById('select');
			load.textContent = 'Loading...';
			load.removeAttribute('onclick');
			
			var pause = document.getElementById('pause');
			pause.textContent = "PAUSE";
			
			gba.loadRomFromFile(file, function (result) {
				if (result) {
					for (var i = 0; i < runCommands.length; ++i) {
						runCommands[i]();
					}

					runCommands = [];
					fadeOut('preload', 'ingame');
					fadeOut('instructions', null, true);
					gba.runStable();
				} else {
					load.textContent = 'FAILED';

					setTimeout(function () {
						load.textContent = 'SELECT';
						
						load.onclick = function () {
							document.getElementById('loader').click();
						};

					}, 3000);
				}
			});
		}

		/**
		 * Resets the emulator
		 * 
		 */
		function reset() {
			gba.pause();
			gba.reset();

			var load = document.getElementById('select');
			
			load.textContent = 'SELECT';

			var crash = document.getElementById('crash');

			if (crash) {
				var context = gba.targetCanvas.getContext('2d');
				context.clearRect(0, 0, 480, 320);
				gba.video.drawCallback();
				crash.parentElement.removeChild(crash);
				var canvas = document.getElementById('screen');
				canvas.removeAttribute('class');
			} else {
				lcdFade(gba.context, gba.targetCanvas.getContext('2d'), gba.video.drawCallback);
			}

			load.onclick = function () {
				document.getElementById('loader').click();
			};

			fadeOut('ingame', 'preload');

			// Clear the ROM
			gba.rom = null;
		}

		/**
		 * Stores the savefile data in the emulator.
		 * 
		 * @param file 
		 */
		function uploadSavedataPending(file) {
			runCommands.push(function () { 
				gba.loadSavedataFromFile(file) 
			});
		}

		/**
		 * Toggles the state of the game
		 */
		function togglePause() {
			var e = document.getElementById('pause');

			if (gba.paused) {
				gba.runStable();
				e.textContent = "PAUSE";
			} else {
				gba.pause();
				e.textContent = "UNPAUSE";
			}
		}

		/**
		 * From a canvas context, creates an LCD animation that fades the content away.
		 * 
		 * @param context 
		 * @param target 
		 * @param callback 
		 */
		function lcdFade(context, target, callback) {
			var i = 0;

			var drawInterval = setInterval(function () {
				i++;

				var pixelData = context.getImageData(0, 0, 240, 160);

				for (var y = 0; y < 160; ++y) {
					for (var x = 0; x < 240; ++x) {
						var xDiff = Math.abs(x - 120);
						var yDiff = Math.abs(y - 80) * 0.8;
						var xFactor = (120 - i - xDiff) / 120;
						var yFactor = (80 - i - ((y & 1) * 10) - yDiff + Math.pow(xDiff, 1 / 2)) / 80;
						pixelData.data[(x + y * 240) * 4 + 3] *= Math.pow(xFactor, 1 / 3) * Math.pow(yFactor, 1 / 2);
					}
				}
				
				context.putImageData(pixelData, 0, 0);

				target.clearRect(0, 0, 480, 320);

				if (i > 40) {
					clearInterval(drawInterval);
				} else {
					callback();
				}
			}, 50);
		}

		/**
		 * Set the volume of the emulator.
		 * 
		 * @param value 
		 */
		function setVolume(value) {
			gba.audio.masterVolume = Math.pow(2, value) - 1;
		}
	</script>
	<!-- End APP Scripts -->

	<!-- Start Events Scripts -->
	<script>
		// If clicked, simulate click on the File Select input to load a ROM
		document.getElementById("select").addEventListener("click", function(){
			document.getElementById("loader").click();
		}, false);
		
		// Run the emulator with the loaded ROM
		document.getElementById("loader").addEventListener("change", function(){
			var ROM = this.files[0];
 			run(ROM);
		}, false);

		// If clicked, simulate click on the File Select Input to load the savegame file
		document.getElementById("select-savegame-btn").addEventListener("click", function(){
			document.getElementById('saveloader').click();
		}, false);

		// Load the savegame to the emulator
		document.getElementById("saveloader").addEventListener("change", function(){
			var SAVEGAME = this.files[0];
			uploadSavedataPending(SAVEGAME);
		}, false); 

		// Pause/Resume game
		document.getElementById("pause").addEventListener("click", function(){
			 togglePause();
		}, false);
		
		// Reset game
		document.getElementById("reset-btn").addEventListener("click", function(){
			 reset();
		}, false);

		// Download the savegamefile
		document.getElementById("download-savegame").addEventListener("click", function(){
			 gba.downloadSavedata();
		}, false);
		
		// Mute/Unmute emulator
		document.getElementById("audio-enabled-checkbox").addEventListener("change", function(){
			gba.audio.masterEnable = this.checked;
		}, false);

		// Handle volume level slider
		document.getElementById("volume-level-slider").addEventListener("change", function(){
			var volumeLevel = this.value;
			setVolume(volumeLevel);
		}, false);
		document.getElementById("volume-level-slider").addEventListener("input", function(){
			var volumeLevel = this.value;
			setVolume(volumeLevel);
		}, false); 

		// In order to pause/resume the game when the user changes the website tab in the browser
		// add the 2 following listeners to the window !
		// 
		// This feature is problematic/tricky to handle, so you can make it better if you need to
		window.onblur = function () {
			if(gba.hasRom()){
				var e = document.getElementById('pause');

				if (!gba.paused) {
					gba.pause();
					e.textContent = "UNPAUSE";

					console.log("Window Focused: the game has been paused");
				}
			}
		};

		window.onfocus = function () {
			if(gba.hasRom()){
				var e = document.getElementById('pause');

				if (gba.paused) {
					gba.runStable();
					e.textContent = "PAUSE";

					console.log("Window Focused: the game has been resumed");
				}
			}
		};
	</script>
	<!-- End Events Scripts -->
</body>
</html>

Final recommendations

  • The project is made to work on sane (recent) browsers so don't expect support for IE8.
  • The Savegame files needs to be loaded before the ROM to make sure that it works properly.
  • Prevent your window from scrolling, otherwise the render process will be heavy and therefore the gameplay will be slowed down temporary.
  • Although we covered the most important points of the emulator, we may have forgotten something so please don't forget to visit the official repository and the official demo for more information.

Happy coding you lovely gamer !

Become a more social person