better intro implementation and suspense handling

This commit is contained in:
ad044 2022-07-25 19:14:48 +04:00
parent 91caca2d41
commit 1512996968
5 changed files with 117 additions and 124 deletions

View file

@ -20,7 +20,7 @@ const Loading = () => {
}); });
return ( return (
<> <group position={[0, 0, 3]}>
<sprite scale={[5, 5, 5]} renderOrder={999}> <sprite scale={[5, 5, 5]} renderOrder={999}>
<spriteMaterial color={0x000000} depthTest={false} /> <spriteMaterial color={0x000000} depthTest={false} />
</sprite> </sprite>
@ -38,7 +38,7 @@ const Loading = () => {
> >
<spriteMaterial map={lifeInstinct} depthTest={false} /> <spriteMaterial map={lifeInstinct} depthTest={false} />
</sprite> </sprite>
</> </group>
); );
}; };

View file

@ -1,8 +1,7 @@
import React, { Suspense, useEffect, useMemo } from "react"; import React, { useEffect, useMemo } from "react";
import { a, useSpring } from "@react-spring/three"; import { a, useSpring } from "@react-spring/three";
import { useStore } from "@/store"; import { useStore } from "@/store";
import Levels from "./Levels"; import Levels from "./Levels";
import Loading from "@canvas/objects/Loading";
import { FlattenedSiteLayout, MainSubscene, NodeID } from "@/types"; import { FlattenedSiteLayout, MainSubscene, NodeID } from "@/types";
import { getLevelY } from "@/utils/site"; import { getLevelY } from "@/utils/site";
import { getRotationForSegment } from "@/utils/site"; import { getRotationForSegment } from "@/utils/site";
@ -106,18 +105,16 @@ const Site = (props: SiteProps) => {
}, [siteLayout]); }, [siteLayout]);
return ( return (
<Suspense fallback={props.introFinished ? <Loading /> : null}> <a.group rotation-x={tiltState.tilt}>
<a.group rotation-x={tiltState.tilt}> <a.group rotation-x={rotationSpring.x}>
<a.group rotation-x={rotationSpring.x}> <a.group rotation-y={rotationSpring.y} position-y={positionSpring.y}>
<a.group rotation-y={rotationSpring.y} position-y={positionSpring.y}> <Levels
<Levels flattenedLayout={layout}
flattenedLayout={layout} activateAllLevels={props.introFinished}
activateAllLevels={props.introFinished} />
/>
</a.group>
</a.group> </a.group>
</a.group> </a.group>
</Suspense> </a.group>
); );
}; };

View file

@ -1,4 +1,4 @@
import React, { Suspense, useEffect, useMemo, useRef, useState } from "react"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { useStore } from "@/store"; import { useStore } from "@/store";
import { playAudio } from "@/utils/audio"; import { playAudio } from "@/utils/audio";
import LevelSelection from "@canvas/objects/MainScene/LevelSelection"; import LevelSelection from "@canvas/objects/MainScene/LevelSelection";
@ -8,12 +8,15 @@ import MiddleRing from "@canvas/objects/MainScene/MiddleRing";
import Starfield from "@canvas/objects/MainScene/Starfield"; import Starfield from "@canvas/objects/MainScene/Starfield";
import Site from "@canvas/objects/MainScene/Site"; import Site from "@canvas/objects/MainScene/Site";
import Lain from "@canvas/objects/MainScene/Lain"; import Lain from "@canvas/objects/MainScene/Lain";
import Loading from "@canvas/objects/Loading";
import usePrevious from "@/hooks/usePrevious"; import usePrevious from "@/hooks/usePrevious";
import { a, useSpring } from "@react-spring/three"; import {
a,
useSpring,
AnimationResult,
} from "@react-spring/three";
import Pause from "@canvas/objects/MainScene/Pause"; import Pause from "@canvas/objects/MainScene/Pause";
import GrayPlane from "@canvas/objects/MainScene/GrayPlane"; import GrayPlane from "@canvas/objects/MainScene/GrayPlane";
import { MainSubscene, Position } from "@/types"; import { MainSubscene, Position, Rotation } from "@/types";
import { handleEvent } from "@/core"; import { handleEvent } from "@/core";
import { import {
resetInputCooldown, resetInputCooldown,
@ -79,18 +82,14 @@ const MainScene = () => {
} }
}, [wordSelected]); }, [wordSelected]);
const introWrapperRef = useRef<THREE.Group>(null);
useEffect(() => { useEffect(() => {
if (intro) { if (intro) {
playAudio("snd_32.mp4");
setStarfieldIntro(false); setStarfieldIntro(false);
setLainIntroAnim(false); setLainIntroAnim(false);
setIntroFinished(false); setIntroFinished(false);
starfieldIntroRef.current = false;
lainIntroRef.current = false;
introFinishedRef.current = false;
handleEvent(setInputCooldown(-1)); handleEvent(setInputCooldown(-1));
} else { } else {
handleEvent(resetInputCooldown); handleEvent(resetInputCooldown);
@ -98,49 +97,10 @@ const MainScene = () => {
}, [intro]); }, [intro]);
const [starfieldIntro, setStarfieldIntro] = useState(false); const [starfieldIntro, setStarfieldIntro] = useState(false);
const starfieldIntroRef = useRef(false);
const [lainIntroAnim, setLainIntroAnim] = useState(false); const [lainIntroAnim, setLainIntroAnim] = useState(false);
const lainIntroRef = useRef(false);
const [introFinished, setIntroFinished] = useState(false); const [introFinished, setIntroFinished] = useState(false);
const introFinishedRef = useRef(false);
useFrame((_, delta) => { useFrame((_, delta) => {
if (!introFinished && intro && introWrapperRef.current) {
if (introWrapperRef.current.position.z === -10) playAudio("snd_32.mp4");
if (
Math.round(introWrapperRef.current.position.z) === -3 &&
!starfieldIntroRef.current
) {
setStarfieldIntro(true);
starfieldIntroRef.current = true;
}
if (
Math.round(introWrapperRef.current.position.z) === -1 &&
!lainIntroRef.current
) {
setLainIntroAnim(true);
lainIntroRef.current = true;
}
if (introWrapperRef.current.position.z < 0) {
introWrapperRef.current.position.z += 2.5 * delta;
}
if (introWrapperRef.current.rotation.x > 0) {
introWrapperRef.current.rotation.x -= 0.4 * delta;
}
if (
!introFinishedRef.current &&
introWrapperRef.current.rotation.x < 0 &&
introWrapperRef.current.position.z > 0
) {
introFinishedRef.current = true;
setIntroFinished(true);
handleEvent(resetInputCooldown);
}
}
if (grayPlaneGroupRef.current) { if (grayPlaneGroupRef.current) {
grayPlaneGroupRef.current.rotation.y -= delta / 1.5; grayPlaneGroupRef.current.rotation.y -= delta / 1.5;
} }
@ -175,67 +135,99 @@ const MainScene = () => {
) )
); );
const introSpring = useSpring<{
from: { position: Position; rotation: Rotation };
to: { position: Position; rotation: Rotation };
}>({
from: {
position: [0, 0, intro ? -8 : 0],
rotation: [intro ? Math.PI / 2 : 0, 0, 0],
},
to: {
rotation: [0, 0, 0],
position: [0, 0, 0],
},
config: { duration: 3000 },
onChange: (result: AnimationResult) => {
const { position } = result.value as {
position: Position;
rotation: Rotation;
};
if (!starfieldIntro && position[2] > -4) {
setStarfieldIntro(true);
}
if (!lainIntroAnim && position[2] > -1) {
setLainIntroAnim(true);
}
},
onRest: () => {
setIntroFinished(true);
handleEvent(resetInputCooldown);
},
});
return ( return (
<group position-z={3}> <group position-z={3}>
<Suspense fallback={<Loading />}> <LevelSelection />
<LevelSelection /> <Pause />
<Pause /> <group position={[-0.85, -0.7, 0]} scale={[0.85, 0.85, 0]}>
<group position={[-0.85, -0.7, 0]} scale={[0.85, 0.85, 0]}> <group position={[1, 0.6, 0]} scale={[1.2, 1.2, 0]}>
<group position={[1, 0.6, 0]} scale={[1.2, 1.2, 0]}> <Prompt />
<Prompt />
</group>
<About />
<PermissionDenied />
<SaveStatusDisplay />
</group> </group>
<NotFound /> <About />
<a.group <PermissionDenied />
visible={intro ? introFinished : true} <SaveStatusDisplay />
position={bgSpring.position} </group>
> <NotFound />
<mesh renderOrder={-5} scale={[5, 1, 0]}> <a.group
<planeBufferGeometry attach="geometry" /> visible={intro ? introFinished : true}
<meshBasicMaterial map={mainSceneBg} depthTest={false} /> position={bgSpring.position}
</mesh> >
</a.group> <mesh renderOrder={-5} scale={[5, 1, 0]}>
<group visible={!paused}> <planeBufferGeometry attach="geometry" />
<group visible={!wordSelected && (intro ? introFinished : true)}> <meshBasicMaterial map={mainSceneBg} depthTest={false} />
<a.group visible={!wordNotFound} position-y={tiltSpring.value}> </mesh>
<HUD /> </a.group>
</a.group> <group visible={!paused}>
<MiddleRing /> <group visible={!wordSelected && (intro ? introFinished : true)}>
<group position={[0.1, 0, -2]} ref={grayPlaneGroupRef}> <a.group visible={!wordNotFound} position-y={tiltSpring.value}>
{grayPlanePoses.map((position, idx: number) => ( <HUD />
<GrayPlane position={position} key={idx} /> </a.group>
))} <MiddleRing />
</group> <group position={[0.1, 0, -2]} ref={grayPlaneGroupRef}>
{grayPlanePoses.map((position, idx: number) => (
<GrayPlane position={position} key={idx} />
))}
</group> </group>
<group visible={intro ? introFinished : true}>
<YellowOrb visible={!paused} />
</group>
<Starfield
shouldIntro={intro}
mainVisible={intro ? starfieldIntro : true}
/>
</group> </group>
<a.group visible={!wordSelected} position-y={tiltSpring.value}> <group visible={intro ? introFinished : true}>
<Lain <YellowOrb visible={!paused} />
shouldAnimate={lainIntroAnim}
introFinished={intro ? introFinished : true}
/>
</a.group>
<group
ref={introWrapperRef}
position-z={intro ? -10 : 0}
rotation-x={intro ? Math.PI / 2 : 0}
>
<Site introFinished={intro ? introFinished : true} />
</group> </group>
<pointLight color={0xffffff} position={[0, 0, 7]} intensity={1} /> <Starfield
<pointLight color={0x7f7f7f} position={[0, 10, 0]} intensity={1.5} /> shouldIntro={intro}
<pointLight color={0xffffff} position={[8, 0, 0]} intensity={0.2} /> mainVisible={intro ? starfieldIntro : true}
<pointLight color={0xffffff} position={[-8, 0, 0]} intensity={0.2} /> />
</Suspense> </group>
<a.group visible={!wordSelected} position-y={tiltSpring.value}>
<Lain
shouldAnimate={lainIntroAnim}
introFinished={intro ? introFinished : true}
/>
</a.group>
<a.group
position={introSpring.position}
// NOTE (cast to any)
// throws a type error, but works
rotation={introSpring.rotation as any}
>
<Site introFinished={intro ? introFinished : true} />
</a.group>
<pointLight color={0xffffff} position={[0, 0, 7]} intensity={1} />
<pointLight color={0x7f7f7f} position={[0, 10, 0]} intensity={1.5} />
<pointLight color={0xffffff} position={[8, 0, 0]} intensity={0.2} />
<pointLight color={0xffffff} position={[-8, 0, 0]} intensity={0.2} />
</group> </group>
); );
}; };

View file

@ -11,7 +11,6 @@ import { useStore } from "@/store";
import { GameScene, MediaComponent, Position, TextType } from "@/types"; import { GameScene, MediaComponent, Position, TextType } from "@/types";
import { createAudioAnalyser } from "@/utils/audio"; import { createAudioAnalyser } from "@/utils/audio";
import Images from "@canvas/objects/Images"; import Images from "@canvas/objects/Images";
import Loading from "@canvas/objects/Loading";
import AudioVisualizer from "@canvas/objects/MediaScene/AudioVisualizer"; import AudioVisualizer from "@canvas/objects/MediaScene/AudioVisualizer";
import LeftSide from "@canvas/objects/MediaScene/LeftSide"; import LeftSide from "@canvas/objects/MediaScene/LeftSide";
import NodeNameContainer from "@canvas/objects/MediaScene/NodeNameContainer"; import NodeNameContainer from "@canvas/objects/MediaScene/NodeNameContainer";
@ -140,7 +139,7 @@ const MediaScene = () => {
return ( return (
<group position-z={3} ref={mediaSceneGroupRef}> <group position-z={3} ref={mediaSceneGroupRef}>
{node && loaded ? ( {node && loaded && (
<group position={[0.4, -0.3, 0]}> <group position={[0.4, -0.3, 0]}>
<pointLight intensity={1.2} color={0xffffff} position={[-2, 0, 0]} /> <pointLight intensity={1.2} color={0xffffff} position={[-2, 0, 0]} />
<LeftSide /> <LeftSide />
@ -178,8 +177,6 @@ const MediaScene = () => {
<Images imageTableIndices={node.image_table_indices} /> <Images imageTableIndices={node.image_table_indices} />
</group> </group>
</group> </group>
) : (
<Loading />
)} )}
</group> </group>
); );

View file

@ -24,6 +24,7 @@ import MediaPlayer from "@canvas/objects/MediaPlayer";
import { GameScene } from "@/types"; import { GameScene } from "@/types";
import Head from "next/head"; import Head from "next/head";
import Preloader from "@/components/canvas/objects/Preloader"; import Preloader from "@/components/canvas/objects/Preloader";
import Loading from "@/components/canvas/objects/Loading";
const Game = () => { const Game = () => {
const scene = useStore((state) => state.scene); const scene = useStore((state) => state.scene);
@ -116,9 +117,15 @@ const Game = () => {
linear linear
className="main-canvas" className="main-canvas"
> >
<Suspense fallback={null}> <Suspense
<Preloader /> fallback={
scene === GameScene.Main || scene === GameScene.Media ? (
<Loading />
) : null
}
>
{dispatchScene[scene]} {dispatchScene[scene]}
<Preloader />
<InputHandler /> <InputHandler />
</Suspense> </Suspense>
</Canvas> </Canvas>