mirror of
https://github.com/Floriansylvain/generic-threejs-game.git
synced 2026-08-19 11:43:18 +02:00
clean: complete optimization of player movements
This commit is contained in:
+14
-50
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface AnimationState {
|
||||||
|
name: string;
|
||||||
|
isPlaying: boolean;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user