2

I'm making a small game in React and right now I'm trying to traverse the 3 dimensional world that I just generated. I'm using React Three Fiber and some other utility libraries to assist in this. For my character I'm just using a sphere object from @react-three/cannon and for the linear movements I made hook to handle the key-press logic. For some reason whenever I load my application my key presses are registered, but the sphere object never moves. Why is this occurring?

App.jsx

import "./App.css";
import React from "react";
import { Canvas } from "@react-three/fiber";
import { Physics } from "@react-three/cannon";
import Lights from "./Components/Lights";
import { Camera } from "./Components/Camera";
import Ground from "./Components/Ground";
import CubeModel from "./Components/CubeModel";
import { Person } from "./Components/Person";
import { range } from "./helperMethods/range";

const matrix = [];
const matrixFactory = (
    length: number,
    width: number,
    height: number,
    scale = 2.5
) => {
    if (length <= 0 || width <= 0 || height <= 0) {
        throw new Error("Dimensions cannot be 0 or less");
    }

    let i = 0;
    for (let x of range(0, length - 1)) {
        for (let y of range(0, width - 1)) {
            for (let z of range(0, height - 1)) {
                let cartesianCoordinates = [x * scale, y * scale, z * scale];
                matrix[i] = cartesianCoordinates;
                i++;
            }
        }
    }
};

function App() {
    matrixFactory(4, 4, 6);

    return (
        <div className="App">
            <Canvas>
                <Lights />
                <Camera />
                <Physics gravity={[0, -30, 0]}>
                    <Ground position={[0, 0, 0]} />
                    <Person position={[-20, 3, 10]} />
                    <React.Suspense fallback={null}>
                        {matrix.map((value, idx) => (
                            <React.Fragment key={idx}>
                                <CubeModel
                                    position={[value[0], value[1], value[2]]}
                                />
                            </React.Fragment>
                        ))}
                    </React.Suspense>
                </Physics>
            </Canvas>
        </div>
    );
}

export default App;

Person.jsx

import React from "react";
import { useSphere } from "@react-three/cannon";
import { useThree, useFrame } from "@react-three/fiber";
import { useKeyboardControls } from "../hooks/useKeyboardControls";
import { Vector3 } from "three";

const SPEED = 6;

export function Person(props) {
    const { camera } = useThree();
    const { moveForward, moveBackward, moveLeft, moveRight } =
        useKeyboardControls();
    const [ref, api] = useSphere(() => ({
        mass: 1,
        type: "Dynamic",
        ...props,
    }));
    const velocity = React.useRef([0, 0, 0]);
    React.useEffect(() => {
        api.velocity.subscribe((v) => (velocity.current = v));
    }, [api.velocity]);

    useFrame(() => {
        camera.position.copy(ref.current.position);
        const direction = new Vector3();

        const frontVector = new Vector3(
            0,
            0,
            Number(moveBackward) - Number(moveForward)
        );
        const sideVector = new Vector3(
            Number(moveLeft) - Number(moveRight),
            0,
            0
        );
        direction
            .subVectors(frontVector, sideVector)
            .normalize()
            .multiplyScalar(SPEED)
            .applyEuler(camera.rotation);

        api.velocity.set(direction.x, velocity.current[1], direction.z);
    });

    return (
        <>
            <mesh ref={ref} />
        </>
    );
}

useKeyboardControls.js

import React from "react"

function actionByKey(key) {
    const keys = {
        KeyW: 'moveForward',
        KeyS: 'moveBackward',
        KeyA: 'moveLeft',
        KeyD: 'moveRight'
    }
    return keys[key]
}

export const useKeyboardControls = () => {
    const [movement, setMovement] = React.useState({
        moveForward: false,
        moveBackward: false,
        moveLeft: false,
        moveRight: false
    })

    React.useEffect(() => {
        const handleKeyDown = (e) => {
            // Movement key
            if (actionByKey(e.code)) {
                setMovement((state) => ({...state, [actionByKey(e.code)]: true}))
            }
        }
        const handleKeyUp = (e) => {
            // Movement key
            if (actionByKey(e.code)) {
                setMovement((state) => ({...state, [actionByKey(e.code)]: false}))
            }
        }

        document.addEventListener('keydown', handleKeyDown);
        document.addEventListener('keyup', handleKeyUp);
        return () => {
            document.removeEventListener('keydown', handleKeyDown);
            document.removeEventListener('keyup', handleKeyUp);
        }
    }, [])

    return movement
}

3
  • You should take a look at Three.js controls Commented Dec 21, 2021 at 13:31
  • 2
    If you are still looking for the answer then please add this line ref.current.getWorldPosition(ref.current.position) after api.velocity.set(direction.x, velocity.current[1], direction.z); . let me if this works for you. Commented Jan 10, 2022 at 10:57
  • That worked, thank you! Commented Jan 18, 2022 at 2:25

1 Answer 1

1

I know the answer is late but I think you should add "allowSleep: false" property to your useSphere under type:"Dynamic" for example

In fact it allows the sphere to never be set to "sleep" mode which basically result in the object not moving at all.

Good to know for future ppl coming here

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.