From 11b5b8fed43335efb8cea1f70cf0691da53520d7 Mon Sep 17 00:00:00 2001 From: ad044 Date: Mon, 31 Aug 2020 13:47:05 +0400 Subject: [PATCH] ported all animations to use react-spring --- package-lock.json | 64 ++++++ package.json | 1 + src/components/Game.tsx | 255 ++++++++++++--------- src/components/Lain.tsx | 11 +- src/components/Preloader.tsx | 2 + src/components/PurpleRing.tsx | 29 ++- src/resources/level_sprite_directions.json | 12 + 7 files changed, 254 insertions(+), 120 deletions(-) diff --git a/package-lock.json b/package-lock.json index 025de7e..c1e79ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,11 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@alloc/types": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@alloc/types/-/types-1.3.0.tgz", + "integrity": "sha512-mH7LiFiq9g6rX2tvt1LtwsclfG5hnsmtIfkZiauAGrm1AwXhoRS0sF2WrN9JGN7eV5vFXqNaB0eXZ3IvMsVi9g==" + }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -1405,6 +1410,50 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@react-spring/animated": { + "version": "9.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.0.0-rc.3.tgz", + "integrity": "sha512-dAvgtKhkYpzzr+EkmZ4ZuJ5CujxCW0LaT109DvO/2MQNk3EWIxcgl+ik4tSulSbgau1GN8RlkRKyDp0wISdQ3Q==", + "requires": { + "@babel/runtime": "^7.3.1", + "@react-spring/shared": "9.0.0-rc.3", + "react-layout-effect": "^1.0.1" + } + }, + "@react-spring/core": { + "version": "9.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.0.0-rc.3.tgz", + "integrity": "sha512-3OzsVFxpfMJNkkQj8TwAH3NhUAX76AXu6WkslQF4EgBeEoG5eY3m+VvM9RsAsGWDuBKpscZ/wBpFt5Ih6KdGHA==", + "requires": { + "@babel/runtime": "^7.3.1", + "@react-spring/animated": "9.0.0-rc.3", + "@react-spring/shared": "9.0.0-rc.3", + "react-layout-effect": "^1.0.1", + "use-memo-one": "^1.1.0" + } + }, + "@react-spring/shared": { + "version": "9.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.0.0-rc.3.tgz", + "integrity": "sha512-dd50TxwwMWd+dSB0InjndUN9w17cbnMCPy+0sag6zRxxKIo7eOyWSliOtLKxvufgmdC8Prm4M3GT5dmB1yxKEQ==", + "requires": { + "@alloc/types": "^1.2.1", + "@babel/runtime": "^7.3.1", + "fluids": "^0.1.6", + "tslib": "^1.11.1" + } + }, + "@react-spring/three": { + "version": "9.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.0.0-rc.3.tgz", + "integrity": "sha512-H55T+Dnck+hsJ8WgE+tb89ngX1E1lDOpMBG4mGzNLGok6XgGqN0VBsHRN3QDl+aPfmJI1BPFPR6b6WbhwqRNbw==", + "requires": { + "@babel/runtime": "^7.3.1", + "@react-spring/animated": "9.0.0-rc.3", + "@react-spring/core": "9.0.0-rc.3", + "@react-spring/shared": "9.0.0-rc.3" + } + }, "@sheerun/mutationobserver-shim": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", @@ -5858,6 +5907,11 @@ "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==" }, + "fluids": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/fluids/-/fluids-0.1.9.tgz", + "integrity": "sha512-cA5WHsJZNjsMXzAp/lxl6KLAiOgXFTOwQ+QVf39LfCcoBgih8sqkRadtjN+6UTU6KoF0h0HdYJKI3GWuqRVdBw==" + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -10607,6 +10661,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-layout-effect": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-layout-effect/-/react-layout-effect-1.0.5.tgz", + "integrity": "sha512-zdRXHuch+OBHU6bvjTelOGUCM+UDr/iCY+c0wXLEAc+G4/FlcJruD/hUOzlKH5XgO90Y/BUJPNhI/g9kl+VAsA==" + }, "react-merge-refs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", @@ -12855,6 +12914,11 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-memo-one": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz", + "integrity": "sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==" + }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", diff --git a/package.json b/package.json index 4105e4d..7c54ad2 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@react-spring/three": "^9.0.0-rc.3", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", diff --git a/src/components/Game.tsx b/src/components/Game.tsx index 994aac9..8cd2f2c 100644 --- a/src/components/Game.tsx +++ b/src/components/Game.tsx @@ -1,22 +1,21 @@ -import React, { useState, Suspense, useCallback, useEffect } from "react"; +import { a, useSpring } from "@react-spring/three"; +//import Orb from "./Orb"; +import { OrbitControls } from "drei"; +import React, { Suspense, useCallback, useEffect, useState } from "react"; import { Canvas } from "react-three-fiber"; +import lain_animations from "../resources/lain_animations.json"; +import level_sprite_directions from "../resources/level_sprite_directions.json"; +import level_sprite_huds from "../resources/level_sprite_huds.json"; +import Hub, { PositionAndScaleProps } from "./Hub"; import Lain, { - LainIntro, LainMoveDown, LainMoveLeft, LainMoveRight, LainMoveUp, LainStanding, } from "./Lain"; -import Hub, { PositionAndScaleProps } from "./Hub"; -//import Orb from "./Orb"; -import { OrbitControls, PerspectiveCamera } from "drei"; import Lights from "./Lights"; import OrthoCamera from "./OrthoCamera"; -import TWEEN from "@tweenjs/tween.js"; -import level_sprite_directions from "../resources/level_sprite_directions.json"; -import lain_animations from "../resources/lain_animations.json"; -import level_sprite_huds from "../resources/level_sprite_huds.json"; import Preloader from "./Preloader"; type KeyCodeAssociations = { @@ -29,7 +28,7 @@ type SpriteDirections = { // will fix the typing on this later type SpriteHuds = { - [key: string]: Record; + [key: string]: Record | any; }; type LainAnimations = { @@ -39,17 +38,16 @@ type LainAnimations = { const Game = () => { const [isLainMoving, setLainMoving] = useState(false); const [lainMoveState, setLainMoveState] = useState(); - const [lainPosY, setLainPosY] = useState(-0.2); - - const [cameraPosY, setCameraPosY] = useState(0); - const [cameraRotationY, setCameraRotationY] = useState(0); const [currentSprite, setCurrentSprite] = useState("043"); + const [currentSpriteHUD, setCurrentSpriteHUD] = useState( + (level_sprite_huds as SpriteHuds)[currentSprite] + ); // we separate positions of the hud sprites into the state since we need to animate thme - const [longHudPosition, setLongHudPosition] = useState< - PositionAndScaleProps - >(); + const [longHudPosX, setLongHudPosX] = useState( + currentSpriteHUD["long"]["position"][0] + ); const [boringHudPosition, setBoringHudPosition] = useState< PositionAndScaleProps >(); @@ -59,30 +57,69 @@ const Game = () => { return (level_sprite_directions as SpriteDirections)[currentLoc][key]; }; - const getHudData = (sprite: string) => { - return (level_sprite_huds as SpriteHuds)[sprite]; - }; + const [{ cameraRotationY }, setCameraRotationY] = useSpring( + () => ({ + cameraRotationY: 0, + config: { precision: 0.0001, duration: 1600 }, + }), + [] + ); - const moveCamera = (value: number, duration: number) => { - const moveInterval = setInterval(() => { - setCameraPosY((prev: number) => prev + value); - setLainPosY((prev: number) => prev - value); - }); + const [{ cameraPositionY }, setCameraPositionY] = useSpring( + () => ({ + cameraPositionY: 0, + config: { precision: 0.0001, duration: 1200 }, + }), + [] + ); - setTimeout(() => { - clearInterval(moveInterval); - }, duration); - }; + const [{ lainPositionY }, setLainPositionY] = useSpring( + () => ({ + lainPositionY: -0.06, + config: { precision: 0.0001, duration: 1200 }, + }), + [] + ); - const rotateCamera = (value: number, duration: number) => { - const rotationInterval = setInterval(() => { - setCameraRotationY((prev: number) => prev + value); - }); + const moveCamera = useCallback( + (val: number) => + void setTimeout( + () => + setCameraPositionY(() => ({ + cameraPositionY: cameraPositionY.get() + val, + })), + 1300 + ), + [cameraPositionY, setCameraPositionY] + ); - setTimeout(() => { - clearInterval(rotationInterval); - }, duration); - }; + const moveLain = useCallback( + (val: number) => + void setTimeout( + () => + setLainPositionY(() => ({ + lainPositionY: lainPositionY.get() + val, + })), + 1300 + ), + [setLainPositionY, lainPositionY] + ); + + const rotateCamera = useCallback( + (val: number) => + void setTimeout( + () => + setCameraRotationY(() => ({ + cameraRotationY: cameraRotationY.get() + val, + })), + 1100 + ), + [setCameraRotationY, cameraRotationY] + ); + + const camRotY = cameraRotationY.to([0, 1], [0, Math.PI]); + const camPosY = cameraPositionY.to([0, 1], [0, Math.PI]); + const lainPosY = lainPositionY.to([0, 1], [0, Math.PI]); const getKeyCodeAssociation = (keyCode: number): string => { return ({ @@ -95,28 +132,23 @@ const Game = () => { const setAnimationState = useCallback( (key: string) => { - const move = getMove(currentSprite, key); - switch (key) { case "down": - // "+" in the json denotes that the sprite chosen by getMove is not currently on screen, - // therefore lain should first do a move (up/down/left/right) and then that sprite - // will be chosen. - if (move[0] !== "+") setCurrentSprite(move); - else { - setLainMoveState(); - setTimeout(() => { - setCurrentSprite(move); - }, (lain_animations as LainAnimations)[key]["duration"] + 200); - } + moveCamera(0.3); + moveLain(-0.3); + setLainMoveState(); break; case "left": + rotateCamera(0.1); setLainMoveState(); break; case "up": + moveCamera(-0.3); + moveLain(0.3); setLainMoveState(); break; case "right": + rotateCamera(-0.1); setLainMoveState(); break; default: @@ -133,78 +165,85 @@ const Game = () => { setLainMoveState(); }, (lain_animations as LainAnimations)[key]["duration"]); }, - [currentSprite] + [moveCamera, moveLain, rotateCamera] ); - const handleUserKeyPress = useCallback( + const animateSpriteHUDIn = useCallback( + (spriteHUD: SpriteHuds) => { + const [longPos, boringPos, bigPos] = [ + spriteHUD["long"]["position"], + spriteHUD["boring"]["position"], + spriteHUD["big"]["position"], + ]; + const [initialLongPos, initialBoringPos, initialBigPos] = [ + spriteHUD["long"]["initial_position"], + spriteHUD["boring"]["initial_position"], + spriteHUD["big"]["initial_position"], + ]; + }, + [longHudPosX] + ); + + const moveDispatcher = useCallback( + (move: string, key: string) => { + switch (move[0]) { + // do nothing / cant move + case "": + break; + // "+" in the json denotes that the sprite chosen by getMove is not currently on screen, + // therefore lain should first do a move (up/down/left/right) and then that sprite + // will be chosen. + case "+": + setAnimationState(key); + setTimeout(() => { + setCurrentSprite(move); + setCurrentSpriteHUD((level_sprite_huds as SpriteHuds)[move]); + }, (lain_animations as LainAnimations)[key]["duration"] + 200); + break; + // only change sprite focus + default: + setCurrentSprite(move); + setCurrentSpriteHUD((level_sprite_huds as SpriteHuds)[move]); + animateSpriteHUDIn(currentSpriteHUD); + } + }, + [setAnimationState, animateSpriteHUDIn, currentSpriteHUD] + ); + + const handleKeyPress = useCallback( (event) => { const { keyCode } = event; const key = getKeyCodeAssociation(keyCode); + const move = getMove(currentSprite, key); + console.log(key); if (!isLainMoving && key) { - setAnimationState(key); - switch (key) { - case "left": - setTimeout(() => { - rotateCamera(0.001, 1600); - }, 1100); - break; - case "right": - setTimeout(() => { - rotateCamera(-0.001, 1600); - }, 1100); - break; - case "up": - setTimeout(() => { - moveCamera(-0.005, 1200); - }, 1300); - break; - case "down": - setTimeout(() => { - moveCamera(0.005, 1200); - }, 1300); - break; - default: - break; - } + moveDispatcher(move, key); } }, - [isLainMoving, setAnimationState] + [isLainMoving, currentSprite, moveDispatcher] ); useEffect(() => { - window.addEventListener("keydown", handleUserKeyPress); + window.addEventListener("keydown", handleKeyPress); document.getElementsByTagName("canvas")[0].className = "hub-background"; return () => { - window.removeEventListener("keydown", handleUserKeyPress); + window.removeEventListener("keydown", handleKeyPress); document.getElementsByTagName("body")[0].className = ""; }; - }, [handleUserKeyPress]); - - const animateSpriteHUDIn = () => { - //wip - const initialLongPos = getHudData(currentSprite)["long"]["initial"]; - const finalLongPos = getHudData(currentSprite)["long"]["initial"]; - const pos = getHudData(currentSprite)["long"]["position"]; - const pos1 = getHudData(currentSprite)["big"]["position"]; - const pos2 = getHudData(currentSprite)["boring"]["position"]; - setBigHudPosition(pos1); - setLongHudPosition(pos); - setBoringHudPosition(pos2); - }; - - useEffect(animateSpriteHUDIn, []); + }, [handleKeyPress]); return ( - @@ -219,17 +258,21 @@ const Game = () => { - + ); }; diff --git a/src/components/Lain.tsx b/src/components/Lain.tsx index 757cf66..6eed98a 100644 --- a/src/components/Lain.tsx +++ b/src/components/Lain.tsx @@ -8,11 +8,12 @@ import moveLeftSpriteSheet from "../static/sprites/move_left.png"; import moveRightSpriteSheet from "../static/sprites/move_right.png"; import moveUpSpriteSheet from "../static/sprites/jump_up.png"; import { PlainSingularAnimator } from "three-plain-animator/lib/plain-singular-animator"; +import { a, Interpolation, useSpring } from "@react-spring/three"; type LainProps = { isLainMoving: boolean; lainMoveState: JSX.Element; - lainPosY: number; + lainPosY: Interpolation; }; type LainConstructorProps = { @@ -113,9 +114,13 @@ export const LainMoveUp = () => { const Lain = (props: LainProps) => { return ( loading...}> - + {props.isLainMoving ? props.lainMoveState : } - + ); }; diff --git a/src/components/Preloader.tsx b/src/components/Preloader.tsx index fd8f6ab..b69601c 100644 --- a/src/components/Preloader.tsx +++ b/src/components/Preloader.tsx @@ -7,6 +7,8 @@ import * as THREE from "three"; import { useLoader, useThree } from "react-three-fiber"; import { useLayoutEffect } from "react"; +// this function just preloads lain's spritesheets cuz they're big and lazy loading them +// used to make the suspense run for a couple milliseconds, resulting in flickering const Preloader = () => { const moveDown = useLoader(THREE.TextureLoader, moveDownSpriteSheet); const moveUp = useLoader(THREE.TextureLoader, moveUpSpriteSheet); diff --git a/src/components/PurpleRing.tsx b/src/components/PurpleRing.tsx index 352d034..3efd0fd 100644 --- a/src/components/PurpleRing.tsx +++ b/src/components/PurpleRing.tsx @@ -1,8 +1,9 @@ import * as THREE from "three"; import React, { useRef, useState, useEffect } from "react"; -import { useLoader } from "react-three-fiber"; +import { useLoader, useFrame } from "react-three-fiber"; import { GLTFLoader, GLTF } from "three/examples/jsm/loaders/GLTFLoader"; import { draco } from "drei"; +import { useSpring, a } from "@react-spring/three"; type GLTFResult = GLTF & { nodes: { @@ -12,7 +13,13 @@ type GLTFResult = GLTF & { }; const PurpleRing = (props: JSX.IntrinsicElements["group"]) => { - const [higherRingRotation, setHigherRingRotation] = useState(0); + const [{ purpleRingRotationY }, setPurpleRingRotationY] = useSpring( + () => ({ + purpleRingRotationY: 0, + config: { precision: 0.0001, duration: 1200 }, + }), + [] + ); const { nodes, materials } = useLoader( GLTFLoader, @@ -20,18 +27,18 @@ const PurpleRing = (props: JSX.IntrinsicElements["group"]) => { draco("/draco-gltf/") ); - const purpleRingPermaRotation = () => { - setHigherRingRotation((prev) => prev + 0.002); - }; + useFrame(() => { + setPurpleRingRotationY(() => ({ + purpleRingRotationY: purpleRingRotationY.get() + 0.04, + })); + }); - useEffect(() => { - setInterval(purpleRingPermaRotation, 1); - }, []); + const purpleRingRotY = purpleRingRotationY.to([0, 1], [0, Math.PI]); return ( - @@ -42,7 +49,7 @@ const PurpleRing = (props: JSX.IntrinsicElements["group"]) => { side={THREE.DoubleSide} /> - + ); }; diff --git a/src/resources/level_sprite_directions.json b/src/resources/level_sprite_directions.json index cea10c4..55d355b 100644 --- a/src/resources/level_sprite_directions.json +++ b/src/resources/level_sprite_directions.json @@ -4,5 +4,17 @@ "down": "042", "left": "+", "right": "" + }, + "042": { + "up": "043", + "down": "", + "left": "", + "right": "041" + }, + "041": { + "up": "043", + "down": "", + "left": "", + "right": "041" } }