diff --git a/src/world/World.tsx b/src/world/World.tsx
index 8213516..3354220 100644
--- a/src/world/World.tsx
+++ b/src/world/World.tsx
@@ -5,7 +5,7 @@ import { DebugHelpers } from "@/utils/debug/scene/DebugHelpers";
import { Environment } from "@/world/Environment";
import { Lighting } from "@/world/Lighting";
import { Map } from "@/world/Map";
-import { FPSController } from "@/world/player/FPSController";
+import { PlayerComponent } from "@/world/player/PlayerComponent";
export function World(): React.JSX.Element {
const cameraMode = useCameraMode();
@@ -15,7 +15,7 @@ export function World(): React.JSX.Element {
- {cameraMode === "debug" ? : }
+ {cameraMode === "debug" ? : }
diff --git a/src/world/player/PlayerCamera.tsx b/src/world/player/PlayerCamera.tsx
new file mode 100644
index 0000000..ae7ce5d
--- /dev/null
+++ b/src/world/player/PlayerCamera.tsx
@@ -0,0 +1,25 @@
+import { useEffect } from "react";
+import { PointerLockControls } from "@react-three/drei";
+import { useThree } from "@react-three/fiber";
+import * as THREE from "three";
+
+export const PLAYER_EYE_HEIGHT = 1.75;
+
+const PLAYER_SPAWN_POSITION = new THREE.Vector3(0, PLAYER_EYE_HEIGHT, 6);
+const PLAYER_LOOK_AT = new THREE.Vector3(0, PLAYER_EYE_HEIGHT, 0);
+
+export function PlayerCamera(): React.JSX.Element {
+ const camera = useThree((state) => state.camera);
+
+ useEffect(() => {
+ camera.position.copy(PLAYER_SPAWN_POSITION);
+ camera.lookAt(PLAYER_LOOK_AT);
+ camera.updateProjectionMatrix();
+
+ return () => {
+ document.exitPointerLock?.();
+ };
+ }, [camera]);
+
+ return ;
+}
diff --git a/src/world/player/PlayerComponent.tsx b/src/world/player/PlayerComponent.tsx
new file mode 100644
index 0000000..8cd1f19
--- /dev/null
+++ b/src/world/player/PlayerComponent.tsx
@@ -0,0 +1,11 @@
+import { PlayerCamera } from "@/world/player/PlayerCamera";
+import { PlayerController } from "@/world/player/PlayerController";
+
+export function PlayerComponent(): React.JSX.Element {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/world/player/FPSController.tsx b/src/world/player/PlayerController.tsx
similarity index 77%
rename from src/world/player/FPSController.tsx
rename to src/world/player/PlayerController.tsx
index 40bf4d7..92a8206 100644
--- a/src/world/player/FPSController.tsx
+++ b/src/world/player/PlayerController.tsx
@@ -1,11 +1,11 @@
import { useEffect, useRef } from "react";
-import { PointerLockControls } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";
+import { PLAYER_EYE_HEIGHT } from "@/world/player/PlayerCamera";
-const PLAYER_EYE_HEIGHT = 1.75;
-const PLAYER_SPAWN_POSITION = new THREE.Vector3(0, PLAYER_EYE_HEIGHT, 6);
-const PLAYER_LOOK_AT = new THREE.Vector3(0, PLAYER_EYE_HEIGHT, 0);
+const JUMP_HEIGHT = 1;
+const GRAVITY = 18;
+const JUMP_VELOCITY = Math.sqrt(2 * GRAVITY * JUMP_HEIGHT);
const MOVE_SPEED = 5;
type PlayerKeys = {
@@ -22,47 +22,43 @@ const DEFAULT_KEYS: PlayerKeys = {
right: false,
};
-export function FPSController(): React.JSX.Element {
+export function PlayerController(): null {
const camera = useThree((state) => state.camera);
const keys = useRef({ ...DEFAULT_KEYS });
const interact = useRef<() => void>(() => {});
+ const verticalVelocity = useRef(0);
const forward = useRef(new THREE.Vector3());
const right = useRef(new THREE.Vector3());
const movement = useRef(new THREE.Vector3());
const up = useRef(new THREE.Vector3(0, 1, 0));
- useEffect(() => {
- camera.position.copy(PLAYER_SPAWN_POSITION);
- camera.lookAt(PLAYER_LOOK_AT);
- camera.updateProjectionMatrix();
-
- return () => {
- document.exitPointerLock?.();
- };
- }, [camera]);
-
useEffect(() => {
const handleKeyChange =
(pressed: boolean) =>
(event: KeyboardEvent): void => {
- switch (event.code) {
- case "KeyZ":
+ switch (event.key.toLowerCase()) {
+ case "z":
keys.current.forward = pressed;
break;
- case "KeyS":
+ case "s":
keys.current.backward = pressed;
break;
- case "KeyQ":
+ case "q":
keys.current.left = pressed;
break;
- case "KeyD":
+ case "d":
keys.current.right = pressed;
break;
- case "KeyE":
+ case "e":
if (pressed) {
interact.current();
}
break;
+ case " ":
+ if (pressed && camera.position.y <= PLAYER_EYE_HEIGHT) {
+ verticalVelocity.current = JUMP_VELOCITY;
+ }
+ break;
default:
return;
}
@@ -81,7 +77,7 @@ export function FPSController(): React.JSX.Element {
window.removeEventListener("keyup", handleKeyUp);
keys.current = { ...DEFAULT_KEYS };
};
- }, []);
+ }, [camera]);
useFrame((_, delta) => {
const currentForward = forward.current;
@@ -119,7 +115,13 @@ export function FPSController(): React.JSX.Element {
camera.position.add(currentMovement);
}
+ verticalVelocity.current -= GRAVITY * delta;
+
+ const nextY = camera.position.y + verticalVelocity.current * delta;
+ camera.position.set(camera.position.x, nextY, camera.position.z);
+
if (camera.position.y < PLAYER_EYE_HEIGHT) {
+ verticalVelocity.current = 0;
camera.position.set(
camera.position.x,
PLAYER_EYE_HEIGHT,
@@ -128,5 +130,5 @@ export function FPSController(): React.JSX.Element {
}
});
- return ;
+ return null;
}