mirror of
https://github.com/Floriansylvain/generic-threejs-game.git
synced 2026-08-19 11:43:18 +02:00
180 lines
4.6 KiB
TypeScript
180 lines
4.6 KiB
TypeScript
import { useAnimations, useGLTF, useKeyboardControls } from "@react-three/drei";
|
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
import {
|
|
MutableRefObject,
|
|
useCallback,
|
|
useEffect,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import {
|
|
DirectionalLight,
|
|
Euler,
|
|
Mesh,
|
|
Quaternion,
|
|
Vector2,
|
|
Vector3,
|
|
} from "three";
|
|
import { degToRad } from "three/src/math/MathUtils.js";
|
|
|
|
export interface KeyPressed {
|
|
moveForward: boolean;
|
|
moveBackward: boolean;
|
|
moveLeft: boolean;
|
|
moveRight: boolean;
|
|
jump: boolean;
|
|
sprint: boolean;
|
|
}
|
|
|
|
const PLAYER_SPEED = 4.75;
|
|
const SHADOW_DRAW_DISTANCE = 30;
|
|
|
|
export function Player(props: {
|
|
container: MutableRefObject<HTMLCanvasElement>;
|
|
}): JSX.Element {
|
|
const model = useGLTF("/Adventurer.glb");
|
|
const animations = 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 [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 camera = useThree((state) => state.camera);
|
|
|
|
const radius = 2;
|
|
const yOffset = 1.5;
|
|
|
|
useEffect(() => {
|
|
idle?.play();
|
|
model.scene.traverse((child) => {
|
|
if (child instanceof Mesh) {
|
|
child.castShadow = true;
|
|
child.receiveShadow = true;
|
|
}
|
|
});
|
|
});
|
|
|
|
useFrame((_, delta) => {
|
|
const move = new Vector3();
|
|
const player = model.scene.position;
|
|
|
|
// TODO Replace getKeys with the subscription system
|
|
if (getKeys().moveForward) move.z += 1;
|
|
if (getKeys().moveBackward) move.z -= 1;
|
|
if (getKeys().moveLeft) move.x += 1;
|
|
if (getKeys().moveRight) move.x -= 1;
|
|
|
|
move.applyEuler(new Euler(0, pivotX - degToRad(180), 0));
|
|
move.multiplyScalar(PLAYER_SPEED * delta);
|
|
|
|
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;
|
|
|
|
const adujstedPos = new Vector3().copy(model.scene.position);
|
|
adujstedPos.y += yOffset;
|
|
|
|
model.nodes?.Head.rotateX(pivotY - model.nodes?.Head.rotation.x);
|
|
|
|
camera.lookAt(adujstedPos);
|
|
|
|
if (Object.values(getKeys()).some((key) => key)) {
|
|
player.add(move);
|
|
|
|
const targetRotation = new Quaternion();
|
|
|
|
const cameraRotation = new Euler().setFromQuaternion(
|
|
camera.quaternion,
|
|
"YXZ"
|
|
);
|
|
targetRotation.multiplyQuaternions(
|
|
targetRotation,
|
|
new Quaternion().setFromEuler(
|
|
new Euler(0, cameraRotation.y - Math.PI, 0)
|
|
)
|
|
);
|
|
|
|
model.scene.quaternion.slerp(targetRotation, 0.2);
|
|
} else {
|
|
//a
|
|
}
|
|
});
|
|
|
|
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 (
|
|
<>
|
|
<primitive
|
|
object={model.scene}
|
|
position={model.scene.position}
|
|
></primitive>
|
|
<directionalLight
|
|
ref={lightRef}
|
|
target={model.scene}
|
|
castShadow
|
|
position={[
|
|
model.scene.position.x + 100,
|
|
100,
|
|
model.scene.position.z + 100,
|
|
]}
|
|
shadow-mapSize={[2048, 2048]}
|
|
intensity={6}
|
|
>
|
|
<orthographicCamera
|
|
attach="shadow-camera"
|
|
args={[
|
|
-SHADOW_DRAW_DISTANCE,
|
|
SHADOW_DRAW_DISTANCE,
|
|
SHADOW_DRAW_DISTANCE,
|
|
-SHADOW_DRAW_DISTANCE,
|
|
]}
|
|
/>
|
|
</directionalLight>
|
|
</>
|
|
);
|
|
}
|