ported all animations to use react-spring

This commit is contained in:
ad044 2020-08-31 13:47:05 +04:00
parent 7c13b05043
commit 11b5b8fed4
7 changed files with 254 additions and 120 deletions

64
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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<string, any>;
[key: string]: Record<string, any> | any;
};
type LainAnimations = {
@ -39,17 +38,16 @@ type LainAnimations = {
const Game = () => {
const [isLainMoving, setLainMoving] = useState(false);
const [lainMoveState, setLainMoveState] = useState(<LainStanding />);
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<SpriteHuds>(
(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(<LainMoveDown />);
setTimeout(() => {
setCurrentSprite(move);
}, (lain_animations as LainAnimations)[key]["duration"] + 200);
}
moveCamera(0.3);
moveLain(-0.3);
setLainMoveState(<LainMoveDown />);
break;
case "left":
rotateCamera(0.1);
setLainMoveState(<LainMoveLeft />);
break;
case "up":
moveCamera(-0.3);
moveLain(0.3);
setLainMoveState(<LainMoveUp />);
break;
case "right":
rotateCamera(-0.1);
setLainMoveState(<LainMoveRight />);
break;
default:
@ -133,78 +165,85 @@ const Game = () => {
setLainMoveState(<LainStanding />);
}, (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 (
<Canvas concurrent>
<PerspectiveCamera
position={[0, cameraPosY, 3]}
rotation={[0, cameraRotationY, 0]}
<a.perspectiveCamera
position-z={3}
position-y={camPosY}
rotation-y={camRotY}
>
<Suspense fallback={null}>
<OrbitControls />
@ -219,17 +258,21 @@ const Game = () => {
<OrthoCamera
bigHudPosition={bigHudPosition!}
boringHudPosition={boringHudPosition!}
longHudPosition={longHudPosition!}
longHudType={getHudData(currentSprite)["long"]["type"]}
boringHudType={getHudData(currentSprite)["boring"]["type"]}
bigHudType={getHudData(currentSprite)["big"]["type"]}
longHudScale={getHudData(currentSprite)["long"]["scale"]}
boringHudScale={getHudData(currentSprite)["boring"]["scale"]}
bigHudScale={getHudData(currentSprite)["big"]["scale"]}
id={getHudData(currentSprite)["id"]}
longHudPosition={[
longHudPosX,
currentSpriteHUD!["long"]["position"][1],
currentSpriteHUD!["long"]["position"][2],
]}
longHudType={currentSpriteHUD!["long"]["type"]}
boringHudType={currentSpriteHUD!["boring"]["type"]}
bigHudType={currentSpriteHUD!["big"]["type"]}
longHudScale={currentSpriteHUD!["long"]["scale"]}
boringHudScale={currentSpriteHUD!["boring"]["scale"]}
bigHudScale={currentSpriteHUD!["big"]["scale"]}
id={currentSpriteHUD!["id"]}
/>
</Suspense>
</PerspectiveCamera>
</a.perspectiveCamera>
</Canvas>
);
};

View file

@ -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<number, number>;
};
type LainConstructorProps = {
@ -113,9 +114,13 @@ export const LainMoveUp = () => {
const Lain = (props: LainProps) => {
return (
<Suspense fallback={<>loading...</>}>
<sprite position={[0.1, props.lainPosY, 0]} scale={[4.9, 4.9, 4.9]}>
<a.sprite
position-x={0.1}
position-y={props.lainPosY}
scale={[4.9, 4.9, 4.9]}
>
{props.isLainMoving ? props.lainMoveState : <LainStanding />}
</sprite>
</a.sprite>
</Suspense>
);
};

View file

@ -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);

View file

@ -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<GLTFResult>(
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 (
<group
<a.group
position={[0, 0.4, 0]}
rotation={[0, higherRingRotation, 0]}
rotation-y={purpleRingRotY}
scale={[1.3, 1.3, 1.3]}
dispose={null}
>
@ -42,7 +49,7 @@ const PurpleRing = (props: JSX.IntrinsicElements["group"]) => {
side={THREE.DoubleSide}
/>
</mesh>
</group>
</a.group>
);
};

View file

@ -4,5 +4,17 @@
"down": "042",
"left": "+",
"right": ""
},
"042": {
"up": "043",
"down": "",
"left": "",
"right": "041"
},
"041": {
"up": "043",
"down": "",
"left": "",
"right": "041"
}
}