Skip to content

Commit

Permalink
Feat #50: Add multiplayer rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
Chosamee committed May 9, 2024
1 parent 44b3af6 commit 80255c0
Show file tree
Hide file tree
Showing 5 changed files with 376 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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 (
<group>
<RigidBody
ref={rigidbody}
colliders={false}
scale={[0.5, 0.5, 0.5]}
enabledRotations={[false, false, false]}
onCollisionEnter={() => {
isOnFloor.current = true
}}
onIntersectionEnter={({ other }) => {
if (other.rigidBodyObject.name === 'void') {
resetPosition()
// Todo: ๊ฒŒ์ž„ ์‹œ์ž‘ ์˜ค๋””์˜ค ์‚ฝ์ž…ํ•˜๊ธฐ!
playAudio('fall', () => {
playAudio('ganbatte')
})
}
}}
>
<CapsuleCollider args={[0.8, 0.4]} position={[0, 1.2, 0]} />
<group ref={character}>
<Character pos={rigidbody.current && rigidbody.current.linvel()} />
</group>
</RigidBody>
</group>
)
// ๊ฒŒ์ž„ ์ง„ํ–‰ ์ƒํƒœ
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 (
<group>
<RigidBody
ref={rigidbody}
colliders={false}
scale={[0.5, 0.5, 0.5]}
enabledRotations={[false, false, false]}
onCollisionEnter={() => {
isOnFloor.current = true
}}
onIntersectionEnter={({ other }) => {
if (other.rigidBodyObject.name === 'void') {
resetPosition()
// Todo: ๊ฒŒ์ž„ ์‹œ์ž‘ ์˜ค๋””์˜ค ์‚ฝ์ž…ํ•˜๊ธฐ!
playAudio('fall', () => {
playAudio('ganbatte')
})
}
}}
>
<CapsuleCollider
args={[0.8, 0.4]}
position={[0, 1.2, 0]}
restitution={1} // ๋ฐ˜๋ฐœ๋ ฅ ์„ค์ •: 0(์™„์ „ ํก์ˆ˜) ~ 1(์™„์ „ ๋ฐ˜์‚ฌ)
friction={0.1} // ๋งˆ์ฐฐ๋ ฅ ์„ค์ •
/>
<group ref={character}>
<Character pos={rigidbody.current && rigidbody.current.linvel()} />
</group>
</RigidBody>
</group>
)
}

export default CharacterController
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -64,6 +65,7 @@ const GameField = () => {
{/* {gameState === gameStateEnum.GAME ? <AnswerSpot /> : <TeamSpot />} */}
{/* <>TODO: OtherPlayers -> Socket์—์„œ ๋ฐ์ดํ„ฐ ๋ฐ›๊ธฐ map()
์•ˆ์—๋‹ค๊ฐ€ OtherCharacter -> pos, action, ์ด๊ฒƒ์ €๊ฒƒ props ์บ๋ฆญํ„ฐ ๋ Œ๋”๋ง </> */}
<OtherPlayers />
<AnswerSpot />
{/* <TeamSpot /> */}
</group>
Expand Down
Loading

0 comments on commit 80255c0

Please sign in to comment.