Skip to content

Commit

Permalink
Add models and controls
Browse files Browse the repository at this point in the history
  • Loading branch information
Nitash-Biswas committed Jul 12, 2024
1 parent d9afc32 commit bde836a
Show file tree
Hide file tree
Showing 13 changed files with 1,877 additions and 122 deletions.
60 changes: 60 additions & 0 deletions app/components/Character.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@6.2.3 public/models/character.glb -o src/components/Character.jsx -r public
*/

import { useAnimations, useGLTF } from "@react-three/drei";
import React, { useEffect, useRef } from "react";

export function Character({ animation, ...props }) {
const group = useRef();
const { nodes, materials, animations } = useGLTF("/models/character.glb");
const { actions } = useAnimations(animations, group);
useEffect(() => {
actions[animation]?.reset().fadeIn(0.24).play();
return () => actions?.[animation]?.fadeOut(0.24);
}, [animation]);
return (
<group ref={group} {...props} dispose={null}>
<group name="Scene">
<group name="fall_guys">
<primitive object={nodes._rootJoint} />
<skinnedMesh
name="body"
geometry={nodes.body.geometry}
material={materials.Material}
skeleton={nodes.body.skeleton}
castShadow
receiveShadow
/>
<skinnedMesh
name="eye"
geometry={nodes.eye.geometry}
material={materials.Material}
skeleton={nodes.eye.skeleton}
castShadow
receiveShadow
/>
<skinnedMesh
name="hand-"
geometry={nodes["hand-"].geometry}
material={materials.Material}
skeleton={nodes["hand-"].skeleton}
castShadow
receiveShadow
/>
<skinnedMesh
name="leg"
geometry={nodes.leg.geometry}
material={materials.Material}
skeleton={nodes.leg.skeleton}
castShadow
receiveShadow
/>
</group>
</group>
</group>
);
}

useGLTF.preload("/models/character.glb");
179 changes: 179 additions & 0 deletions app/components/CharacterController.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { useEffect, useRef, useState } from "react";
import { Character } from "./Character";
import { CapsuleCollider, RigidBody } from "@react-three/rapier";
import { MathUtils, MOUSE, Vector3 } from "three";
import { useFrame } from "@react-three/fiber";
import { useControls } from "leva";
import { useKeyboardControls } from "@react-three/drei";
import { degToRad } from "three/src/math/MathUtils";

const normalizeAngle = (angle) => {
while (angle > Math.PI) angle -= 2 * Math.PI;
while (angle < -Math.PI) angle += 2 * Math.PI;
return angle;
};

const lerpAngle = (start, end, t) => {
start = normalizeAngle(start);
end = normalizeAngle(end);

if (Math.abs(end - start) > Math.PI) {
if (end > start) {
start += 2 * Math.PI;
} else {
end += 2 * Math.PI;
}
}

return normalizeAngle(start + (end - start) * t);
};

const CharacterController = () => {
// LevaControls
const { WALK_SPEED, RUN_SPEED, ROTATION_SPEED } = useControls(
"Character Controls",
{
WALK_SPEED: { value: 0.8, min: 0.1, max: 4, step: 0.1 },
RUN_SPEED: { value: 1.6, min: 0.2, max: 12, step: 0.1 },
ROTATION_SPEED: {
value: degToRad(1),
min: degToRad(0.1),
max: degToRad(5),
step: degToRad(0.1),
},
}
);
//Variables
const rb = useRef();
const container = useRef();

const cameraTarget = useRef();
const cameraPosition = useRef();

const [animation, setAnimation] = useState("idle");
const character = useRef();
const characterRotationTarget = useRef(0);
const rotationTarget = useRef(0);

const cameraWorldPosition = useRef(new Vector3());
const cameraLookAtWorldPosition = useRef(new Vector3());
const cameraLookAt = useRef(new Vector3());
const [, get] = useKeyboardControls();
const isClicking = useRef(false);

//MOUSE CLICK
useEffect(() => {
const onMouseDown = (e) => (isClicking.current = true);
const onMouseUp = (e) => (isClicking.current = false);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mouseup", onMouseUp);
//TOUCH
document.addEventListener("touchstart", onMouseDown);
document.addEventListener("touchend",onMouseUp)
return () => {
document.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("touchstart", onMouseDown);
document.removeEventListener("touchend",onMouseUp)
};
}, []);

useFrame(({ camera, mouse }) => {
if (rb.current) {
const vel = rb.current.linvel();

const movement = {
x: 0,
z: 0,
};

//MOVEMENT DIRECTION
if (get().forward) {
movement.z = 1;
}
if (get().backward) {
movement.z = -1;
}
if (get().left) {
movement.x = 1;
}
if (get().right) {
movement.x = -1;
}

//MOVEMENT SPEED
let speed = get().run ? RUN_SPEED : WALK_SPEED;

if (isClicking.current) {
console.log("clicking", mouse.x, mouse.y);
if(Math.abs(mouse.x)>0.1){
movement.x = -mouse.x;
}

movement.z = mouse.y+0.4;
if(Math.abs(movement.x)>0.5||Math.abs(movement.z)>0.5){
speed = RUN_SPEED;
}
}

if (movement.x !== 0 || movement.z !== 0) {
characterRotationTarget.current = Math.atan2(movement.x, movement.z);
vel.x =
Math.sin(rotationTarget.current + characterRotationTarget.current) *
speed;
vel.z =
Math.cos(rotationTarget.current + characterRotationTarget.current) *
speed;
if (speed === RUN_SPEED) {
setAnimation("run");
} else {
setAnimation("walk");
}
} else {
setAnimation("idle");
}
character.current.rotation.y = lerpAngle(
character.current.rotation.y,
characterRotationTarget.current,
0.1
);
rb.current.setLinvel(vel, true);

//ROTATION
if (movement.x !== 0 || movement.z !== 0) {
rotationTarget.current += ROTATION_SPEED * movement.x;
}
}

//CAMERA
container.current.rotation.y = MathUtils.lerp(
container.current.rotation.y,
rotationTarget.current,
0.1
);
cameraPosition.current.getWorldPosition(cameraWorldPosition.current);
camera.position.lerp(cameraWorldPosition.current, 0.1);

if (cameraTarget.current) {
cameraTarget.current.getWorldPosition(cameraLookAtWorldPosition.current);
cameraLookAt.current.lerp(cameraLookAtWorldPosition.current, 0.1);

camera.lookAt(cameraLookAt.current);
}
});

return (
<RigidBody colliders={false} lockRotations ref={rb}>
<group ref={container}>
<group ref={cameraTarget} position-z={1.5} />
<group ref={cameraPosition} position-y={4} position-z={-4} />
<group ref={character}>
<Character scale={0.18} position-y={-0.25} animation={animation} />
</group>
</group>
<CapsuleCollider args={[0.08, 0.2]} />
</RigidBody>
);
};

export default CharacterController;
77 changes: 77 additions & 0 deletions app/components/Experience.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use client";
import {
Environment,
OrbitControls,
OrthographicCamera,
} from "@react-three/drei";
import { useControls } from "leva";
import { useRef } from "react";
import { Character } from "./Character";
import { Map } from "./Map";
import { Physics } from "@react-three/rapier";
import CharacterController from "./CharacterController";

const maps = {
castle_on_hills: {
scale: 3,
position: [-6, -7, 0],
},
animal_crossing_map: {
scale: 20,
position: [-15, -1, 10],
},
city_scene_tokyo: {
scale: 0.72,
position: [0, -1, -3.5],
},
de_dust_2_with_real_light: {
scale: 0.3,
position: [-5, -3, 13],
},
medieval_fantasy_book: {
scale: 0.4,
position: [-4, 0, -6],
},
};

export const Experience = () => {
const shadowCameraRef = useRef();
const { map } = useControls("Map", {
map: {
value: "castle_on_hills",
options: Object.keys(maps),
},
});

return (
<>
{/* <OrbitControls /> */}
<Environment preset="sunset" />
<directionalLight
intensity={0.65}
castShadow
position={[-15, 10, 15]}
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
shadow-bias={-0.00005}
>
<OrthographicCamera
left={-22}
right={15}
top={10}
bottom={-20}
ref={shadowCameraRef}
attach={"shadow-camera"}
/>
</directionalLight>
<Physics debug key={map}>
<Map
scale={maps[map].scale}
position={maps[map].position}
model={`models/${map}.glb`}
/>
<CharacterController />
</Physics>
</>
);
};
31 changes: 31 additions & 0 deletions app/components/Map.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useAnimations, useGLTF } from "@react-three/drei";
import { RigidBody } from "@react-three/rapier";
import { useEffect, useRef } from "react";

export const Map = ({ model, ...props }) => {
const { scene, animations } = useGLTF(model);
const group = useRef();
const { actions } = useAnimations(animations, group);
useEffect(() => {
scene.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
}, [scene]);

useEffect(() => {
if (actions && animations.length > 0) {
actions[animations[0].name].play();
}
}, [actions]);

return (
<group>
<RigidBody type="fixed" colliders="trimesh">
<primitive object={scene} {...props} ref={group} />
</RigidBody>
</group>
);
};
Loading

0 comments on commit bde836a

Please sign in to comment.