Up-in-the-Air – commitdiff

You can use Git to clone the repository via the web URL. Download snapshot (zip)
Final version of intro and outro cutscenes
authorJulian Fietkau <git@fietkau.software>
Fri, 27 Sep 2024 23:39:29 +0000 (01:39 +0200)
committerJulian Fietkau <git@fietkau.software>
Fri, 27 Sep 2024 23:39:29 +0000 (01:39 +0200)
index.html
main.js

index a207b355350ae69847fee32c59ec352c2258889f..fda855d0bcca3e8e3b7cea627892755021773417 100644 (file)
     background-position: left;
     padding-left: 1.2em;
   }
+  .ui-page.gameplay p {
+    position: absolute;
+    font-family: 'Cookie';
+    font-size: 1em;
+    text-shadow: -.15em -.15em 0 #fff, .15em -.15em 0 #fff, .15em .15em 0 #fff, -.15em .15em 0 #fff, 0 -.19em 0 #fff, .19em 0 0 #fff, 0 .19em 0 #fff, -.19em 0 0 #fff;
+  }
   .ui-page.credits {
     padding: 1em;
     display: flex;
diff --git a/main.js b/main.js
index 0f81ed57a8d26c0cee49586b7697b13c3a4db2b2..686dd4b28d51328c585f9fde7c5841d069377604 100644 (file)
--- a/main.js
+++ b/main.js
@@ -27,7 +27,9 @@ function playRandomSound(game) {
   let sound = new THREE.Audio(game.view.audioListener);
   sound.setBuffer(game.assets['audio']['sound' + index + '-' + game.settings['audio']['theme']]);
   sound.setVolume(game.settings['audio']['sounds']);
-  sound.play();
+  if(game.settings['audio']['sounds'] > 0) {
+    sound.play();
+  }
 }
 
 function easeInOut(val) {
@@ -46,6 +48,7 @@ function loadAllAssets(game, renderProgressCallback) {
   ];
   return new Promise((resolve, reject) => {
     let todoList = {
+      'audio/wind.ogg': 37866,
       'fonts/cookie.json': 37866,
       'textures/cloud0a.png': 568,
       'textures/cloud0b.png': 569,
@@ -93,6 +96,7 @@ function loadAllAssets(game, renderProgressCallback) {
     }
     game.assets.audiothemes = [];
     const audioThemes = {
+      'jazz': [1636930, 34002, 34629, 25399, 16426, 26122],
       'classical': [1636930, 34002, 34629, 25399, 16426, 26122],
     }
     for(let theme of Object.keys(audioThemes)) {
@@ -451,6 +455,10 @@ function init(game, canvas) {
   game.var.pinwheelDistance = new THREE.Vector3();
   game.var.notCollectedPos = new THREE.Vector3();
   game.var.collectedPos = new THREE.Vector3();
+  game.var.endingEntryTrajectory = new THREE.Vector3();
+  game.var.endingExitTrajectory = new THREE.Vector3();
+  game.var.endingEntryRotation = new THREE.Vector3();
+  game.var.endingExitRotation = new THREE.Vector3();
   renderer.setAnimationLoop(() => { animate(game, renderer, scene); });
 }
 
@@ -680,18 +688,67 @@ function animate(game, renderer, scene) {
       if(game.ui.reachedEnd) {
         reset(game);
       }
+      if(game.timeProgress < 0.1 && !game.view.windSound.isPlaying) {
+        game.view.windSound.stop();
+        game.objects.feather.position.set(cameraX - 8.45, -game.courseRadius - 6.4, -9.9);
+        game.objects.feather.rotation.set(Math.PI, 0, Math.PI / 2.1);
+      }
+      if(game.timeProgress > 1.0 && game.timeProgress <= 1.1 && !game.ui.root.querySelector('.gameplay p')) {
+        const lines = ['Why are the simplest words…', '…often the most difficult to write?'];
+        for(let line of lines) {
+          let elem = document.createElement('p');
+          elem.innerText = line;
+          elem.style.left = (5 + 6 * lines.indexOf(line)) + 'em';
+          elem.style.top = (6 + 4 * lines.indexOf(line)) + 'em';
+          elem.style.opacity = '0';
+          document.querySelector('.ui-page.gameplay').appendChild(elem);
+        }
+        if(!game.view.windSound.isPlaying) {
+          game.view.windSound.setVolume(game.settings['audio']['sounds']);
+          game.view.windSound.offset = 0;
+          game.view.windSound.play();
+        }
+      }
+      if(game.timeProgress > 1.0 && game.timeProgress <= 5.0) {
+        game.ui.root.querySelectorAll('.ui-page.gameplay p').forEach((elem) => {
+          let opacity = Math.max(0.0, Math.min(1.0, game.timeProgress - (elem.nextSibling ? 1.0 : 3.5)));
+          elem.style.transform = 'translateX(' + (Math.pow(1.0 - opacity, 2) * 10).toFixed(2) + 'em)';
+          elem.style.opacity = opacity.toFixed(2);
+        });
+      }
+      if(game.timeProgress >= 1.0 && game.timeProgress <= 3.0) {
+        let windStrength = 0.5 * (1 + Math.cos((game.timeProgress - 2.0) * Math.PI));
+        game.objects.feather.position.x = cameraX - 8.45 + 0.15 * windStrength;
+        game.objects.feather.rotation.z = Math.PI / 2.1 - 0.2 * windStrength;
+      }
+      if(game.timeProgress >= 3.0) {
+        let windStrength = Math.max(0.5 * (1 + Math.cos((game.timeProgress - 4.0) * Math.PI)), Math.min(2 * (game.timeProgress - 4.2), 1.2));
+        game.objects.feather.position.x = cameraX - 8.45 + 0.15 * windStrength + 13.45 * Math.min(1.0, Math.max(0.0, 1 - Math.pow(Math.max(0.0, 6.0 - game.timeProgress), 2)));
+        game.objects.feather.position.y = -game.courseRadius - 6.4 + 6.4 * easeInOut(Math.min(1,Math.max(0, game.timeProgress - 5.0) / 2));
+        game.objects.feather.position.y += (Math.cos((Math.max(5.0, Math.min(8.0, game.timeProgress)) - 5.0) * 2 * Math.PI / 3) - 1) * 2 * Math.sin(game.timeProgress * 2);
+        game.objects.feather.position.z = -9.9 + 9.9 * Math.min(1.0, Math.max(0.0, 1 - Math.pow(Math.max(0.0, 6.0 - game.timeProgress), 2)));
+        game.objects.feather.rotation.z = Math.PI / 2.1 - 0.2 * windStrength + 1.45 * Math.max(0.0, game.timeProgress - 5.0);
+        game.objects.feather.rotation.x = Math.PI + Math.max(0.0, game.timeProgress - 4.5) * Math.sin(Math.pow(game.timeProgress - 4.5, 2));
+      }
+      if(game.timeProgress > 7.0) {
+        game.ui.root.querySelectorAll('.ui-page.gameplay p').forEach((elem) => {
+          let opacity = Math.max(0.0, Math.min(1.0, 8.0 - game.timeProgress));
+          elem.style.opacity = opacity.toFixed(2);
+        });
+      }
       cameraSwayFactor = cameraSwayFactor * (1 - (game.timeProgress / 8));
       cameraX = -5 + Math.pow(Math.max(0, game.timeProgress - 3) / 5, 1.6) * 5;
 
-      game.objects.feather.position.setX(cameraX - 8.45 + 12 * Math.min(1, easeInOut(Math.max(0, game.timeProgress - 4) / 4)));
-      game.objects.feather.position.setY(-game.courseRadius - 6.4 + 4 * Math.min(1, easeInOut(Math.max(0, game.timeProgress - 4) / 4)));
-      game.objects.feather.position.setZ(-9.9 + 9.9 * Math.min(1, easeInOut(Math.max(0, game.timeProgress - 4) / 4)));
-      if(game.timeProgress > 6 && game.timeProgress < 7) {
+      if(game.timeProgress > 6.0 && game.timeProgress < 7.0 && game.settings['controls'] != 'mouse') {
         game.controls.positionX = 0;
         game.controls.positionY = -3;
       }
       game.objects.pinwheel.material[4].opacity = easeInOut(Math.max(0, (game.timeProgress - 7)));
-      if(game.timeProgress >= 8) {
+      if(game.timeProgress >= 8.0) {
+        game.ui.root.querySelectorAll('.ui-page.gameplay p').forEach((elem) => {
+          let opacity = Math.max(0.0, Math.min(1.0, 8.0 - game.timeProgress));
+          elem.style.opacity = opacity.toFixed(2);
+        });
         game.view.music.offset = 0;
         game.view.music.play();
         game.ui.moveToPage('gameplay', true);
@@ -699,11 +756,15 @@ function animate(game, renderer, scene) {
     } else if(game.ui.currentPage == 'endingcutscene') {
       cameraSwayFactor = cameraSwayFactor * game.timeProgress / 8;
       cameraX = 5 - Math.pow(Math.max(0, 5 - game.timeProgress) / 5, 1.6) * 5;
-      game.objects.feather.rotation.x = -0.2;
-      game.objects.feather.rotation.z = -0.2;
-      game.objects.feather.position.setX(11.2 * Math.min(1, easeInOut(1 - Math.max(0, 4 - game.timeProgress) / 4)));
-      game.objects.feather.position.setY(-game.courseRadius - 7.6 * Math.min(1, easeInOut(1 - Math.max(0, 4 - game.timeProgress) / 4)));
-      game.objects.feather.position.setZ(-8.9 * Math.min(1, easeInOut(1 - Math.max(0, 4 - game.timeProgress) / 4)));
+      let trajectoryLerpValue = easeInOut(Math.min(6.0, game.timeProgress) / 6);
+      game.var.endingEntryTrajectory.addScaledVector(game.objects.feather.speed, delta);
+      game.var.endingExitTrajectory.setX(11.2 * easeInOut(Math.min(1, 1 - Math.max(0, 4 - game.timeProgress) / 4)));
+      game.var.endingExitTrajectory.setY(-game.courseRadius - 7.6 * easeInOut(Math.min(1, 1 - Math.max(0, 4 - game.timeProgress) / 4)));
+      game.var.endingExitTrajectory.setZ(-8.9 * easeInOut(Math.min(1, 1 - Math.max(0, 4 - game.timeProgress) / 4)));
+      game.objects.feather.rotation.x = lerp(game.var.endingEntryRotation.x, game.var.endingExitRotation.x, trajectoryLerpValue);
+      game.objects.feather.rotation.y = lerp(game.var.endingEntryRotation.y, game.var.endingExitRotation.y, trajectoryLerpValue);
+      game.objects.feather.rotation.z = lerp(game.var.endingEntryRotation.z, game.var.endingExitRotation.z, trajectoryLerpValue);
+      game.objects.feather.position.lerpVectors(game.var.endingEntryTrajectory, game.var.endingExitTrajectory, trajectoryLerpValue);
       game.objects.pinwheel.material[4].opacity = easeInOut(Math.max(0, (1 - game.timeProgress)));
       if(!game.settings['highcontrast']) {
         let letterScale = lerp(0.3, 0.0, easeInOut(Math.max(0, Math.min(1, game.timeProgress - 6))));
@@ -749,8 +810,16 @@ function animate(game, renderer, scene) {
     renderer.render(scene, game.view.camera);
     return;
   }
+  if(game.ui.root.querySelector('.ui-page.gameplay p')) {
+    game.ui.root.querySelectorAll('.ui-page.gameplay p').forEach(elem => elem.remove());
+  }
   if(game.timeProgress / game.timeTotal >= 1.0) {
     game.ui.reachedEnd = true;
+    game.var.endingEntryTrajectory.set(game.objects.feather.position.x, game.objects.feather.position.y, game.objects.feather.position.z);
+    game.var.endingEntryRotation.x = game.objects.feather.rotation.x;
+    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);
   }
 
@@ -1400,6 +1469,10 @@ game.ui.root.querySelectorAll('button.goto').forEach((btn) => {
       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];
@@ -1507,6 +1580,12 @@ game.ui.moveToPage = (target, skipFade = false) => {
       game.ui.reachedStart = true;
     }, fadeDuration);
   }
+  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) {
+    game.view.windSound.stop();
+  }
   if(target == 'title' && game.view && game.view.music.isPlaying) {
     game.view.music.stop();
     if(game.view.music.timeoutID) {
@@ -1548,6 +1627,13 @@ game.ui.moveToPage = (target, skipFade = false) => {
   }
   if(target == 'pause') {
     game.view.music.stop();
+    game.view.windSound.stop();
+  } else if(game.ui.currentPage == 'pause' && target == 'openingcutscene') {
+    if(game.timeProgress >= 1.0) {
+      game.view.windSound.offset = game.timeProgress - 1.0;
+      game.view.windSound.setVolume(game.settings['audio']['sounds']);
+      game.view.windSound.play();
+    }
   } else if(game.ui.currentPage == 'pause' && target == 'gameplay') {
     game.view.music.offset = (game.timeProgress / (game.settings['difficulty']['speed'] / 100)) % game.assets['audio']['music-' + game.settings['audio']['theme']].duration;
     game.view.music.play();