Search code examples
react-three-fiber

How to control movement of a person in react three fiber?


I want to create a game where I have to make my model controllable with keyboard input. I don't know what's the best way to do it and how to implement it properly.


Solution

  • We can implement this with cannon.js

    1. Create a custom hook to listen to user's input.
    const usePersonControls = () => {
      const keys = {
        KeyW: 'forward',
        KeyS: 'backward',
        KeyA: 'left',
        KeyD: 'right',
        Space: 'jump',
      }
    
      const moveFieldByKey = (key) => keys[key]
    
      const [movement, setMovement] = useState({
        forward: false,
        backward: false,
        left: false,
        right: false,
        jump: 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
    }
    

    Now use it like so,

    const { forward, backward, left, right, jump } = usePersonControls()
    
    1. Create a body for person in Cannon World.
    const [mesh, api] = useSphere(() => ({
        mass: 10,
        position: [0, 1, 0],
        type: 'Dynamic',
    }))
    
    1. Apply velocity to sphere body.
    useFrame(() => {
        // Calculating front/side movement ...
        let frontVector = new Vector3(0,0,0);
        let sideVector = new Vector3(0,0,0);
        let direction = new Vector3(0,0,0);
    
        frontVector.set(0, 0, Number(forward) - Number(backward))
        sideVector.set(Number(right) - Number(left), 0, 0)
        direction
          .subVectors(frontVector, sideVector)
          .normalize()
          .multiplyScalar(SPEED)
    
        api.velocity.set(direction.x, 0, direction.z)
    })
    
    1. Now our sphere body is able to move with user's input in Cannon World, so now just update your player model in fiber using sphere's position on each frame, like so
    // Setting person model position to sphere body position ...
    useFrame(() => {
        ...
        mesh.current.getWorldPosition(playerModelReference.current.position)
    })
    

    Sorry for long explanation, hope you find it helpful.