clean: complete optimization of player movements

This commit is contained in:
Florian Sylvain
2024-03-15 02:36:00 +01:00
parent 93ce8386ab
commit 7d22a4eee9
4 changed files with 177 additions and 146 deletions
+14 -50
View File
@@ -2,21 +2,15 @@ import "./App.css";
import { Canvas } from "@react-three/fiber"; import { Canvas } from "@react-three/fiber";
import { KeyboardControls } from "@react-three/drei"; import { KeyboardControls } from "@react-three/drei";
import { MutableRefObject, useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Vector2, Vector3 } from "three"; import { Vector3 } from "three";
import { Cubes } from "./components/Cubes";
import { Player } from "./components/Player"; import { Player } from "./components/Player";
import { import { Bloom, EffectComposer, Vignette } from "@react-three/postprocessing";
Bloom, import { Cubes } from "./components/Cubes";
ChromaticAberration,
EffectComposer, const CUBES_QT = 10;
Vignette,
} from "@react-three/postprocessing";
import { BlendFunction } from "postprocessing";
function App() { function App() {
const CUBES_QT = 10;
const container = useRef<HTMLCanvasElement>(null); const container = useRef<HTMLCanvasElement>(null);
useEffect(() => { useEffect(() => {
@@ -37,7 +31,7 @@ function App() {
className="container" className="container"
onClick={() => container.current?.requestPointerLock()} onClick={() => container.current?.requestPointerLock()}
> >
<Canvas className="canvas" ref={container} shadows> <Canvas className="canvas" ref={container} shadows camera={{ fov: 50 }}>
<Cubes cubesPosition={cubes}></Cubes> <Cubes cubesPosition={cubes}></Cubes>
<mesh <mesh
@@ -46,8 +40,11 @@ function App() {
receiveShadow receiveShadow
castShadow castShadow
> >
<planeGeometry attach={"geometry"} args={[100, 100]}></planeGeometry> <planeGeometry attach={"geometry"} args={[50, 50]}></planeGeometry>
<meshLambertMaterial color={"green"}></meshLambertMaterial> <meshStandardMaterial
toneMapped={false}
color={"green"}
></meshStandardMaterial>
</mesh> </mesh>
<KeyboardControls <KeyboardControls
@@ -60,11 +57,7 @@ function App() {
{ keys: ["ShiftLeft"], name: "sprint" }, { keys: ["ShiftLeft"], name: "sprint" },
]} ]}
> >
<Player <Player></Player>
container={
container as unknown as MutableRefObject<HTMLCanvasElement>
}
></Player>
</KeyboardControls> </KeyboardControls>
<ambientLight intensity={Math.PI / 2} /> <ambientLight intensity={Math.PI / 2} />
@@ -74,37 +67,8 @@ function App() {
{/* <gridHelper args={[100, 100]}></gridHelper> */} {/* <gridHelper args={[100, 100]}></gridHelper> */}
{/* <axesHelper args={[100]}></axesHelper> */} {/* <axesHelper args={[100]}></axesHelper> */}
<EffectComposer enableNormalPass> <EffectComposer enableNormalPass>
<Bloom <Bloom mipmapBlur luminanceThreshold={0.4} />
luminanceThreshold={0.3}
luminanceSmoothing={1.2}
height={300}
/>
<Vignette eskil={false} offset={0.2} darkness={0.8} /> <Vignette eskil={false} offset={0.2} darkness={0.8} />
{/* <SSAO
blendFunction={BlendFunction.MULTIPLY}
samples={30}
rings={4}
distanceThreshold={1.0}
distanceFalloff={0.0}
rangeThreshold={0.5}
rangeFalloff={0.1}
luminanceInfluence={0.9}
radius={20}
bias={0.5}
worldDistanceFalloff={0.5}
worldDistanceThreshold={0.5}
worldProximityFalloff={0.5}
worldProximityThreshold={0.5}
intensity={30}
></SSAO> */}
<ChromaticAberration
modulationOffset={0.02}
radialModulation={true}
offset={new Vector2(0.002, 0.002)}
blendFunction={BlendFunction.NORMAL}
/>
{/* <SSR thickness={1} intensity={0.1}></SSR> */}
</EffectComposer> </EffectComposer>
</Canvas> </Canvas>
</main> </main>
+4 -1
View File
@@ -38,7 +38,10 @@ export function Cubes(props: { cubesPosition: Vector3[] }) {
scale={[1, 1, 1]} scale={[1, 1, 1]}
> >
<boxGeometry attach={"geometry"}></boxGeometry> <boxGeometry attach={"geometry"}></boxGeometry>
<meshLambertMaterial color={"hotpink"}></meshLambertMaterial> <meshLambertMaterial
color={"hotpink"}
toneMapped={false}
></meshLambertMaterial>
</mesh> </mesh>
); );
}); });
+155 -95
View File
@@ -1,12 +1,6 @@
import { useAnimations, useGLTF, useKeyboardControls } from "@react-three/drei"; import { useAnimations, useGLTF, useKeyboardControls } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { import { useEffect, useRef } from "react";
MutableRefObject,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { import {
DirectionalLight, DirectionalLight,
Euler, Euler,
@@ -15,6 +9,7 @@ import {
Vector2, Vector2,
Vector3, Vector3,
} from "three"; } from "three";
import { AnimationState } from "../models/Animations";
export interface KeyPressed { export interface KeyPressed {
moveForward: boolean; moveForward: boolean;
@@ -27,110 +22,175 @@ export interface KeyPressed {
const PLAYER_SPEED = 4.75; const PLAYER_SPEED = 4.75;
const SHADOW_DRAW_DISTANCE = 30; const SHADOW_DRAW_DISTANCE = 30;
const MOUSE_X_SENSITIVITY = 0.002;
const MOUSE_Y_SENSITIVITY = 0.001;
const ANIM_SPEED = 0.2;
const CAMERA_RADIUS = 2;
const CAMERA_HEIGHT = 1.5;
export function Player(props: { export function Player(): JSX.Element {
container: MutableRefObject<HTMLCanvasElement>;
}): JSX.Element {
const model = useGLTF("/Adventurer.glb"); const model = useGLTF("/Adventurer.glb");
const animations = useAnimations(model.animations, model.scene); const anims = useAnimations(model.animations, model.scene);
// const walk = animations.actions["CharacterArmature|Walk"];
const run = animations.actions["CharacterArmature|Run"];
const idle = animations.actions["CharacterArmature|Idle"];
const [, getKeys] = useKeyboardControls(); const [, getKeys] = useKeyboardControls();
const [isWalking, setIsWalking] = useState(false);
const [pivotX, setPivotX] = useState(0);
const [pivotY, setPivotY] = useState(0);
const [mouseRef, setMouseRef] = useState(new Vector2());
const lightRef = useRef<DirectionalLight>(null); const lightRef = useRef<DirectionalLight>(null);
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
const radius = 2; const pivot = new Vector2();
const yOffset = 1.5; const moveVector = new Vector3();
const moveEuler = new Euler();
const targetRotation = new Quaternion();
const targetRotationEuler = new Euler();
const cameraRotationEuler = new Euler();
const rotationQuaternion = new Quaternion();
const rotationEuler = new Euler();
const animsState: AnimationState[] = [
{ name: "CharacterArmature|Idle", isPlaying: true },
{ name: "CharacterArmature|Run", isPlaying: false },
{ name: "CharacterArmature|Run_Back", isPlaying: false },
];
const lastAnimState = {} as AnimationState;
const setAnimState = (name: string, isPlaying: boolean): void => {
const anim = animsState.find((anim) => anim.name === name);
if (!anim) return;
anim.isPlaying = isPlaying;
};
const getAnimState = (name: string): AnimationState => {
const anim = animsState.find((anim) => anim.name === name);
if (!anim) return { name: "", isPlaying: false };
return anim;
};
const onMouseMove = (mouseEvent: MouseEvent): void => {
pivot.x = pivot.x - mouseEvent.movementX * MOUSE_X_SENSITIVITY;
pivot.y = Math.min(
Math.max(pivot.y + mouseEvent.movementY * MOUSE_Y_SENSITIVITY, -0.4),
1.2
);
};
const someKeyPressed = (): boolean =>
Object.values(getKeys()).some((key) => key);
function setMoveVector(delta: number): void {
moveVector.set(0, 0, 0);
const keys = getKeys();
if (keys.moveLeft) moveVector.x += 1;
if (keys.moveRight) moveVector.x -= 1;
if (keys.moveForward) moveVector.z += 1;
if (keys.moveBackward) moveVector.z -= 1;
moveVector.applyEuler(moveEuler.set(0, pivot.x - Math.PI, 0));
moveVector.multiplyScalar(PLAYER_SPEED * delta);
}
function setAnimationsState(): void {
const keys = getKeys();
const isMovingSideways = keys.moveLeft || keys.moveRight;
const isMovingForward = keys.moveForward;
const isMovingBackward = keys.moveBackward;
const isIdle = !isMovingSideways && !isMovingForward && !isMovingBackward;
if (isMovingForward || (isMovingSideways && !isMovingBackward)) {
setAnimState("CharacterArmature|Idle", false);
setAnimState("CharacterArmature|Run", true);
setAnimState("CharacterArmature|Run_Back", false);
} else if (isMovingBackward) {
setAnimState("CharacterArmature|Idle", false);
setAnimState("CharacterArmature|Run", false);
setAnimState("CharacterArmature|Run_Back", true);
} else if (isIdle) {
setAnimState("CharacterArmature|Idle", true);
setAnimState("CharacterArmature|Run", false);
setAnimState("CharacterArmature|Run_Back", false);
}
}
function setPlayerHeadRotationX(): void {
model.nodes?.Head.rotateX(pivot.y);
}
function calculateTargetRotation(): void {
targetRotationEuler.y = pivot.x + Math.atan2(moveVector.x, moveVector.z);
targetRotation.setFromEuler(targetRotationEuler);
}
function calculateCameraRotation(): void {
cameraRotationEuler.setFromQuaternion(camera.quaternion, "YXZ");
rotationEuler.set(0, -cameraRotationEuler.y, 0);
}
function applyCameraRotationToTargetRotation(): void {
rotationQuaternion.setFromEuler(rotationEuler);
targetRotation.multiplyQuaternions(targetRotation, rotationQuaternion);
}
function rotateBackwards(): void {
rotationEuler.set(0, Math.PI, 0);
rotationQuaternion.setFromEuler(rotationEuler);
targetRotation.multiplyQuaternions(targetRotation, rotationQuaternion);
}
function interpolatePlayerQuaternionToTargetRotation(): void {
model.scene.quaternion.slerp(targetRotation, 0.2);
}
function setPlayerPosition(): void {
model.scene.position.add(moveVector);
calculateTargetRotation();
calculateCameraRotation();
applyCameraRotationToTargetRotation();
if (getAnimState("CharacterArmature|Run_Back").isPlaying) {
rotateBackwards();
}
interpolatePlayerQuaternionToTargetRotation();
}
function setCameraPosition(): void {
const player = model.scene;
camera.position.set(
CAMERA_RADIUS * Math.sin(pivot.x) * Math.cos(pivot.y) + player.position.x,
CAMERA_RADIUS * Math.sin(pivot.y) + player.position.y + CAMERA_HEIGHT,
CAMERA_RADIUS * Math.cos(pivot.x) * Math.cos(pivot.y) + player.position.z
);
camera.lookAt(
player.position.x,
player.position.y + CAMERA_HEIGHT,
player.position.z
);
}
function setPlayerAnimations(): void {
animsState.forEach((anim) => {
if (lastAnimState.name !== anim.name && anim.isPlaying) {
anims.actions[lastAnimState.name]?.fadeOut(ANIM_SPEED);
anims.actions[anim.name]?.reset().fadeIn(ANIM_SPEED).play();
lastAnimState.name = anim.name;
}
});
}
useFrame((_, delta) => {
setMoveVector(delta);
setAnimationsState();
setPlayerHeadRotationX();
if (someKeyPressed()) setPlayerPosition();
setCameraPosition();
setPlayerAnimations();
});
useEffect(() => { useEffect(() => {
idle?.play();
model.scene.traverse((child) => { model.scene.traverse((child) => {
if (child instanceof Mesh) { if (child instanceof Mesh) {
child.castShadow = true; child.castShadow = true;
child.receiveShadow = true; child.receiveShadow = true;
} }
}); });
window.addEventListener("mousemove", onMouseMove);
return () => window.removeEventListener("mousemove", onMouseMove);
}); });
const moveVector = new Vector3();
const moveEuler = new Euler();
const targetRotation = new Quaternion();
const rotationEuler = new Euler();
useFrame((_, delta) => {
const player = model.scene.position;
moveVector.set(0, 0, 0);
if (getKeys().moveForward) moveVector.z += 1;
if (getKeys().moveBackward) moveVector.z -= 1;
if (getKeys().moveLeft) moveVector.x += 1;
if (getKeys().moveRight) moveVector.x -= 1;
moveVector.applyEuler(moveEuler.set(0, pivotX - Math.PI, 0));
moveVector.multiplyScalar(PLAYER_SPEED * delta);
model.nodes?.Head.rotateX(pivotY - model.nodes?.Head.rotation.x);
if (Object.values(getKeys()).some((key) => key)) {
player.add(moveVector);
targetRotation.setFromEuler(rotationEuler.set(0, pivotX - Math.PI, 0));
model.scene.quaternion.slerp(targetRotation, 0.2);
}
camera.position.x = radius * Math.sin(pivotX) * Math.cos(pivotY) + player.x;
camera.position.y = radius * Math.sin(pivotY) + player.y + yOffset;
camera.position.z = radius * Math.cos(pivotX) * Math.cos(pivotY) + player.z;
camera.lookAt(player.x, player.y + yOffset, player.z);
});
const onMouseMove = useCallback((event: MouseEvent) => {
setMouseRef(new Vector2(event.movementX, event.movementY));
}, []);
useEffect(() => {
props.container.current?.addEventListener("mousemove", onMouseMove);
return () => {
props.container.current?.removeEventListener("mousemove", onMouseMove);
};
}, [onMouseMove, props.container]);
useEffect(() => {
const MOUSE_X_SENSITIVITY = 0.005;
const MOUSE_Y_SENSITIVITY = 0.003;
setPivotX((pivotX) => pivotX - mouseRef.x * MOUSE_X_SENSITIVITY);
setPivotY((pivotY) =>
Math.min(Math.max(pivotY + mouseRef.y * MOUSE_Y_SENSITIVITY, -0.2), 1.2)
);
}, [mouseRef]);
useEffect(() => {
const ANIM_SPEED = 0.2;
if (isWalking) {
idle?.fadeOut(ANIM_SPEED);
run?.reset().fadeIn(ANIM_SPEED).play();
} else {
run?.fadeOut(ANIM_SPEED);
idle?.reset().fadeIn(ANIM_SPEED).play();
}
return () => {
run?.fadeOut(ANIM_SPEED);
idle?.fadeOut(ANIM_SPEED);
};
}, [isWalking, idle, run]);
return ( return (
<> <>
<primitive <primitive
+4
View File
@@ -0,0 +1,4 @@
export interface AnimationState {
name: string;
isPlaying: boolean;
}