'textures/house-evening-2.png': 597,
'textures/house-evening-3.png': 646,
};
- for(let unlockable of ['golden', 'ghost']) {
- if(game.settings['unlocks'].includes(unlockable)) {
- if(unlockable == 'golden') {
- todoList['textures/feather-' + unlockable + '.png'] = 1027;
- } else {
- todoList['textures/feather-' + unlockable + '.png'] = 1023;
+ for(let unlockable of game.settings['unlocks']) {
+ if(unlockable == 'golden') {
+ todoList['textures/feather-golden.png'] = 1027;
+ } else if(unlockable == 'ghost') {
+ todoList['textures/feather-ghost.png'] = 1023;
+ } else {
+ let unlock = unlockWithKey(game, 'NIbp2kW5' + unlockable + 'e2ZDFl5Y');
+ if(unlock && unlock['type'] == 'feather') {
+ todoList['data:textures/feather-' + unlock['name']] = unlock['url'];
}
}
}
todoList['audio/sound5-' + theme + '.ogg'] = audioThemes[theme][5];
game.assets.audiothemes.push(theme);
}
- let total = Object.keys(todoList).map(k => todoList[k]).reduce((a, b) => a + b, 0);
+ let total = Object.keys(todoList).filter(k => !k.startsWith('data:')).map(k => todoList[k]).reduce((a, b) => a + b, 0);
let progress = {};
const loader = {
'audio': new THREE.AudioLoader(),
'textures': new THREE.TextureLoader(),
};
for(let todo in todoList) {
- progress[todo] = 0;
+ let isDataUri = todo.startsWith('data:');
+ if(isDataUri) {
+ todo = todo.slice(5);
+ } else {
+ progress[todo] = 0;
+ }
let segments = todo.split('/');
- if(!game.assets.hasOwnProperty(segments[0])) {
+ if(!(segments[0] in game.assets)) {
game.assets[segments[0]] = {};
}
if(!(segments[0] in loader)) {
reject('Unsupported resource: ' + todo);
}
- loader[segments[0]].load(todo, (result) => {
+ let url = todo;
+ if(isDataUri) {
+ url = todoList['data:' + todo];
+ }
+ loader[segments[0]].load(url, (result) => {
if(segments[0] == 'textures') {
result.colorSpace = THREE.SRGBColorSpace;
result.minFilter = THREE.NearestFilter;
}
}
game.assets[segments[0]][segments[1].split('.')[0]] = result;
- progress[todo] = todoList[todo];
- if(renderProgressCallback) {
- renderProgressCallback(Object.keys(progress).map(k => progress[k]).reduce((a, b) => a + b, 0) / total);
+ if(todo in progress) {
+ progress[todo] = todoList[todo];
+ if(renderProgressCallback) {
+ renderProgressCallback(Object.keys(progress).map(k => progress[k]).reduce((a, b) => a + b, 0) / total);
+ }
}
}, (xhr) => {
- progress[todo] = xhr.loaded;
- if(renderProgressCallback) {
- renderProgressCallback(Object.keys(progress).map(k => progress[k]).reduce((a, b) => a + b, 0) / total);
+ if(todo in progress) {
+ progress[todo] = xhr.loaded;
+ if(renderProgressCallback) {
+ renderProgressCallback(Object.keys(progress).map(k => progress[k]).reduce((a, b) => a + b, 0) / total);
+ }
}
}, (err) => {
reject('Error while loading ' + todo + ': ' + err);
game.objects.feather.speed.add(vector);
}
-function init(game, canvas) {
+function initializeGame(game, canvas) {
game.timeProgress = 0;
game.timeTotal = 258;
});
document.body.addEventListener('touchend', e => {
if(e.target.closest('.ui-container') && game.settings['controls'] != 'mouse' && ['gameplay', 'openingcutscene', 'endingcutscene'].includes(game.ui.currentPage)) {
- game.ui.moveToPage('pause', true);
+ moveToPage(game, 'pause', true);
e.preventDefault();
}
if(game.settings['controls'] == 'touchpad' || game.settings['controls'] == 'thumbstick') {
});
game.view.music.offset = 0;
game.view.music.play();
- game.ui.moveToPage('gameplay', true);
+ moveToPage(game, 'gameplay', true);
}
} else if(game.ui.currentPage == 'endingcutscene') {
cameraSwayFactor = cameraSwayFactor * game.timeProgress / 8;
}
if(game.timeProgress >= 8) {
game.ui.root.querySelector('.ui-page.title').classList.add('end');
- game.ui.moveToPage('outro', true);
+ moveToPage(game, 'outro', true);
}
} else if(game.ui.reachedEnd) {
cameraX = 5;
game.var.endingEntryRotation.y = game.objects.feather.rotation.y;
game.var.endingEntryRotation.z = game.objects.feather.rotation.z;
game.var.endingExitRotation.set(-0.2, 0, -0.2);
- game.ui.moveToPage('endingcutscene', true);
+ moveToPage(game, 'endingcutscene', true);
}
if(game.settings['audio']['music'] > 0.0 && game.view.music && !game.view.music.isPlaying) {
if(audioThemeRadio) {
audioThemeRadio.checked = true;
}
+ // Custom hash function that ensures our unlockables get stored in the same order,
+ // regardless of the order in which they get unlocked.
+ let miniHash = (input) => {
+ return 4 * input.charCodeAt(0) + 0.1 * input.charCodeAt(1) + 3 * input.charCodeAt(2) + 2 * input.charCodeAt(3);
+ }
+ settings['unlocks'].sort((u1, u2) => miniHash(u1) > miniHash(u2));
for(let unlockedFeather of settings['unlocks']) {
if(!game.ui.root.querySelector('.ui-page.options .feather input[value="' + unlockedFeather + '"]')) {
let radio = document.createElement('input');
radio.name = 'upInTheAirGame-feather';
radio.value = unlockedFeather;
let img = document.createElement('img');
- img.src = 'textures/feather-' + unlockedFeather + '.png';
+ if(unlockedFeather == 'golden' || unlockedFeather == 'ghost') {
+ img.src = 'textures/feather-' + unlockedFeather + '.png';
+ } else {
+ let unlock = unlockWithKey(game, 'NIbp2kW5' + unlockedFeather + 'e2ZDFl5Y');
+ if(unlock && unlock['type'] == 'feather') {
+ img.src = unlock['url'];
+ } else {
+ continue;
+ }
+ }
img.alt = unlockedFeather[0].toUpperCase() + unlockedFeather.slice(1) + ' feather';
let label = document.createElement('label');
label.appendChild(radio);
elem.value = value;
elem.parentNode.nextElementSibling.innerText = value;
});
- if(audioCategory == 'music' && game.view) {
+ if(audioCategory == 'music' && game.view && game.view.music) {
game.view.music.setVolume(game.settings['audio'][audioCategory]);
}
}
// Custom hash function that ensures our unlockables get stored in the same order,
// regardless of the order in which they get unlocked.
let miniHash = (input) => {
- return 4 * input.charCodeAt(0) + 3 * input.charCodeAt(2) + 2 * input.charCodeAt(3);
+ return 4 * input.charCodeAt(0) + 0.1 * input.charCodeAt(1) + 3 * input.charCodeAt(2) + 2 * input.charCodeAt(3);
}
game.settings['unlocks'].sort((u1, u2) => miniHash(u1) > miniHash(u2));
let insertAfterFeather = 'purple';
radio.type = 'radio';
radio.name = 'upInTheAirGame-feather';
radio.value = feather;
+ radio.addEventListener('change', () => {
+ applySettings(game);
+ createFeather(game);
+ });
let img = document.createElement('img');
img.src = url;
img.alt = feather[0].toUpperCase() + feather.slice(1) + ' feather';
return true;
}
-window['game'] = {
- state: 'loadingAssets',
- ui: {
- root: document.querySelector('.game-upintheair .ui-container'),
- gamepads: [],
- },
- settings: {},
-};
-loadSettings(window['game']);
-applySettings(window['game']);
-
-game.ui.root.querySelectorAll('button.goto').forEach((btn) => {
- btn.addEventListener('click', (e) => {
- if(!game.view.music) {
- game.view.audioListener = new THREE.AudioListener();
- game.view.camera.add(game.view.audioListener);
- game.view.music = new THREE.Audio(game.view.audioListener);
- game.view.music.setBuffer(game.assets['audio']['music-' + game.settings['audio']['theme']]);
- game.view.music.setVolume(game.settings['audio']['music']);
- game.view.windSound = new THREE.Audio(game.view.audioListener);
- game.view.windSound.setBuffer(game.assets['audio']['wind']);
- game.view.windSound.setVolume(game.settings['audio']['sounds']);
- game.view.windSound.setLoop(false);
- }
- let btn = e.target.closest('button');
- let target = Array.from(btn.classList).filter(c => c != 'goto')[0];
- if(target == 'previous') {
- target = game.ui.previousPage;
- }
- game.ui.moveToPage(target);
- });
-});
-game.ui.root.querySelectorAll('.options .controls input, .options .graphics input, .options .feather input, .options .accessibility input, .options .accessibility select').forEach((elem) => {
- elem.addEventListener('change', () => {
- applySettings(game);
- if(elem.name == 'upInTheAirGame-controls') {
- game.ui.root.querySelector('.controls .leftside').style.display = (['touchpad', 'thumbstick'].includes(game.settings['controls'])) ? 'block' : 'none';
- game.ui.root.querySelectorAll('.options .controls p span:not(.' + game.settings['controls'] + ')').forEach(span => span.style.display = 'none');
- game.ui.root.querySelector('.options .controls span.' + game.settings['controls']).style.display = 'block';
- } else if(elem.value == 'highcontrast' || elem.name == 'upInTheAirGame-graphics') {
- createMeshes(game);
- } else if(elem.name == 'upInTheAirGame-feather') {
- createFeather(game);
- }
- });
-});
-game.ui.root.querySelectorAll('.ui-page .audio input[type=range]').forEach((elem) => {
- elem.addEventListener('input', (e) => {
- let audioCategory = Array.from(e.target.classList).filter(v => ['music', 'sounds'].includes(v))[0];
- game.ui.root.querySelector('.ui-page.options .audio input[type=range].' + audioCategory).value = e.target.value;
- applySettings(game);
- });
-});
-game.ui.root.querySelectorAll('.options .audio button').forEach((btn) => {
- btn.addEventListener('click', (e) => {
- if(e.target.classList.contains('music')) {
- if(game.view.music.isPlaying) {
- game.view.music.stop();
- if(game.view.music.timeoutID) {
- clearTimeout(game.view.music.timeoutID);
- delete game.view.music.timeoutID;
- }
- } else {
- game.view.music.offset = 36;
- game.view.music.play();
- game.view.music.timeoutID = setTimeout(() => {
- game.view.music.stop();
- }, 6000);
- }
- } else if(e.target.classList.contains('sounds')) {
- playRandomSound(game);
- }
- });
-});
-game.ui.root.querySelectorAll('.options .keyboard label button').forEach((btn) => {
- btn.addEventListener('click', () => {
- if(game.ui.root.querySelector('.ui-page.keyboard-modal')) {
- return;
- }
- const keyboardModal = document.createElement('div');
- keyboardModal.classList.add('ui-page', 'keyboard-modal');
- const instruction = document.createElement('span');
- const direction = btn.classList[0];
- keyboardModal.classList.add(direction);
- instruction.innerText = 'Please press the key for “' + direction[0].toUpperCase() + direction.slice(1) + '”';
- keyboardModal.appendChild(instruction);
- game.ui.root.appendChild(keyboardModal);
- });
-});
-game.ui.root.querySelector('.options .keyboard button[value="reset"]').addEventListener('click', (e) => {
- const container = e.target.parentNode;
- container.querySelector('button.up').value = 'ArrowUp|w';
- container.querySelector('button.right').value = 'ArrowRight|d';
- container.querySelector('button.down').value = 'ArrowDown|s';
- container.querySelector('button.left').value = 'ArrowLeft|a';
- applySettings(game);
- loadSettings(game);
-});
-game.ui.root.querySelectorAll('.ui-page .areatabs button').forEach((btn) => {
- btn.addEventListener('click', (e) => {
- btn.parentNode.querySelectorAll('button').forEach((otherBtn) => {
- otherBtn.classList.remove('active');
- let val = otherBtn.classList[0];
- otherBtn.closest('.ui-page').querySelector('div.' + val).style.display = 'none';
- });
- btn.classList.add('active');
- let val = Array.from(btn.classList).filter(c => c != 'active')[0];
- btn.closest('.ui-page').querySelector('div.' + val).style.display = 'flex';
- });
-});
-game.ui.moveToPage = (target, skipFade = false) => {
+function moveToPage(game, target, skipFade = false) {
let fadeDuration = 250;
if(skipFade) {
fadeDuration = 0;
game.ui.reachedStart = true;
}, fadeDuration);
}
+ if(target == 'title') {
+ game.cheatBuffer = '';
+ }
if(target == 'title' && game.ui.root.querySelector('.ui-page.gameplay p')) {
game.ui.root.querySelectorAll('.ui-page.gameplay p').forEach(elem => elem.remove());
}
if(game.view) {
game.startTime = game.view.clock.getElapsedTime();
}
+}
+
+function unlockWithKey(game, input) {
+ input = 'aBYPmb2xCwF2ilfD'+ input + 'PNHFwI2zKZejUv6c';
+ let hash = (input) => {
+ // Adapted with appreciation from bryc:
+ // https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
+ let h1 = 0xdeadbeef, h2 = 0x41c6ce57;
+ for(let i = 0, ch; i < input.length; i++) {
+ ch = input.charCodeAt(i);
+ h1 = Math.imul(h1 ^ ch, 2654435761);
+ h2 = Math.imul(h2 ^ ch, 1597334677);
+ }
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
+ h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
+ h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
+ return (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
+ }
+ for(let unlockable of game.unlockables) {
+ if(unlockable.accessKey == hash(input)) {
+ let key = hash('UZx2jWen9w5jm0FB' + input + '7DZpEq4OOwv2kiJ1');
+ let seed = parseInt(key.slice(12), 16);
+ let prng = () => {
+ seed |= 0;
+ seed = seed + 0x9e3779b9 | 0;
+ let t = seed ^ seed >>> 16;
+ t = Math.imul(t, 0x21f0aaad);
+ t = t ^ t >>> 15;
+ t = Math.imul(t, 0x735a2d97);
+ return ((t = t ^ t >>> 15) >>> 0);
+ };
+ let data = Uint8Array.from(atob(unlockable.payload), (c) => c.codePointAt(0));
+ let pad;
+ for(let i = 0; i < data.length; i++) {
+ if(i % 4 == 0) {
+ pad = prng();
+ pad = [pad % 256, (pad >> 8) % 256, (pad >> 16) % 256, (pad >> 24) % 256];
+ }
+ data[i] = data[i] ^ pad[i % 4];
+ }
+ data = new TextDecoder().decode(data);
+ let result = JSON.parse(data);
+ if(result['type'] == 'redirect') {
+ return unlockWithKey(game, result['target']);
+ } else {
+ return result;
+ }
+ }
+ }
+ return null;
};
+
+window['game'] = {
+ state: 'loadingAssets',
+ ui: {
+ root: document.querySelector('.game-upintheair .ui-container'),
+ gamepads: [],
+ },
+ settings: {},
+ // If you're looking at the source code and the following seems scary, don't worry, it's just a few
+ // unlockable feather textures. I liked easter eggs and cheat codes when I was young, and I didn't
+ // want these to be trivially bypassable for people who can read the code.
+ unlockables: [
+ {
+ 'accessKey': '5b32eb7ad08488f4',
+ 'payload': 'k4JPu3sWfEhcgleieVGghixKSI10qdRRC5tAl39Tzy1U7Rx9EEEYbLx9wCcxAf7wC8r9mJZCOj8bNa7grMbUmTeCeWWPAg==',
+ },
+ {
+ 'accessKey': '6273da5894b2dd8b',
+ 'payload': 'XAFLhAjcTzsWgGb7DAMCKwHXjoyUg/yxkIUyLcV/7PpktgW3MHtXhh4OkVeANr52RfdbwfVpgO4dxuyYPaFZ4x4JDmI=',
+ },
+ {
+ 'accessKey': 'fb8b533451a6dd68',
+ 'payload': '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',
+ },
+ {
+ 'accessKey': '3c78e25c7b1106b6',
+ 'payload': 'dnbO28tXI2i2+o+etuRc0/Cfxd4UpdG1IRgqB0fTwJE1xCoK+TtB/JVGTquaD/stnIkKiA==',
+ },
+ {
+ 'accessKey': '5f79141f861a146a',
+ 'payload': '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',
+ },
+ {
+ 'accessKey': 'c2cfaaebb10f00ab',
+ 'payload': 'CAVH/4AwEJzNt950XEAZP3Q92fMNasIje6K5FSBgjqchkxmrFxjlNHjadnrHWdqM+zrB',
+ },
+ {
+ 'accessKey': '130b037dadbe1d7a',
+ 'payload': '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',
+ },
+ {
+ 'accessKey': 'd8e8dd84f4b0c103',
+ 'payload': 'EGKsJYSjVVaxCBWPRUGjWuLMl3k7fB/7uKYp8wz28r/5XTaOJF7LnbPMpBwysAR8IR/whArG',
+ },
+ ],
+};
+
+loadSettings(window['game']);
+applySettings(window['game']);
+
+game.ui.root.querySelectorAll('button.goto').forEach((btn) => {
+ btn.addEventListener('click', (e) => {
+ if(!game.view.music) {
+ game.view.audioListener = new THREE.AudioListener();
+ game.view.camera.add(game.view.audioListener);
+ game.view.music = new THREE.Audio(game.view.audioListener);
+ game.view.music.setBuffer(game.assets['audio']['music-' + game.settings['audio']['theme']]);
+ game.view.music.setVolume(game.settings['audio']['music']);
+ game.view.windSound = new THREE.Audio(game.view.audioListener);
+ game.view.windSound.setBuffer(game.assets['audio']['wind']);
+ game.view.windSound.setVolume(game.settings['audio']['sounds']);
+ game.view.windSound.setLoop(false);
+ }
+ let btn = e.target.closest('button');
+ let target = Array.from(btn.classList).filter(c => c != 'goto')[0];
+ if(target == 'previous') {
+ target = game.ui.previousPage;
+ }
+ moveToPage(game, target);
+ });
+});
+game.ui.root.querySelectorAll('.options .controls input, .options .graphics input, .options .feather input, .options .accessibility input, .options .accessibility select').forEach((elem) => {
+ elem.addEventListener('change', () => {
+ applySettings(game);
+ if(elem.name == 'upInTheAirGame-controls') {
+ game.ui.root.querySelector('.controls .leftside').style.display = (['touchpad', 'thumbstick'].includes(game.settings['controls'])) ? 'block' : 'none';
+ game.ui.root.querySelectorAll('.options .controls p span:not(.' + game.settings['controls'] + ')').forEach(span => span.style.display = 'none');
+ game.ui.root.querySelector('.options .controls span.' + game.settings['controls']).style.display = 'block';
+ } else if(elem.value == 'highcontrast' || elem.name == 'upInTheAirGame-graphics') {
+ createMeshes(game);
+ } else if(elem.name == 'upInTheAirGame-feather') {
+ createFeather(game);
+ }
+ });
+});
+game.ui.root.querySelectorAll('.ui-page .audio input[type=range]').forEach((elem) => {
+ elem.addEventListener('input', (e) => {
+ let audioCategory = Array.from(e.target.classList).filter(v => ['music', 'sounds'].includes(v))[0];
+ game.ui.root.querySelector('.ui-page.options .audio input[type=range].' + audioCategory).value = e.target.value;
+ applySettings(game);
+ });
+});
+game.ui.root.querySelectorAll('.options .audio button').forEach((btn) => {
+ btn.addEventListener('click', (e) => {
+ if(e.target.classList.contains('music')) {
+ if(game.view.music.isPlaying) {
+ game.view.music.stop();
+ if(game.view.music.timeoutID) {
+ clearTimeout(game.view.music.timeoutID);
+ delete game.view.music.timeoutID;
+ }
+ } else {
+ game.view.music.offset = 36;
+ game.view.music.play();
+ game.view.music.timeoutID = setTimeout(() => {
+ game.view.music.stop();
+ }, 6000);
+ }
+ } else if(e.target.classList.contains('sounds')) {
+ playRandomSound(game);
+ }
+ });
+});
+game.ui.root.querySelectorAll('.options .keyboard label button').forEach((btn) => {
+ btn.addEventListener('click', () => {
+ if(game.ui.root.querySelector('.ui-page.keyboard-modal')) {
+ return;
+ }
+ const keyboardModal = document.createElement('div');
+ keyboardModal.classList.add('ui-page', 'keyboard-modal');
+ const instruction = document.createElement('span');
+ const direction = btn.classList[0];
+ keyboardModal.classList.add(direction);
+ instruction.innerText = 'Please press the key for “' + direction[0].toUpperCase() + direction.slice(1) + '”';
+ keyboardModal.appendChild(instruction);
+ game.ui.root.appendChild(keyboardModal);
+ });
+});
+game.ui.root.querySelector('.options .keyboard button[value="reset"]').addEventListener('click', (e) => {
+ const container = e.target.parentNode;
+ container.querySelector('button.up').value = 'ArrowUp|w';
+ container.querySelector('button.right').value = 'ArrowRight|d';
+ container.querySelector('button.down').value = 'ArrowDown|s';
+ container.querySelector('button.left').value = 'ArrowLeft|a';
+ applySettings(game);
+ loadSettings(game);
+});
+game.ui.root.querySelectorAll('.ui-page .areatabs button').forEach((btn) => {
+ btn.addEventListener('click', (e) => {
+ btn.parentNode.querySelectorAll('button').forEach((otherBtn) => {
+ otherBtn.classList.remove('active');
+ let val = otherBtn.classList[0];
+ otherBtn.closest('.ui-page').querySelector('div.' + val).style.display = 'none';
+ });
+ btn.classList.add('active');
+ let val = Array.from(btn.classList).filter(c => c != 'active')[0];
+ btn.closest('.ui-page').querySelector('div.' + val).style.display = 'flex';
+ });
+});
game.ui.root.style.fontSize = (game.ui.root.clientWidth / 50) + 'px';
window.addEventListener('resize', () => {
game.ui.root.style.fontSize = (game.ui.root.clientWidth / 50) + 'px';
}
let bbox = game.ui.root.querySelector('canvas').getBoundingClientRect();
if(bbox.bottom < -100 || bbox.top - bbox.height > 100 || bbox.left + bbox.width < -100 || bbox.left - window.innerWidth > 100) {
- game.ui.moveToPage('pause', true);
+ moveToPage(game, 'pause', true);
}
});
window.addEventListener('blur', () => {
if(['gameplay', 'openingcutscene', 'endingcutscene'].includes(game.ui.currentPage)) {
- game.ui.moveToPage('pause', true);
+ moveToPage(game, 'pause', true);
}
});
document.addEventListener('keydown', (e) => {
e.stopPropagation();
return;
}
+ if(game.ui.currentPage == 'title' && e.key.match(/[a-z]/)) {
+ game.cheatBuffer = (game.cheatBuffer + e.key).slice(-25);
+ for(let len = 10; len <= 25; len++) {
+ if(game.cheatBuffer.length < len) {
+ break;
+ }
+ let unlock = unlockWithKey(game, game.cheatBuffer.slice(-len));
+ if(unlock && unlock['type'] == 'feather' && !game.settings['unlocks'].includes(unlock['name'])) {
+ playRandomSound(game);
+ unlockFeather(game, unlock['name'], unlock['url']);
+ moveToPage(game, 'unlock');
+ }
+ }
+ return;
+ }
if(e.key == 'Escape') {
if(['gameplay', 'openingcutscene', 'endingcutscene'].includes(game.ui.currentPage)) {
- game.ui.moveToPage('pause', true);
+ moveToPage(game, 'pause', true);
} else if(game.ui.currentPage == 'pause') {
- game.ui.moveToPage(game.ui.previousPage, true);
+ moveToPage(game, game.ui.previousPage, true);
}
}
});
game.ui.root.querySelector('.ui-page.pause button.title').addEventListener('click', () => {
reset(game);
});
-loadAllAssets(game, (progress) => {
+
+loadAllAssets(window['game'], (progress) => {
let percentage = Math.floor(100 * progress);
game.ui.root.querySelector('.ui-page.loading progress').value = percentage;
game.ui.root.querySelector('.ui-page.loading span').innerText = percentage;
snippet.childNodes[1].textContent = ' ' + audioTheme[0].toUpperCase() + audioTheme.slice(1);
container.appendChild(snippet);
}
- game.ui.moveToPage('title');
- init(window['game'], game.ui.root.querySelector('canvas'));
+ moveToPage(game, 'title');
+ initializeGame(window['game'], game.ui.root.querySelector('canvas'));
}, (err) => {
console.error(err);
});