diff --git a/frontend/app/(page)/(needProtection)/game/component/CharacterController.jsx b/frontend/app/(page)/(needProtection)/game/component/CharacterController.jsx index f0bec4d..f4eea45 100644 --- a/frontend/app/(page)/(needProtection)/game/component/CharacterController.jsx +++ b/frontend/app/(page)/(needProtection)/game/component/CharacterController.jsx @@ -6,11 +6,11 @@ import Character from './Character' import * as THREE from 'three' import { - gameStateEnum, - playerMoveStateEnum, - useCharacterSelectStore, - useGameRoomStore, - usePlayerStore, + gameStateEnum, + playerMoveStateEnum, + useCharacterSelectStore, + useGameRoomStore, + usePlayerStore, } from '../lib/store' import { controls } from './KeyboardControl' @@ -20,154 +20,159 @@ const MAX_VEL = 3 const RUN_VEL = 1.5 const CharacterController = () => { - // 게임 진행 상태 - const { gameState } = useGameRoomStore((state) => ({ - gameState: state.gameState, - })) - - // 플레이어 상태 - const { playerMoveState, setPlayerMoveState, playerTeamState } = usePlayerStore((state) => ({ - playerMoveState: state.playerMoveState, - setPlayerMoveState: state.setPlayerMoveState, - playerTeamState: state.playerTeamState, - })) - - const { characterIndex } = useCharacterSelectStore() - - const jumpPressed = useKeyboardControls((state) => state[controls.jump]) - const leftPressed = useKeyboardControls((state) => state[controls.left]) - const rightPressed = useKeyboardControls((state) => state[controls.right]) - const backPressed = useKeyboardControls((state) => state[controls.back]) - const forwardPressed = useKeyboardControls((state) => state[controls.forward]) - const rigidbody = useRef() - const isOnFloor = useRef(true) - const character = useRef() - const cameraLookAt = useRef(new THREE.Vector3()).current - - useEffect(() => { - if (!rigidbody.current) return - // 캐릭터가 이동할 때마다 좌표 받아오기 - // console.log('rigidbody.current', rigidbody.current.linvel()) - }) - - useFrame((state, delta) => { - if (!rigidbody.current) return - - const impulse = { x: 0, y: 0, z: 0 } - if (jumpPressed && isOnFloor.current) { - impulse.y += JUMP_FORCE - setPlayerMoveState(playerMoveStateEnum.JUMP) - isOnFloor.current = false - } - - const linvel = rigidbody.current.linvel() - let changeRotation = false - if (rightPressed && linvel.x < MAX_VEL) { - impulse.x += MOVEMENT_SPEED - changeRotation = true - } - if (leftPressed && linvel.x > -MAX_VEL) { - impulse.x -= MOVEMENT_SPEED - changeRotation = true - } - if (backPressed && linvel.z < MAX_VEL) { - impulse.z += MOVEMENT_SPEED - changeRotation = true - } - if (forwardPressed && linvel.z > -MAX_VEL) { - impulse.z -= MOVEMENT_SPEED - changeRotation = true - } - - rigidbody.current.applyImpulse(impulse, true) - - if (Math.abs(linvel.x) > RUN_VEL || Math.abs(linvel.z) > RUN_VEL) { - if (isOnFloor.current && playerMoveState !== playerMoveStateEnum.RUN) { - setPlayerMoveState(playerMoveStateEnum.RUN) - } - } else { - if (isOnFloor.current && playerMoveState !== playerMoveStateEnum.IDLE) { - setPlayerMoveState(playerMoveStateEnum.IDLE) - } - } - - if (changeRotation) { - const angle = Math.atan2(linvel.x, linvel.z) - character.current.rotation.y = angle - } - - // CAMERA FOLLOW - const characterWorldPosition = character.current.getWorldPosition(new THREE.Vector3()) - - const targetCameraPosition = new THREE.Vector3( - characterWorldPosition.x, - 0, - characterWorldPosition.z + 14 - ) - - // 게임 시작 시 카메라 위치 - if (gameState === gameStateEnum.GAME) { - targetCameraPosition.y = 6 - } - // 게임 시작 전 카메라 위치 - if (gameState !== gameStateEnum.GAME) { - targetCameraPosition.y = 0 - } - - state.camera.position.lerp(targetCameraPosition, delta * 2) - - const targetLookAt = new THREE.Vector3(characterWorldPosition.x, 0, characterWorldPosition.z) - - const direction = new THREE.Vector3() - state.camera.getWorldDirection(direction) - - const position = new THREE.Vector3() - state.camera.getWorldPosition(position) - - const currentLookAt = position.clone().add(direction) - const lerpedLookAt = new THREE.Vector3() - - lerpedLookAt.lerpVectors(currentLookAt, targetLookAt, delta * 2) - - // 바라보는 위치 부드럽게 이동 - cameraLookAt.lerp(targetLookAt, delta * 2) - - // 카메라가 실제로 바라보게 설정 - state.camera.lookAt(cameraLookAt) - }) - - const resetPosition = () => { - rigidbody.current.setTranslation(vec3({ x: 0, y: 0, z: 0 })) - rigidbody.current.setLinvel(vec3({ x: 0, y: 0, z: 0 })) - } - - return ( - - { - isOnFloor.current = true - }} - onIntersectionEnter={({ other }) => { - if (other.rigidBodyObject.name === 'void') { - resetPosition() - // Todo: 게임 시작 오디오 삽입하기! - playAudio('fall', () => { - playAudio('ganbatte') - }) - } - }} - > - - - - - - - ) + // 게임 진행 상태 + const { gameState } = useGameRoomStore((state) => ({ + gameState: state.gameState, + })) + + // 플레이어 상태 + const { playerMoveState, setPlayerMoveState, playerTeamState } = usePlayerStore((state) => ({ + playerMoveState: state.playerMoveState, + setPlayerMoveState: state.setPlayerMoveState, + playerTeamState: state.playerTeamState, + })) + + const { characterIndex } = useCharacterSelectStore() + + const jumpPressed = useKeyboardControls((state) => state[controls.jump]) + const leftPressed = useKeyboardControls((state) => state[controls.left]) + const rightPressed = useKeyboardControls((state) => state[controls.right]) + const backPressed = useKeyboardControls((state) => state[controls.back]) + const forwardPressed = useKeyboardControls((state) => state[controls.forward]) + const rigidbody = useRef() + const isOnFloor = useRef(true) + const character = useRef() + const cameraLookAt = useRef(new THREE.Vector3()).current + + useEffect(() => { + if (!rigidbody.current) return + // 캐릭터가 이동할 때마다 좌표 받아오기 + console.log('rigidbody.current', rigidbody.current.linvel()) + }, [jumpPressed, leftPressed, rightPressed, backPressed, forwardPressed]) + + useFrame((state, delta) => { + if (!rigidbody.current) return + + const impulse = { x: 0, y: 0, z: 0 } + if (jumpPressed && isOnFloor.current) { + impulse.y += JUMP_FORCE + setPlayerMoveState(playerMoveStateEnum.JUMP) + isOnFloor.current = false + } + + const linvel = rigidbody.current.linvel() + let changeRotation = false + if (rightPressed && linvel.x < MAX_VEL) { + impulse.x += MOVEMENT_SPEED + changeRotation = true + } + if (leftPressed && linvel.x > -MAX_VEL) { + impulse.x -= MOVEMENT_SPEED + changeRotation = true + } + if (backPressed && linvel.z < MAX_VEL) { + impulse.z += MOVEMENT_SPEED + changeRotation = true + } + if (forwardPressed && linvel.z > -MAX_VEL) { + impulse.z -= MOVEMENT_SPEED + changeRotation = true + } + + rigidbody.current.applyImpulse(impulse, true) + + if (Math.abs(linvel.x) > RUN_VEL || Math.abs(linvel.z) > RUN_VEL) { + if (isOnFloor.current && playerMoveState !== playerMoveStateEnum.RUN) { + setPlayerMoveState(playerMoveStateEnum.RUN) + } + } else { + if (isOnFloor.current && playerMoveState !== playerMoveStateEnum.IDLE) { + setPlayerMoveState(playerMoveStateEnum.IDLE) + } + } + + if (changeRotation) { + const angle = Math.atan2(linvel.x, linvel.z) + character.current.rotation.y = angle + } + + // CAMERA FOLLOW + const characterWorldPosition = character.current.getWorldPosition(new THREE.Vector3()) + + const targetCameraPosition = new THREE.Vector3( + characterWorldPosition.x, + 0, + characterWorldPosition.z + 14 + ) + + // 게임 시작 시 카메라 위치 + if (gameState === gameStateEnum.GAME) { + targetCameraPosition.y = 6 + } + // 게임 시작 전 카메라 위치 + if (gameState !== gameStateEnum.GAME) { + targetCameraPosition.y = 0 + } + + state.camera.position.lerp(targetCameraPosition, delta * 2) + + const targetLookAt = new THREE.Vector3(characterWorldPosition.x, 0, characterWorldPosition.z) + + const direction = new THREE.Vector3() + state.camera.getWorldDirection(direction) + + const position = new THREE.Vector3() + state.camera.getWorldPosition(position) + + const currentLookAt = position.clone().add(direction) + const lerpedLookAt = new THREE.Vector3() + + lerpedLookAt.lerpVectors(currentLookAt, targetLookAt, delta * 2) + + // 바라보는 위치 부드럽게 이동 + cameraLookAt.lerp(targetLookAt, delta * 2) + + // 카메라가 실제로 바라보게 설정 + state.camera.lookAt(cameraLookAt) + }) + + const resetPosition = () => { + rigidbody.current.setTranslation(vec3({ x: 0, y: 0, z: 0 })) + rigidbody.current.setLinvel(vec3({ x: 0, y: 0, z: 0 })) + } + + return ( + + { + isOnFloor.current = true + }} + onIntersectionEnter={({ other }) => { + if (other.rigidBodyObject.name === 'void') { + resetPosition() + // Todo: 게임 시작 오디오 삽입하기! + playAudio('fall', () => { + playAudio('ganbatte') + }) + } + }} + > + + + + + + + ) } export default CharacterController diff --git a/frontend/app/(page)/(needProtection)/game/component/GameField.tsx b/frontend/app/(page)/(needProtection)/game/component/GameField.tsx index cf02612..585428b 100644 --- a/frontend/app/(page)/(needProtection)/game/component/GameField.tsx +++ b/frontend/app/(page)/(needProtection)/game/component/GameField.tsx @@ -4,6 +4,7 @@ import AnswerSpot from './AnswerSpot' import React, { useEffect } from 'react' import CharacterController from './CharacterController' import { teamEnum, useGameRoomStore, usePlayerStore } from '../lib/store' +import OtherPlayers from './OtherPlayers' const GameField = () => { const { startGame, gameState } = useGameRoomStore() @@ -64,6 +65,7 @@ const GameField = () => { {/* {gameState === gameStateEnum.GAME ? : } */} {/* <>TODO: OtherPlayers -> Socket에서 데이터 받기 map() 안에다가 OtherCharacter -> pos, action, 이것저것 props 캐릭터 렌더링 */} + {/* */} diff --git a/frontend/app/(page)/(needProtection)/game/component/Other.tsx b/frontend/app/(page)/(needProtection)/game/component/OtherCharacter.tsx similarity index 57% rename from frontend/app/(page)/(needProtection)/game/component/Other.tsx rename to frontend/app/(page)/(needProtection)/game/component/OtherCharacter.tsx index 252f7bd..3209428 100644 --- a/frontend/app/(page)/(needProtection)/game/component/Other.tsx +++ b/frontend/app/(page)/(needProtection)/game/component/OtherCharacter.tsx @@ -1,9 +1,9 @@ -// @ts-nocheck 'use client' import { Html, useAnimations, useGLTF } from '@react-three/drei' import React, { useEffect, useRef } from 'react' import { teamEnum, useCharacterSelectStore, useModalStore, usePlayerStore } from '../lib/store' +import { IOtherStatus } from './OtherPlayers' /* 모델별 scale 조정 @@ -17,28 +17,36 @@ model6 -> 1 닉네임 y 위치 1 -> 3 0.64 -> 4.7 + +안녕하세요 저는 이재민입니다 +저의 취미는 던전앤파이터입니다. +제가 좋아하는 색깔은 연분홍입니다. +저의 닮은꼴 캐릭터는 스누피의 찰리브라운입니다. +저는 빨간 리본이 잘 어울립니다 ㅎ.ㅎ"> */ -const pathObj = { - 0: '/models/custom/custom-model0.gltf', - 1: '/models/custom/custom-model1.gltf', - 2: '/models/custom/custom-model2.gltf', - 3: '/models/custom/custom-model3.gltf', - 4: '/models/custom/custom-model4.gltf', - 5: '/models/custom/custom-model5.gltf', -} -export default function Character({ pos }) { +const pathObj = [ + '/models/custom/custom-model0.gltf', + '/models/custom/custom-model1.gltf', + '/models/custom/custom-model2.gltf', + '/models/custom/custom-model3.gltf', + '/models/custom/custom-model4.gltf', + '/models/custom/custom-model5.gltf', +] +export default function OtherCharacter({ + pos, + moveState, + characterType, + direction, + nickname, + team, +}: IOtherStatus) { const groupRef = useRef() - const nickname = '꽁꽁얼어붙은한강위에고양이가걸어다닙니다.' - const { characterIndex } = useCharacterSelectStore() + // const nickname = '꽁꽁얼어붙은한강위에고양이가걸어다닙니다.' - const { nodes, animations, scene } = useGLTF(pathObj[characterIndex]) + const { nodes, animations, scene } = useGLTF(pathObj[characterType]) const { actions } = useAnimations(animations, scene) - const { playerMoveState, playerTeamState } = usePlayerStore((state) => ({ - playerMoveState: state.playerMoveState, - playerTeamState: state.playerTeamState, - })) const { isModalOpen } = useModalStore((state) => ({ isModalOpen: state.isModalOpen, })) @@ -54,12 +62,12 @@ export default function Character({ pos }) { useEffect(() => { if (!actions) return - actions[playerMoveState].reset().fadeIn(0.2).play() + actions[moveState].reset().fadeIn(0.2).play() return () => { - if (!actions[playerMoveState]) return - actions[playerMoveState].fadeOut(0.2) + if (!actions[moveState]) return + actions[moveState].fadeOut(0.2) } - }, [playerMoveState, actions]) + }, [moveState, actions]) return ( @@ -69,9 +77,9 @@ export default function Character({ pos }) {
{ + // 플레이어 상태 + + const rigidbody = useRef() + const isOnFloor = useRef(true) + const character = useRef() + + useEffect(() => { + if (!rigidbody.current) return + rigidbody.current.setTranslation(vec3({ x: pos.x, y: pos.y, z: pos.z })) + rigidbody.current.setLinvel(vec3({ x: pos.x, y: pos.y, z: pos.z })) + // 캐릭터가 이동할 때마다 좌표 받아오기 + // console.log('rigidbody.current', rigidbody.current.linvel()) + }, [pos]) + + useFrame((state, delta) => { + if (!rigidbody.current) return + + // vec3({ x: pos.x, y: pos.y, z: pos.z }) + + const impulse = { x: 0, y: 0, z: 0 } + if (moveState === playerMoveStateEnum.JUMP && isOnFloor.current) { + impulse.y += JUMP_FORCE + isOnFloor.current = false + } + + const linvel = rigidbody.current.linvel() + let changeRotation = false + if (direction === 'right') { + if (moveState === playerMoveStateEnum.RUN && linvel.x < MAX_VEL) impulse.x += MOVEMENT_SPEED + changeRotation = true + } + if (direction === 'left') { + if (moveState === playerMoveStateEnum.RUN && linvel.x > -MAX_VEL) impulse.x -= MOVEMENT_SPEED + changeRotation = true + } + if (direction === 'back') { + if (moveState === playerMoveStateEnum.RUN && linvel.z < MAX_VEL) impulse.z += MOVEMENT_SPEED + changeRotation = true + } + if (direction === 'forward') { + if (moveState === playerMoveStateEnum.RUN && linvel.z > -MAX_VEL) impulse.z -= MOVEMENT_SPEED + changeRotation = true + } + + rigidbody.current.applyImpulse(impulse, true) + + // if (Math.abs(linvel.x) > RUN_VEL || Math.abs(linvel.z) > RUN_VEL) { + // if (isOnFloor.current && moveState !== playerMoveStateEnum.RUN) { + // setPlayerMoveState(playerMoveStateEnum.RUN) + // } + // } else { + // if (isOnFloor.current && moveState !== playerMoveStateEnum.IDLE) { + // setPlayerMoveState(playerMoveStateEnum.IDLE) + // } + // } + + if (changeRotation) { + const angle = Math.atan2(linvel.x, linvel.z) + character.current.rotation.y = angle + } + }) + + const resetPosition = () => { + rigidbody.current.setTranslation(vec3({ x: 0, y: 0, z: 0 })) + rigidbody.current.setLinvel(vec3({ x: 0, y: 0, z: 0 })) + } + return ( + + { + isOnFloor.current = true + }} + onIntersectionEnter={({ other }) => { + if (other.rigidBodyObject.name === 'void') { + resetPosition() + } + }} + > + + + + + + + ) +} + +export default OtherController diff --git a/frontend/app/(page)/(needProtection)/game/component/OtherPlayers.tsx b/frontend/app/(page)/(needProtection)/game/component/OtherPlayers.tsx new file mode 100644 index 0000000..aaa69b8 --- /dev/null +++ b/frontend/app/(page)/(needProtection)/game/component/OtherPlayers.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import { useMainSocketStore } from '../../channel/lib/store' +import { playerMoveStateEnum } from '../lib/store' +import OtherController from './OtherController' + +// forward: 'forward', +// back: 'back', +// left: 'left', +// right: 'right', +// jump: 'jump', +// RED, BLUE, NONE + +export interface IOtherStatus { + pos: { x: number; y: number; z: number } + moveState: string + characterType: number + direction: string + nickname: string + team: string +} + +const samplePlayers: IOtherStatus[] = [ + { + pos: { x: 0, y: 0, z: 3 }, + moveState: playerMoveStateEnum.IDLE, + characterType: 2, + direction: 'right', + nickname: '??dsad', + team: 'red', + }, + // { + // pos: { x: 4, y: 0, z: 0 }, + // moveState: playerMoveStateEnum.RUN, + // characterType: 1, + // direction: 'right', + // nickname: '!!!옆에대단한사람이있어요', + // team: 'blue', + // }, +] + +export default function OtherPlayers() { + const { socket } = useMainSocketStore() + return ( + <> + {samplePlayers.map((player, index) => { + return + })} + + ) +}