drop-shadow(.1em .1em 0 #fff)
drop-shadow(-.1em .1em 0 #fff);
}
+ .ui-page.options .feather label:nth-child(1) { z-index: 50; }
+ .ui-page.options .feather label:nth-child(2) { z-index: 49; }
+ .ui-page.options .feather label:nth-child(3) { z-index: 48; }
+ .ui-page.options .feather label:nth-child(4) { z-index: 47; }
+ .ui-page.options .feather label:nth-child(5) { z-index: 46; }
+ .ui-page.options .feather label:nth-child(6) { z-index: 45; }
+ .ui-page.options .feather label:nth-child(7) { z-index: 44; }
+ .ui-page.options .feather label:nth-child(8) { z-index: 43; }
+ .ui-page.options .feather label:nth-child(9) { z-index: 42; }
+ .ui-page.options .feather label:nth-child(10) { z-index: 41; }
.ui-page.loading {
display: flex;
flex-direction: column;
flex-direction: column;
gap: 1ex;
}
- .ui-page.options .keyboard label:not(:nth-child(8)) {
- width: 8em;
+ .ui-page.options .keyboard div:nth-child(2) label {
+ width: 10em;
margin: 0 auto;
display: flex;
justify-content: flex-end;
}
.ui-page.options .keyboard label button {
box-sizing: border-box;
- width: 5em;
+ margin: .7ex;
+ width: 6em;
}
.ui-page .audio input[type=range] {
width: 8em;
<div class="column">
<div class="controls">
<h3>Controls</h3>
-<p><label><input type="radio" name="upInTheAirGame-controls" value="mouse" checked> Mouse movement</label></p>
+<p><label><input type="radio" name="upInTheAirGame-controls" value="mouse"> Mouse movement</label></p>
<p><label><input type="radio" name="upInTheAirGame-controls" value="touchpad"> Virtual touchpad</label></p>
<p><label><input type="radio" name="upInTheAirGame-controls" value="thumbstick"> Virtual thumbstick</label></p>
<p><label><input type="radio" name="upInTheAirGame-controls" value="keyboard"> Keyboard</label></p>
<div class="graphics">
<h3>Graphics</h3>
<p>Quality:
-<label><input type="radio" name="upInTheAirGame-font" value="standard" checked> Full</label>
-<label><input type="radio" name="upInTheAirGame-font" value="standard" checked> Reduced</label>
-<label><input type="radio" name="upInTheAirGame-font" value="standard" checked> Minimal</label>
+<label><input type="radio" name="upInTheAirGame-graphics" value="1"> Full</label>
+<label><input type="radio" name="upInTheAirGame-graphics" value="2"> Reduced</label>
+<label><input type="radio" name="upInTheAirGame-graphics" value="3"> Minimal</label>
</div>
<div class="audio">
<h3>Audio</h3>
<h3>Feather Customization</h3>
<p>You can change the feather’s visual appearance. This is an aesthetic choice with no impact on the game mechanics.</p>
<div class="feather">
-<label><input type="radio" name="upInTheAirGame-feather" value="blue" checked><img src="textures/feather.png" alt="Blue feather"></label>
+<label><input type="radio" name="upInTheAirGame-feather" value="blue"><img src="textures/feather.png" alt="Blue feather"></label>
<label><input type="radio" name="upInTheAirGame-feather" value="red"><img src="textures/feather.png" alt="Red feather"></label>
<label><input type="radio" name="upInTheAirGame-feather" value="green"><img src="textures/feather.png" alt="Green feather"></label>
<label><input type="radio" name="upInTheAirGame-feather" value="black"><img src="textures/feather.png" alt="Black feather"></label>
<label><input type="checkbox" value="highcontrast"> High contrast mode</label>
<p class="annotation">Render collectibles as bright green dots, replace clouds with dark background.</p>
</div>
-<div>
+<div class="font">
<h3>Font</h3>
-<p><label class="standardfont"><input type="radio" name="upInTheAirGame-font" value="standard" checked> Standard</label></p>
+<p><label class="standardfont"><input type="radio" name="upInTheAirGame-font" value="standard"> Standard</label></p>
<p><label class="atkinson"><input type="radio" name="upInTheAirGame-font" value="atkinson"> Atkinson Hyperlegible</label></p>
<p><label class="opendyslexic"><input type="radio" name="upInTheAirGame-font" value="opendyslexic"> OpenDyslexic</label></p>
</div>
<div class="difficulty">
<h3>Difficulty</h3>
<p><label>Collecting radius: <select class="collectingradius">
-<option value="standard" selected>Standard</option>
-<option value="generous">Generous</option>
-<option value="eager">Eager</option>
+<option value="1">Standard</option>
+<option value="2">Generous</option>
+<option value="3">Eager</option>
</select></label></p>
<p class="annotation">This setting adjusts how close you need to get to a collectible in order to pick it up.</p>
-<p><label>Gameplay speed: <select class="gameplayspeed">
-<option value="100" selected>100 %</option>
+<p><label>Gameplay speed: <select class="speed">
+<option value="100">100 %</option>
<option value="50">50 %</option>
<option value="25">25 %</option>
<option value="10">10 %</option>
</div>
</div>
-<div class="column">
-<div class="keyboard">
+<div class="column keyboard">
+<div>
<h3>Keyboard Settings</h3>
<p>These settings only have an effect if keyboard controls are enabled.</p>
-<label>Up: <button class="up" value="Arrow up|w">🠕 or W</button></label>
-<label>Right: <button class="right" value="Arrow right|d">🠖 or D</button></label>
-<label>Down: <button class="down" value="Arrow down|s">🠗 or S</button></label>
-<label>Left: <button class="left" value="Arrow left|a">🠔 or A</button></label>
+</div>
+<div>
+<label>Up: <button class="up" value="ArrowUp|w">🠕 or W</button></label>
+<label>Right: <button class="right" value="ArrowRight|d">🠖 or D</button></label>
+<label>Down: <button class="down" value="ArrowDown|s">🠗 or S</button></label>
+<label>Left: <button class="left" value="ArrowLeft|a">🠔 or A</button></label>
<button value="reset">Reset to default</button>
+</div>
+<div>
<label><input type="checkbox" value="tapmode"> Tap mode</label>
<p class="annotation">With this setting enabled, movement gets triggered by tapping the keys instead of holding them. Tap a direction multiple times to accelerate, tap the opposite direction to stop.</p>
</div>
renderer.render(scene, game.view.camera);
}
+function loadSettings(game) {
+ let settings = {
+ 'controls': 'mouse',
+ 'graphics': 1,
+ 'audio': {
+ 'music': 0.5,
+ 'sounds': 0.5,
+ },
+ 'feather': 'blue',
+ 'unlocks': [],
+ 'highcontrast': false,
+ 'font': 'standard',
+ 'difficulty': {
+ 'collectingradius': 1,
+ 'speed': 100,
+ },
+ 'keyboard': {
+ 'up': ['ArrowUp', 'w'],
+ 'right': ['ArrowRight', 'd'],
+ 'down': ['ArrowDown', 's'],
+ 'left': ['ArrowLeft', 'a'],
+ 'tapmode': false,
+ },
+ };
+ let stored = window['localStorage'].getItem('upInTheAirGameSettings');
+ if(stored) {
+ let merge = (source, target) => {
+ for(let k of Object.keys(source)) {
+ if(typeof(source[k]) == 'object' && typeof(target[k]) == 'object') {
+ merge(source[k], target[k]);
+ } else if(k in target) {
+ target[k] = source[k];
+ }
+ }
+ };
+ stored = JSON.parse(stored);
+ merge(stored, settings);
+ }
+ const ui = game.ui.root.querySelector('.ui-page.options');
+ ui.querySelector('.controls input[value="' + settings['controls'] + '"]').checked = true;
+ ui.querySelectorAll('.controls p span:not(.' + settings['controls'] + ')').forEach(span => span.style.display = 'none');
+ ui.querySelector('.controls span.' + settings['controls']).style.display = 'block';
+ ui.querySelector('.graphics input[value="' + settings['graphics'] + '"]').checked = true;
+ for(let audioCategory of ['music', 'sounds']) {
+ let newValue = Math.max(0, Math.min(100, Math.round(100 * settings['audio'][audioCategory])));
+ game.ui.root.querySelectorAll('.ui-page .audio input[type=range].' + audioCategory).forEach((elem) => {
+ elem.value = newValue;
+ elem.parentNode.nextElementSibling.innerText = newValue;
+ });
+ }
+ for(let unlockedFeather of settings['unlocks']) {
+ let label = document.createElement('label');
+ let radio = document.createElement('input');
+ radio.type = 'radio';
+ radio.name = 'upInTheAirGame-feather';
+ radio.value = unlockedFeather.toLowerCase().replace(/ /g, '');
+ label.appendChild(radio);
+ let img = document.createElement('img');
+ img.src = 'textures/feather-' + radio.value + '.png';
+ img.alt = unlockedFeather + ' feather';
+ label.appendChild(img);
+ ui.querySelector('.feather').appendChild(label);
+ }
+ ui.querySelector('.feather input[value=' + settings['feather'] + ']').checked = true;
+ ui.querySelector('input[value="highcontrast"]').checked = !!settings['highcontrast'];
+ ui.querySelector('.font input[value=' + settings['font'] + ']').checked = true;
+ ui.querySelector('.difficulty select.collectingradius option[value="' + settings['difficulty']['collectingradius'] + '"]').selected = true;
+ ui.querySelector('.difficulty select.speed option[value="' + settings['difficulty']['speed'] + '"]').selected = true;
+ for(let direction of ['up', 'right', 'down', 'left']) {
+ let keys = settings['keyboard'][direction];
+ let btn = ui.querySelector('.keyboard button.' + direction);
+ btn.value = keys.join('|');
+ keys = keys.map(k => {
+ if(k.length == 1) {
+ k = k.toUpperCase();
+ }
+ switch(k) {
+ case 'ArrowUp': return '🠕';
+ case 'ArrowRight': return '🠖';
+ case 'ArrowDown': return '🠗';
+ case 'ArrowLeft': return '🠔';
+ default: return k;
+ }
+ });
+ btn.innerText = keys.join(' or ');
+ }
+ ui.querySelector('input[value="tapmode"]').checked = !!settings['tapmode'];
+ game.settings = settings;
+}
+
+function applySettings(game) {
+ const ui = game.ui.root.querySelector('.ui-page.options');
+ game.settings['controls'] = ui.querySelector('input[name="upInTheAirGame-controls"]:checked').value;
+ game.settings['graphics'] = parseInt(ui.querySelector('input[name="upInTheAirGame-graphics"]:checked').value, 10);
+ for(let audioCategory of ['music', 'sounds']) {
+ game.settings['audio'][audioCategory] = parseInt(ui.querySelector('.audio input[type=range].' + audioCategory).value, 10) / 100;
+ }
+ game.settings['feather'] = ui.querySelector('input[name="upInTheAirGame-feather"]:checked').value;
+ game.settings['highcontrast'] = ui.querySelector('input[value="highcontrast"]').checked;
+ game.settings['font'] = ui.querySelector('input[name="upInTheAirGame-font"]:checked').value;
+ game.settings['difficulty']['collectingradius'] = parseInt(ui.querySelector('.difficulty select.collectingradius').value, 10);
+ game.settings['difficulty']['speed'] = parseInt(ui.querySelector('.difficulty select.speed').value, 10);
+ for(let direction of ['up', 'right', 'down', 'left']) {
+ game.settings['keyboard'][direction] = ui.querySelector('.keyboard button.' + direction).value.split('|');
+ }
+ game.settings['keyboard']['tapmode'] = ui.querySelector('input[value="tapmode"]').checked;
+ window['localStorage'].setItem('upInTheAirGameSettings', JSON.stringify(game.settings));
+ for(let audioCategory of ['music', 'sounds']) {
+ game.settings['audio'][audioCategory] = parseInt(ui.querySelector('.audio input[type=range].' + audioCategory).value, 10) / 100;
+ let value = Math.round(100 * game.settings['audio'][audioCategory]);
+ game.ui.root.querySelectorAll('.ui-page .audio input[type=range].' + audioCategory).forEach((elem) => {
+ elem.value = value;
+ elem.parentNode.nextElementSibling.innerText = value;
+ });
+ if(audioCategory == 'music') {
+ game.view.music.setVolume(game.settings['audio'][audioCategory]);
+ }
+ }
+}
+
window['game'] = {
state: 'loadingAssets',
ui: {
root: document.querySelector('.game-upintheair'),
},
+ settings: {},
};
+loadSettings(window['game']);
+applySettings(window['game']);
+
game.ui.root.querySelectorAll('button.goto').forEach((btn) => {
btn.addEventListener('click', (e) => {
if(!game.view.music) {
});
game.ui.root.querySelectorAll('.ui-page .audio input[type=range]').forEach((elem) => {
elem.addEventListener('input', (e) => {
- let newValue = e.target.value;
- let soundType = Array.from(e.target.classList).filter(v => ['music', 'sounds'].includes(v))[0];
- game.ui.root.querySelectorAll('.ui-page .audio input[type=range].' + soundType).forEach((elem) => {
- elem.value = newValue;
- elem.parentNode.nextElementSibling.innerText = newValue;
- });
- if(soundType == 'music') {
- game.view.music.setVolume(parseInt(e.target.value, 10) / 100);
- }
+ 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) => {