Up-in-the-Air – commitdiff

You can use Git to clone the repository via the web URL. Download snapshot (zip)
Improvements to title screen and opening animation
authorJulian Fietkau <git@fietkau.software>
Sun, 22 Sep 2024 11:44:52 +0000 (13:44 +0200)
committerJulian Fietkau <git@fietkau.software>
Sun, 22 Sep 2024 11:44:52 +0000 (13:44 +0200)
fonts/cookie.woff2 [new file with mode: 0644]
index.html
main.js

diff --git a/fonts/cookie.woff2 b/fonts/cookie.woff2
new file mode 100644 (file)
index 0000000..280019a
Binary files /dev/null and b/fonts/cookie.woff2 differ
index ad15c8467d30744eacbac03e8149ab2a24c6b7cb..416176e1dd9f44c9925921552a5f8ca66214baf2 100644 (file)
@@ -5,6 +5,10 @@
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>FediJam</title>
 <style>
+  @font-face {
+    font-family: 'Cookie';
+    src: url('fonts/cookie.woff2') format('woff2');
+  }
   body {
     font-family: sans-serif;
     box-sizing: border-box;
@@ -16,7 +20,7 @@
     justify-content: center;
     align-items: center;
   }
-  .game {
+  .game-upintheair {
     position: relative;
     width: min(calc(100vh - 4rem), calc(100vw));
     height: min(calc(100vh - 4rem), calc(100vw));
     align-items: center;
     gap: 1ex;
     opacity: 1;
+    font-size: 1.3rem;
+  }
+  .ui-page.loading img {
+    display: block;
+    margin-bottom: 1rem;
+    animation: loading-pinwheel-spin 2s linear infinite;
+  }
+  @keyframes loading-pinwheel-spin {
+    100% {
+      transform:rotate(360deg);
+    }
   }
   .ui-page.title {
+    padding: 3em;
     display: flex;
     flex-direction: column;
-    justify-content: center;
-    align-items: center;
-    gap: 1ex;
+    justify-content: flex-start;
+    align-items: end;
+    gap: 2em;
+  }
+  .ui-page.title h1 {
+    margin: 0 0 .7em;
+    padding: 0;
+    font-size: 4em;
+  }
+  .ui-page.title button {
+    width: 8em;
+    padding: 1.5ex 2ex;
+    font-size: 2.5em;
+    font-family: Cookie;
   }
   .ui-page.title .footer {
     position: absolute;
     right: 0;
     bottom: 0;
+    padding: .7ex;
     display: flex;
     flex-direction: column;
     align-items: end;
-    font-size: .7em;
-    padding: .7ex;
+    font-size: 1em;
+    text-shadow: 0 0 .07em #fff;
+  }
+  .ui-page.gameplay, .ui-page.openingcutscene {
+    cursor: none;
   }
   canvas {
     box-sizing: border-box;
@@ -66,7 +97,6 @@
     height: 100% !important;
     border: 1px solid #000;
     margin: 0 auto;
-    cursor: none;
   }
   .options {
     position: relative;
 </script>
 </head>
 <body>
-<div class="game">
+<div class="game-upintheair">
 <div class="ui-page loading">
+<img src="textures/pinwheel.png" alt="Spinning red pinwheel loading animation">
 <progress value="0" max="100"></progress>
 <div><span>0</span> %</div>
 </div>
 <div class="ui-page title">
 <h1>Up in the Air</h1>
-<div>
 <button class="startGame">Start game</button>
-</div>
+<button class="options">Options</button>
 <div class="footer">
 <span>Version: dev</span>
 <span>A game for <a href="https://itch.io/jam/fedi-jam" target="_blank">FediJam 2024</a></span>
diff --git a/main.js b/main.js
index c858c09020439a7174062fa31460703b24d473f1..881ce842be6eff11dd9bbcc0769965eff5480c98 100644 (file)
--- a/main.js
+++ b/main.js
@@ -116,26 +116,6 @@ function applyForceToFeather(game, vector) {
 }
 
 function start(game) {
-
-  function pinwheelPositionUpdate(game, viewportX, viewportY) {
-    const vFOV = THREE.MathUtils.degToRad(game.view.camera.fov);
-    const viewportHeight = 2 * Math.tan(vFOV / 2) * (game.view.camera.position.z - game.objects.pinwheel.position.z);
-    game.objects.pinwheel.cameraX = viewportHeight * viewportX;
-    game.objects.pinwheel.cameraY = - viewportHeight * viewportY;
-  }
-
-  function cursorMoveEvent(canvasLocalX, canvasLocalY) {
-    const canvasBb = game.view.canvas.getBoundingClientRect();
-    // Intentional division by height instead of width in the following line, since
-    // three.js controls the vertical FOV. So if we ever change the aspect ratio from 1:1,
-    // y will still be in range (-0.5, 0.5), but the range for x will be smaller or larger.
-    const x = (canvasLocalX - canvasBb.x - (canvasBb.width / 2)) / canvasBb.height;
-    const y = (canvasLocalY - canvasBb.y - (canvasBb.height / 2)) / canvasBb.height;
-    pinwheelPositionUpdate(game, x, y);
-  }
-
-  document.body.addEventListener('mousemove', e => cursorMoveEvent(e.clientX, e.clientY));
-  document.body.addEventListener('touchmove', e => cursorMoveEvent(e.touches[0].clientX, e.touches[0].clientY));
 }
 
 function init(game, canvas) {
@@ -191,7 +171,8 @@ function init(game, canvas) {
   const pinwheelMaterial = new THREE.MeshPhongMaterial({
     map: game.assets.textures.pinwheel,
     transparent: true,
-    alphaTest: 0.5,
+    alphaTest: 0.01,
+    opacity: 0.0,
   });
   game.objects.pinwheel = new THREE.Mesh(pinwheelGeometry, [null, null, null, null, pinwheelMaterial, null]);
   game.objects.pinwheel.position.setY(2);
@@ -362,6 +343,26 @@ function init(game, canvas) {
 
   game.view.camera.position.set(-5, -game.courseRadius, game.view.camera.position.z);
 
+  function pinwheelPositionUpdate(game, viewportX, viewportY) {
+    const vFOV = THREE.MathUtils.degToRad(game.view.camera.fov);
+    const viewportHeight = 2 * Math.tan(vFOV / 2) * (game.view.camera.position.z - game.objects.pinwheel.position.z);
+    game.objects.pinwheel.cameraX = viewportHeight * viewportX;
+    game.objects.pinwheel.cameraY = - viewportHeight * viewportY;
+  }
+
+  function cursorMoveEvent(canvasLocalX, canvasLocalY) {
+    const canvasBb = game.view.canvas.getBoundingClientRect();
+    // Intentional division by height instead of width in the following line, since
+    // three.js controls the vertical FOV. So if we ever change the aspect ratio from 1:1,
+    // y will still be in range (-0.5, 0.5), but the range for x will be smaller or larger.
+    const x = (canvasLocalX - canvasBb.x - (canvasBb.width / 2)) / canvasBb.height;
+    const y = (canvasLocalY - canvasBb.y - (canvasBb.height / 2)) / canvasBb.height;
+    pinwheelPositionUpdate(game, x, y);
+  }
+
+  document.body.addEventListener('mousemove', e => cursorMoveEvent(e.clientX, e.clientY));
+  document.body.addEventListener('touchmove', e => cursorMoveEvent(e.touches[0].clientX, e.touches[0].clientY));
+
   // All vectors used by the game loop (no allocations inside)
   game.var = {};
   game.var.featherLocalPos = new THREE.Vector3();
@@ -380,6 +381,10 @@ function animate(game, renderer, scene) {
   let delta = Math.min(game.view.clock.getDeltaTime(), 1 / 12);
   game.timeProgress = (game.timeProgress + delta);
 
+  game.objects.pinwheel.rotation.z -= 5 * delta;
+  game.objects.pinwheel.position.x = game.view.camera.position.x + game.objects.pinwheel.cameraX;
+  game.objects.pinwheel.position.y = game.view.camera.position.y + game.objects.pinwheel.cameraY;
+
   if(game.ui.currentPage != 'gameplay') {
     let cameraSwayFactor = 1;
     let cameraX = 0;
@@ -394,14 +399,14 @@ function animate(game, renderer, scene) {
       game.objects.feather.position.setX(-11.45 + 12 * Math.min(1, easeInOut(Math.max(0, game.timeProgress - 4) / 4)));
       game.objects.feather.position.setY(-game.courseRadius - 4.2 + 4 * Math.min(1, easeInOut(Math.max(0, game.timeProgress - 4) / 4)));
       game.objects.feather.position.setZ(-6.6 + 6.6 * Math.min(1, easeInOut(Math.max(0, game.timeProgress - 4) / 4)));
+      game.objects.pinwheel.material[4].opacity = easeInOut(Math.max(0, (game.timeProgress - 7)));
       if(game.timeProgress >= 8) {
-        game.ui.currentPage = 'gameplay';
-        game.timeProgress = 0;
+        game.ui.moveToPage('gameplay', true);
         start(game);
       }
     }
-    game.view.camera.position.setY(-game.courseRadius + 0.03 * cameraSwayFactor * Math.sin(game.view.clock.getElapsedTime() * 0.5));
-    game.view.camera.position.setX(cameraX + 0.015 * cameraSwayFactor * Math.sin(game.view.clock.getElapsedTime() * 0.7));
+    game.view.camera.position.setY(-game.courseRadius + 0.07 * cameraSwayFactor * Math.sin(game.view.clock.getElapsedTime() * 0.5));
+    game.view.camera.position.setX(cameraX + 0.05 * cameraSwayFactor * Math.sin(game.view.clock.getElapsedTime() * 0.7));
     renderer.render(scene, game.view.camera);
     return;
   }
@@ -461,9 +466,6 @@ function animate(game, renderer, scene) {
 
   game.objects.feather.twistSpeed = Math.min(0.13, game.objects.feather.twistSpeed);
   game.objects.feather.rotation.x = (game.objects.feather.rotation.x + game.objects.feather.twistSpeed) % (2 * Math.PI);
-  game.objects.pinwheel.rotation.z -= 5 * delta;
-  game.objects.pinwheel.position.x = game.view.camera.position.x + game.objects.pinwheel.cameraX;
-  game.objects.pinwheel.position.y = game.view.camera.position.y + game.objects.pinwheel.cameraY;
 
   let collectedScale = lerp(0.6, 0.3, 1 - Math.pow(1 - game.objects.words.collectedCount / game.objects.words.length, 2));
   for(let i = 0; i < game.objects.words.length; i++) {
@@ -526,11 +528,14 @@ document.querySelector('.startGame').addEventListener('click', () => {
 window['game'] = {
   state: 'loadingAssets',
   ui: {
-    root: document.querySelector('.game'),
+    root: document.querySelector('.game-upintheair'),
   },
 };
-game.ui.moveToPage = (target) => {
-  const fadeDuration = 250;
+game.ui.moveToPage = (target, skipFade = false) => {
+  let fadeDuration = 250;
+  if(skipFade) {
+    fadeDuration = 0;
+  }
   // After the gameplay page is shown for the first time, always keep it around as a backdrop
   game.ui.root.querySelectorAll('.ui-page:not(.' + target + '):not(.gameplay)').forEach((page) => {
     page.style.opacity = '0';
@@ -557,14 +562,21 @@ game.ui.moveToPage = (target) => {
     game.startTime = game.view.clock.getElapsedTime();
   }
 };
+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';
+});
 loadAllAssets(game, (progress) => {
   let percentage = Math.floor(100 * progress);
-  document.querySelector('.ui-page.loading progress').value = percentage;
-  document.querySelector('.ui-page.loading span').innerText = percentage;
+  game.ui.root.querySelector('.ui-page.loading progress').value = percentage;
+  game.ui.root.querySelector('.ui-page.loading span').innerText = percentage;
 }).then(() => {
+  if(window.location.hostname == 'fietkau.media') {
+    game.ui.root.querySelector('.ui-page.title .footer span:last-child').remove();
+  }
   game.ui.moveToPage('title');
-  init(window['game'], document.querySelector('canvas'));
-  document.querySelector('#enableMusic').addEventListener('change', () => toggleMusic(window['game']));
+  init(window['game'], game.ui.root.querySelector('canvas'));
+  game.ui.root.querySelector('#enableMusic').addEventListener('change', () => toggleMusic(window['game']));
 }, (err) => {
   console.error(err);
 });