Problems with character movement reactjs threejs

Viewed 120

I am making a 3D walkable world. Implemented the keyboard movement using wasd and space and mouse movement. I have no problem with the mouse movement, but when pressing the keys on the keyboard it doesnt work. I have seen the example multiple times and it works, but for me it doesnt seem so.

I have a useKeyboardInput.js where I add the event listeners and it registers when i`m pressing the key.

import { useCallback, useEffect, useState } from "react";

export const useKeyboardInput = (keysToListen = []) => {
  const getKeys = useCallback(() => {
    const lowerCaseArray = [];
    const hookReturn = {};

    keysToListen.forEach((key) => {
      const lowerCaseKey = key.toLowerCase();
      lowerCaseArray.push(lowerCaseKey);
      hookReturn[lowerCaseKey] = false;
    });

    return {
      lowerCaseArray,
      hookReturn,
    };
  }, [keysToListen]);

  const [keysPressed, setPressedKeys] = useState(getKeys().hookReturn);

  useEffect(() => {
    const handleKeyDown = (e) => {
      const lowerKey = e.key.toLowerCase();
      if (getKeys().lowerCaseArray.includes(lowerKey)) {
        setPressedKeys((keysPressed) => ({ ...keysPressed, [lowerKey]: true }));
      }
      console.log("Pressed Key is: " + e.key);
    };
    const handleKeyUp = (e) => {
      const lowerKey = e.key.toLowerCase();
      if (getKeys().lowerCaseArray.includes(lowerKey)) {
        setPressedKeys((keysPressed) => ({
          ...keysPressed,
          [lowerKey]: false,
        }));
      }
    };

    document.addEventListener("keydown", handleKeyDown);
    document.addEventListener("keyup", handleKeyUp);

    return () => {
      document.removeEventListener("keydown", handleKeyDown);
      document.removeEventListener("keyup", handleKeyUp);
    };
  }, [keysToListen, getKeys]);

  console.log("KeysPressed is: " + JSON.stringify(keysPressed));
  return keysPressed;
};

And then i have the Player.js where i am using the hooks for keyboard and mouse.

import { useSphere } from "@react-three/cannon";
import React, { useEffect, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { Vector3 } from "three";
import { useKeyboardInput } from "../Hooks/useKeyboardInput";
import { useMouseInput } from "../Hooks/useMouseInput";
import { useVariable } from "../Hooks/useVariable";
import { Bullet } from "./Bullet";
import { Raycaster } from "three";

/** Player movement constants */
const speed = 300;
const bulletSpeed = 30;
const bulletCoolDown = 300;
const jumpSpeed = 5;
const jumpCoolDown = 400;

export const Player = () => {
  /** Player collider */
  const [sphereRef, api] = useSphere(() => ({
    mass: 100,
    fixedRotation: true,
    position: [0, 1, 0],
    args: [0.2],
    material: {
      friction: 0,
    },
  }));
  /** Bullets */
  const [bullets, setBullets] = useState([]);

  /** Input hooks */
  const pressed = useKeyboardInput(["w", "a", "s", "d", " "]);
  const pressedMouse = useMouseInput();

  /** Converts the input state to ref so they can be used inside useFrame */
  const input = useVariable(pressed);
  const mouseInput = useVariable(pressedMouse);

  /** Player movement constants */
  const { camera, scene } = useThree();

  /** Player state */
  const state = useRef({
    timeToShoot: 0,
    timeTojump: 0,
    vel: [0, 0, 0],
    jumping: false,
  });

  useEffect(() => {
    api.velocity.subscribe((v) => (state.current.vel = v));
  }, [api]);

  /** Player loop */
  useFrame((_, delta) => {
    /** Handles movement */
    console.log("Input.current is: " + JSON.stringify(input.current));
    const { w, s, a, d } = input.current;

    const space = input.current[" "];

    let velocity = new Vector3(0, 0, 0);
    let cameraDirection = new Vector3();
    camera.getWorldDirection(cameraDirection);

    let forward = new Vector3();
    forward.setFromMatrixColumn(camera.matrix, 0);
    forward.crossVectors(camera.up, forward);

    let right = new Vector3();
    right.setFromMatrixColumn(camera.matrix, 0);

    let [horizontal, vertical] = [0, 0];

    if (w == true) {
      vertical += 1;
      console.log("Pressed w");
    } else if (s == true) {
      vertical -= 1;
      console.log("Pressed s");
    } else if (d == true) {
      horizontal += 1;
      console.log("Pressed d");
    } else if (a == true) {
      horizontal -= 1;
      console.log("Pressed a");
    }
    console.log(
      "Horizontal is: " + horizontal + " and vertical is: " + vertical
    );
    if (horizontal !== 0 && vertical !== 0) {
      velocity
        .add(forward.clone().multiplyScalar(speed * vertical))
        .add(right.clone().multiplyScalar(speed * horizontal));
      velocity.clampLength(-speed, speed);
    } else if (horizontal !== 0) {
      velocity.add(right.clone().multiplyScalar(speed * horizontal));
    } else if (vertical !== 0) {
      velocity.add(forward.clone().multiplyScalar(speed * vertical));
    }

    console.log("velocity is: " + JSON.stringify(velocity));
    /** Updates player velocity */
    api.velocity.set(
      velocity.x * delta,
      state.current.vel[1],
      velocity.z * delta
    );
    /** Updates camera position */
    camera.position.set(
      sphereRef.current.position.x,
      sphereRef.current.position.y + 1,
      sphereRef.current.position.z
    );

    /** Handles jumping */
    if (state.current.jumping && state.current.vel[1] < 0) {
      /** Ground check */
      const raycaster = new Raycaster(
        sphereRef.current.position,
        new Vector3(0, -1, 0),
        0,
        0.2
      );
      const intersects = raycaster.intersectObjects(scene.children);
      if (intersects.length !== 0) {
        state.current.jumping = false;
      }
    }

    if (space && !state.current.jumping) {
      const now = Date.now();
      if (now > state.current.timeTojump) {
        state.current.timeTojump = now + jumpCoolDown;
        state.current.jumping = true;
        api.velocity.set(state.current.vel[0], jumpSpeed, state.current.vel[2]);
      }
    }

    /** Handles shooting */
    const bulletDirection = cameraDirection.clone().multiplyScalar(bulletSpeed);
    const bulletPosition = camera.position
      .clone()
      .add(cameraDirection.clone().multiplyScalar(2));

    if (mouseInput.current.left) {
      const now = Date.now();
      if (now >= state.current.timeToShoot) {
        state.current.timeToShoot = now + bulletCoolDown;
        setBullets((bullets) => [
          ...bullets,
          {
            id: now,
            position: [bulletPosition.x, bulletPosition.y, bulletPosition.z],
            forward: [bulletDirection.x, bulletDirection.y, bulletDirection.z],
          },
        ]);
      }
    }
  });

  return (
    <>
      {/** Renders bullets */}
      {bullets.map((bullet) => {
        return (
          <Bullet
            key={bullet.id}
            velocity={bullet.forward}
            position={bullet.position}
          />
        );
      })}
    </>
  );
};

It registers the keys that are being pressed but nothing happens.

1 Answers

At first glance, the following snippet seems to be the source of issue.

  const getKeys = useCallback(() => {
    const lowerCaseArray = [];
    const hookReturn = {};

You may have to move those variables outside of the callback.

both handleKeyDown and handleKeyUp calls getKeys() which creates new empty values

Related