Up-in-the-Air – commitdiff

You can use Git to clone the repository via the web URL. Download snapshot (zip)
First launch controls detection
authorJulian Fietkau <git@fietkau.software>
Sun, 29 Sep 2024 15:59:43 +0000 (17:59 +0200)
committerJulian Fietkau <git@fietkau.software>
Sun, 29 Sep 2024 15:59:43 +0000 (17:59 +0200)
index.html
main.js

index 281a753fd5bd4b5b9b7e9ac60c65fe916ff08969..fa3e03b0a7b9a550b3a2cfafa5ba5a8b808866c1 100644 (file)
@@ -74,6 +74,7 @@
     position: relative;
     width: min(calc(100cqh - 2 * var(--game-margin)), calc(100cqw));
     aspect-ratio: 1 / 1;
+    background: #fff;
     font-size-adjust: 0.4;
     image-rendering: pixelated;
     box-shadow: 1px 1px 3px #000a;
     flex-grow: 0;
     flex-shrink: 0;
   }
+  .ui-page.controls {
+    background-color: #fff;
+    color: #444;
+    padding: 1em;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    font-size: 1.5em;
+    text-align: center;
+  }
+  .ui-page.controls p {
+    margin: .15em auto;
+    max-width: 23em;
+  }
+  .ui-page.controls p:last-child {
+    font-size: .85;
+  }
+  .ui-page.controls svg {
+    margin: 1em;
+    max-height: 8em;
+  }
+  .ui-page.controls svg path {
+    fill: currentColor;
+  }
+  .ui-page.controls button {
+    margin: 1em;
+    border: 1px solid #444;
+    box-shadow: 0 2px #444;
+    padding: .6em 1em;
+    background: #fff;
+    color: #444;
+    font-family: unset;
+    font-size: 1em;
+  }
+  .ui-page.controls button:hover {
+    background: #eee;
+    border: 1px solid #444;
+  }
+  .ui-page.controls button:active {
+    background: #ddd;
+    border: 1px solid #444;
+  }
   .ui-page.options .feather {
     flex-grow: 1;
     display: flex;
 <progress value="0" max="100"></progress>
 <div><span>0</span> %</div>
 </div>
+<div class="ui-page controls">
+<p class="mouse">You appear to be using a device with a <strong>mouse or touchpad</strong> and an on-screen cursor.</p>
+<p class="touchpad">You appear to be using a device with a <strong>touch screen</strong>.</p>
+<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+<path class="mouse" d="m16 2c-1.654 0-3 1.346-3 3v23c0 1.654 1.346 3 3 3h11.96v3h-1.955c-0.553 0-1 0.447-1 1v4c0 0.553 0.447 1 1 1h12c0.553 0 1-0.447 1-1v-4c0-0.553-0.447-1-1-1h-1.955v-3h11.96c1.654 0 3-1.346 3-3v-23c0-1.654-1.346-3-3-3h-32zm0 2h32c0.552 0 1 0.448 1 1v18h-2v-15c0-1.103-0.897-2-2-2h-26c-1.103 0-2 0.897-2 2v15h-2v-18c0-0.552 0.448-1 1-1zm3 4h26v15h-6v2h10v3c0 0.552-0.448 1-1 1h-32c-0.552 0-1-0.448-1-1v-3h18v-2h-14v-15zm16 15v2h2v-2h-2zm-5.045 8h4.09v3h-4.09v-3zm-2.955 5h10v2h-10v-2zm-22 6c-1.654 0-3 1.346-3 3v14c0 1.654 1.346 3 3 3h36c1.654 0 3-1.346 3-3v-14c0-1.654-1.346-3-3-3h-23v2h23c0.552 0 1 0.448 1 1v14c0 0.552-0.448 1-1 1h-36c-0.552 0-1-0.448-1-1v-14c0-0.552 0.448-1 1-1h7v-2h-7zm9 0v2h2v-2h-2zm40.94 0c-3.827 0-6.939 3.112-6.939 6.939v6.121c0 3.827 3.112 6.939 6.939 6.939h0.1211c3.827 0 6.938-3.113 6.938-6.941v-6.119c0-3.827-3.112-6.939-6.939-6.939h-0.1191zm0 2h0.1191c2.724 0 4.939 2.214 4.939 4.939v6.119c0 2.724-2.214 4.939-4.939 4.939h-0.1191c-2.724 0-4.939-2.214-4.939-4.939v-6.119c0-2.724 2.215-4.939 4.939-4.939zm-48.94 2v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm17 0c-0.553 0-1 0.447-1 1v2c0 0.553 0.447 1 1 1s1-0.447 1-1v-2c0-0.553-0.447-1-1-1zm-49 5v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h2v-2h-2zm-32 5v2h2v-2h-2zm4 0v2h2v-2h-2zm4 0v2h18v-2h-18zm20 0v2h2v-2h-2zm4 0v2h2v-2h-2z"/>
+<path class="touchpad" d="m16 2c-1.654 0-3 1.346-3 3v54c0 1.654 1.346 3 3 3h30c1.654 0 3-1.346 3-3v-33h2c1.654 0 3-1.346 3-3v-12c0-1.654-1.346-3-3-3h-2v-3c0-1.654-1.346-3-3-3zm0 2h30c0.551 0 1 0.448 1 1v3h-14c-1.654 0-3 1.346-3 3h-15v-6c0-0.552 0.449-1 1-1zm17 6h18c0.551 0 1 0.448 1 1v12c0 0.552-0.449 1-1 1h-9c-0.265 0-0.519 0.105-0.707 0.293l-4.293 4.293v-3.586c0-0.553-0.448-1-1-1h-3c-0.551 0-1-0.448-1-1v-12c0-0.552 0.449-1 1-1zm1 2v2h8v-2zm10 0v2h2v-2zm4 0v2h2v-2zm-33 1h15v10c0 1.654 1.346 3 3 3h2v5c0 0.404 0.2442 0.7698 0.6172 0.9238 0.124 0.052 0.2538 0.07617 0.3828 0.07617 0.261 0 0.516-0.102 0.707-0.293l5.707-5.707h4.586v18h-19v2h19v13c0 0.552-0.449 1-1 1h-30c-0.551 0-1-0.448-1-1v-13h7v-2h-7zm19 3v2h16v-2zm0 4v2h16v-2zm-10 24v2h2v-2zm7 4c-2.757 0-5 2.243-5 5s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zm0 2c1.654 0 3 1.346 3 3s-1.346 3-3 3-3-1.346-3-3 1.346-3 3-3zm-1 2v2h2v-2z"/>
+</svg>
+<p>Setting gameplay controls to: <strong><span class="mouse">Mouse movement</span><span class="touchpad">Virtual touchpad</span></strong></p>
+<p>If this is incorrect or you have other preferences, different game control settings are available in the options menu.</p>
+<button class="goto title">Continue</button>
+<p>This message will only be shown on first launch.</p>
+</div>
 <div class="ui-page title">
 <h1>Up in the Air</h1>
 <button class="goto openingcutscene">Start Game</button>
 <p>“<a href="https://www.brailleinstitute.org/freefont/" target="_blank">Atkinson Hyperlegible</a>” by <a href="https://www.brailleinstitute.org/" target="_blank">Braille Institute of America, Inc.</a></p>
 <p>“<a href="https://opendyslexic.org/" target="_blank">OpenDyslexic</a>” by <a href="https://abbiegonzalez.com/" target="_blank">Abbie Gonzalez</a> (<a href="https://hackers.town/@antijingoist" target="_blank">@antijingoist@hackers.town</a>)</p>
 <p>Logo: “<a href="https://www.fontspace.com/precious-font-f7252" target="_blank">Precious</a>” by Bolt Cutter Design</p>
+<h4>Icons</h4>
+<p><a href="https://www.iconfinder.com/icons/2941610/computer_desktop_keyboard_monitor_mouse_icon" target="_blank">Computer</a> and <a href="https://www.iconfinder.com/icons/2990368/chat_communication_mobile_phone_smartphone_icon" target="_blank">Mobile</a> by <a href="https://www.iconfinder.com/zirconicusso" target="_blank">Zirsolostudio</a> (via <a href="https://www.iconfinder.com/" target="_blank">Iconfinder</a>)</p>
 <h4>Engine</h4>
 <p><a href="https://threejs.org/" target="_blank">three.js</a> v169 by mrdoob and contributors</p>
 <h4>Inspiration</h4>
diff --git a/main.js b/main.js
index 198d3dfec2863944d379a5934990b2b96d73e0cc..5a0aef70b593de786fbb4938c88782d5a3f0719c 100644 (file)
--- a/main.js
+++ b/main.js
@@ -1074,7 +1074,7 @@ function animate(game, scene) {
 
 function loadSettings(game) {
   let settings = {
-    'controls': 'mouse',
+    'controls': null, // set during first time launch depending on device
     'virtualinputleft': false,
     'graphics': 1,
     'audio': {
@@ -1102,7 +1102,7 @@ function loadSettings(game) {
   if(stored) {
     let merge = (source, target) => {
       for(let k of Object.keys(source)) {
-        if(typeof(source[k]) == 'object' && typeof(target[k]) == 'object' && !Array.isArray(target[k])) {
+        if(source[k] != null && typeof(source[k]) == 'object' && typeof(target[k]) == 'object' && !Array.isArray(target[k])) {
           merge(source[k], target[k]);
         } else if(k in target) {
           target[k] = source[k];
@@ -1113,11 +1113,13 @@ function loadSettings(game) {
     merge(stored, settings);
   }
   const ui = game.ui.root.querySelector('.ui-page.options');
-  ui.querySelector('.controls input[value="' + settings['controls'] + '"]').checked = true;
-  ui.querySelector('.controls .leftside input').checked = settings['virtualinputleft'];
-  ui.querySelector('.controls .leftside').style.display = (['touchpad', 'thumbstick'].includes(settings['controls'])) ? 'block' : 'none';
-  ui.querySelectorAll('.controls p span:not(.' + settings['controls'] + ')').forEach(span => span.style.display = 'none');
-  ui.querySelector('.controls span.' + settings['controls']).style.display = 'block';
+  if(settings['controls']) {
+    ui.querySelector('.controls input[value="' + settings['controls'] + '"]').checked = true;
+    ui.querySelector('.controls .leftside input').checked = settings['virtualinputleft'];
+    ui.querySelector('.controls .leftside').style.display = (['touchpad', 'thumbstick'].includes(settings['controls'])) ? 'block' : 'none';
+    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])));
@@ -1193,7 +1195,9 @@ function loadSettings(game) {
 
 function applySettings(game) {
   const ui = game.ui.root.querySelector('.ui-page.options');
-  game.settings['controls'] = ui.querySelector('input[name="upInTheAirGame-controls"]:checked').value;
+  if(ui.querySelector('input[name="upInTheAirGame-controls"]:checked')) {
+    game.settings['controls'] = ui.querySelector('input[name="upInTheAirGame-controls"]:checked').value;
+  }
   game.settings['virtualinputleft'] = ui.querySelector('.controls .leftside input').checked;
   if(game.settings['virtualinputleft']) {
     game.ui.root.parentNode.classList.add('virtual-input-left');
@@ -1636,15 +1640,18 @@ function moveToPage(game, target, skipFade = false) {
     }, fadeDuration);
   }
   if(target == 'title') {
-    game.cheatBuffer = '';
+    game.ui.cheatBuffer = '';
+  }
+  if(target == 'title' && (!game.ui.currentPage || ['loading', 'controls'].includes(game.ui.currentPage))) {
+    initializeGame(game, game.ui.root.querySelector('canvas'));
   }
   if(target == 'title' && game.ui.root.querySelector('.ui-page.gameplay p')) {
     game.ui.root.querySelectorAll('.ui-page.gameplay p').forEach(elem => elem.remove());
   }
-  if(target == 'title' && game.view && game.view.windSound.isPlaying) {
+  if(target == 'title' && game.view && game.view.windSound && game.view.windSound.isPlaying) {
     game.view.windSound.stop();
   }
-  if(target == 'title' && game.view && game.view.music.isPlaying) {
+  if(target == 'title' && game.view && game.view.music && game.view.music.isPlaying) {
     game.view.music.stop();
     if(game.view.music.timeoutID) {
       clearTimeout(game.view.music.timeoutID);
@@ -1776,7 +1783,7 @@ function moveToPage(game, target, skipFade = false) {
     });
   }
   const targetElems = [game.ui.root.querySelector('.ui-page.' + target + '')];
-  if(game.ui.root.querySelector('.ui-page.gameplay').style.opacity != '1') {
+  if(game.ui.root.querySelector('.ui-page.gameplay').style.opacity != '1' && target == 'title') {
     targetElems.push(game.ui.root.querySelector('.ui-page.gameplay'));
   }
   for(let targetElem of targetElems) {
@@ -1916,7 +1923,7 @@ applySettings(window['game']);
 
 game.ui.root.querySelectorAll('button.goto').forEach((btn) => {
   btn.addEventListener('click', (e) => {
-    if(!game.view.music) {
+    if(game.view && !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);
@@ -2046,12 +2053,12 @@ document.addEventListener('keydown', (e) => {
     return;
   }
   if(game.ui.currentPage == 'title' && e.key.match(/[a-z]/)) {
-    game.cheatBuffer = (game.cheatBuffer + e.key).slice(-25);
+    game.ui.cheatBuffer = (game.ui.cheatBuffer + e.key).slice(-25);
     for(let len = 10; len <= 25; len++) {
-      if(game.cheatBuffer.length < len) {
+      if(game.ui.cheatBuffer.length < len) {
         break;
       }
-      let unlock = unlockWithKey(game, game.cheatBuffer.slice(-len));
+      let unlock = unlockWithKey(game, game.ui.cheatBuffer.slice(-len));
       if(unlock && unlock['type'] == 'feather' && !game.settings['unlocks'].includes(unlock['name'])) {
         playRandomSound(game);
         unlockFeather(game, unlock['name'], unlock['url']);
@@ -2088,6 +2095,20 @@ loadAllAssets(window['game'], (progress) => {
   if(window.location.hostname == 'fietkau.media' && window.location.pathname == '/up_in_the_air') {
     game.ui.root.querySelector('.ui-page.title .footer span:last-child').remove();
   }
+  let controlsInterstitial = false;
+  if(!game.settings['controls']) {
+    controlsInterstitial = true;
+    let control;
+    if(matchMedia('(hover: hover)').matches) {
+      control = 'mouse';
+    } else {
+      control = 'touchpad';
+    }
+    game.ui.root.querySelector('.controls input[value="' + control + '"]').checked = true;
+    applySettings(game);
+    loadSettings(game);
+    game.ui.root.querySelectorAll('.ui-page.controls .' + ((control == 'mouse') ? 'touchpad' : 'mouse')).forEach(elem => elem.remove());
+  }
   if(!game.assets.audiothemes.includes(game.settings['audio']['theme'])) {
     game.settings['audio']['theme'] = game.assets.audiothemes[0];
   }
@@ -2108,8 +2129,11 @@ loadAllAssets(window['game'], (progress) => {
     snippet.childNodes[1].textContent = ' ' + audioTheme[0].toUpperCase() + audioTheme.slice(1);
     container.appendChild(snippet);
   }
-  moveToPage(game, 'title');
-  initializeGame(window['game'], game.ui.root.querySelector('canvas'));
+  if(controlsInterstitial) {
+    moveToPage(game, 'controls');
+  } else {
+    moveToPage(game, 'title');
+  }
 }, (err) => {
   console.error(err);
 });