Up-in-the-Air – commitdiff

You can use Git to clone the repository via the web URL. Download snapshot (zip)
Load and save settings
authorJulian Fietkau <git@fietkau.software>
Tue, 24 Sep 2024 12:22:40 +0000 (14:22 +0200)
committerJulian Fietkau <git@fietkau.software>
Tue, 24 Sep 2024 12:22:40 +0000 (14:22 +0200)
index.html
main.js

index ea9c40a6272e9433f0a8a626e511c676516d4ed2..f6ab7b4b68947fdc1aaf92fba432722088a5c891 100644 (file)
         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:&nbsp;&nbsp;&nbsp;<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:&nbsp;&nbsp;&nbsp;<select class="gameplayspeed">
-<option value="100" selected>100 %</option>
+<p><label>Gameplay speed:&nbsp;&nbsp;&nbsp;<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>
diff --git a/main.js b/main.js
index 047843f55dbb1aded5f158fe9082c334dff797ad..7d7314c20a0117a5507b8d44f05c100e54da289b 100644 (file)
--- a/main.js
+++ b/main.js
@@ -603,12 +603,136 @@ function animate(game, renderer, scene) {
   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) {
@@ -624,15 +748,9 @@ game.ui.root.querySelectorAll('button.goto').forEach((btn) => {
 });
 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) => {