Zoko
Zoko

Reputation: 99

How to make character to move around 3D world in React Three Fiber?

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
}

Upvotes: 2

Views: 3953

Answers (1)

rootKitty
rootKitty

Reputation: 101

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

Upvotes: 1

Related Questions