diff --git a/components/Experience.js b/components/Experience.js new file mode 100644 index 0000000..2f1aaaa --- /dev/null +++ b/components/Experience.js @@ -0,0 +1,30 @@ +import { OrbitControls, useRef } from "@react-three/drei"; +import Soldier from "./Soldier"; +import ZWorld from "./ZWorld"; + +const Experience = () => { + return ( + <> + + + + + + + + + + + ); +}; + +export default Experience; \ No newline at end of file diff --git a/components/Soldier.jsx b/components/Soldier.jsx new file mode 100644 index 0000000..7809195 --- /dev/null +++ b/components/Soldier.jsx @@ -0,0 +1,156 @@ +import React, { useEffect, useRef } from "react"; +import { OrbitControls, useGLTF, useAnimations } from "@react-three/drei"; +import useInputs from "@/hooks/useInputs"; +import { useFrame, useThree } from "@react-three/fiber"; +import * as THREE from "three"; + + +let walkDirection = new THREE.Vector3(); +let rotateAngle = new THREE.Vector3(0, 1, 0); +let rotateQuarternion = new THREE.Quaternion(); +let cameraTarget = new THREE.Vector3(); + + +const directionOffset = ({ forward, backward, left, right }) => { + var directionOffset = 0; + + if(forward) { + if(left) { + directionOffset = Math.PI / 4; + } else if (right) { + directionOffset = -Math.PI / 4; + } + } else if (backward) { + if (left) { + directionOffset = Math.PI / 4 + Math.PI / 2; + } else if (right) { + directionOffset = -Math.PI / 4 - Math.PI / 2; + } else { + directionOffset = Math.PI; + } + } else if (left) { + directionOffset = Math.PI; + } else if (right) { + directionOffset = -Math.PI / 2; + } + + return directionOffset; +} + + +const Soldier = (props) => { + const group = useRef(); + const { nodes, materials, animations } = useGLTF("./Model/Soldier.glb"); + const { actions } = useAnimations(animations, group); + const { forward, backward, left, right, shift } = useInputs(); + + const currentAction = useRef(""); + const controlsRef = useRef(); + const camera = useThree((state) => state.camera); + + const updateCameraTarget = (moveX, moveZ) => { + //move Camera + camera.position.x += moveX; + camera.position.z += moveZ; + + //update camera target + cameraTarget.x = group.current.position.x; + cameraTarget.y = group.current.position.y + 1; + cameraTarget.z = group.current.position.z; + if(controlsRef.current) controlsRef.current.target = cameraTarget; + }; + + useEffect(() => { + let action = ""; + + if(forward || backward || left || right) { + action = "Walk"; + + if(shift) { + action = "Run"; + } + } else { + action = "Idle"; + } + + if(currentAction.current != action) { + const nextActionToPlay = actions[action]; + const current = actions[currentAction.current]; + current?.fadeOut(0.2); + nextActionToPlay?.reset().fadeIn(0.2).play(); + currentAction.current = action; + } + + }, [forward, backward, left, right, shift]); + + useFrame((state, delta) => { + //camera direction + if(currentAction.current == "Run" || currentAction.current == "Walk") { + let angleYCameraDirection = Math.atan2( + camera.position.x - group.current.position.x, + camera.position.z - group.current.position.z + ); + + //diagonal movement angle offset + let newDirectionOffset = directionOffset({ + forward, + backward, + left, + right, + }); + + // rotate model + rotateQuarternion.setFromAxisAngle( + rotateAngle, + angleYCameraDirection + newDirectionOffset + ); + group.current.quaternion.rotateTowards(rotateQuarternion, 0.2) + + + // Calculate Direction + camera.getWorldDirection(walkDirection); + walkDirection.y = 0; + walkDirection.normalize(); + walkDirection.applyAxisAngle(rotateAngle, newDirectionOffset); + + // run/walk velocity + const velocity = currentAction.current == "Run" ? 60 : 30; + + // Move model & camera + const moveX = walkDirection.x * velocity * delta; + const moveZ = walkDirection.z * velocity * delta; + group.current.position.x += moveX; + group.current.position.z += moveZ; + updateCameraTarget(moveX, moveZ); + } + }) + + return ( + <> + + + + + + + + + + + + ); +} + +export default Soldier; + +useGLTF.preload("./Model/Soldier.glb"); diff --git a/components/ZWorld.jsx b/components/ZWorld.jsx new file mode 100644 index 0000000..10aecb8 --- /dev/null +++ b/components/ZWorld.jsx @@ -0,0 +1,337 @@ +import React, { useRef } from "react"; +import { useGLTF, useAnimations } from "@react-three/drei"; + +const ZWorld = (props) => { + const group = useRef(); + const { nodes, materials, animations } = useGLTF("./Model/zworld.glb"); + const { actions } = useAnimations(animations, group); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +export default ZWorld; + +useGLTF.preload("./Model/zworld.glb"); diff --git a/hooks/useInputs.js b/hooks/useInputs.js new file mode 100644 index 0000000..4537717 --- /dev/null +++ b/hooks/useInputs.js @@ -0,0 +1,39 @@ +import { useEffect, useState } from "react"; + +const useInputs = () => { + const keys = { + KeyW: 'forward', + KeyS: 'backward', + KeyA: 'left', + KeyD: 'right', + ShiftLeft: 'shift', + } + + const moveFieldByKey = (key) => keys[key] + + const [movement, setMovement] = useState({ + forward: false, + backward: false, + left: false, + right: false, + shift: false, + }) + + useEffect(() => { + const handleKeyDown = (e) => { + setMovement((m) => ({ ...m, [moveFieldByKey(e.code)]: true })) + } + const handleKeyUp = (e) => { + setMovement((m) => ({ ...m, [moveFieldByKey(e.code)]: false })) + } + document.addEventListener('keydown', handleKeyDown) + document.addEventListener('keyup', handleKeyUp) + return () => { + document.removeEventListener('keydown', handleKeyDown) + document.removeEventListener('keyup', handleKeyUp) + } + }, []) + return movement + } + + export default useInputs; \ No newline at end of file diff --git a/pages/world.js b/pages/world.js new file mode 100644 index 0000000..3f88119 --- /dev/null +++ b/pages/world.js @@ -0,0 +1,14 @@ +import { Canvas } from "@react-three/fiber"; +import Experience from "../components/Experience"; + +const world = () => { + return ( + <> + + + + + ) +} + +export default world \ No newline at end of file diff --git a/public/Model/Soldier.glb b/public/Model/Soldier.glb new file mode 100644 index 0000000..1788f12 Binary files /dev/null and b/public/Model/Soldier.glb differ diff --git a/public/Model/zworld.glb b/public/Model/zworld.glb new file mode 100644 index 0000000..64c1ed8 Binary files /dev/null and b/public/Model/zworld.glb differ diff --git a/styles/globals.css b/styles/globals.css index 23d6a76..63ba0a0 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -150,3 +150,7 @@ a { filter: blur(10px); } +canvas{ + height: 100vh; + width: 100vw; +}