0

I am trying to build a "3D tank game", and I used most of the code from this Original Project. I cannot find any resources with good examples that demonstrates "useRaycastVehicle" with "3rd person camera". My solution is simple, it checks if z dimension is negative, if so, on next frame it flips the POV, so the vehicle does not face forward towards the camera. I think, there are better solutions to solve this. Because I've seen on Youtube solving this but reimplementing his code caused camera being stuck to ground, not following etc.

import { useFrame, useThree } from "@react-three/fiber";
import { useRaycastVehicle } from "@react-three/cannon";
import { useEffect, useRef } from "react";

import { useKeyboardControls } from "@react-three/drei";
import Beetle from "./Beetle";
import Wheel from "./Wheel";
import { Vector3 } from "three";

export default function Tank({
    radius = 0.7,
    width = 1.2,
    height = -0.04,
    front = 1.3,
    back = -1.15,
    steer = 0.75,
    force = 2000,
    maxBrake = 1e5,
    ...props
}) {
    const {
        forward,
        backward,
        leftward,
        rightward,
        // shoot: jump,
    } = useKeyboardControls((state) => state);

    const chassis = useRef();
    const wheel1 = useRef();
    const wheel2 = useRef();
    const wheel3 = useRef();
    const wheel4 = useRef();

    const wheelInfo = {
        radius,
        directionLocal: [0, -1, 0],
        suspensionStiffness: 30,
        suspensionRestLength: 0.3,
        maxSuspensionForce: 1e4,
        maxSuspensionTravel: 0.3,
        dampingRelaxation: 10,
        dampingCompression: 4.4,
        axleLocal: [-1, 0, 0],
        chassisConnectionPointLocal: [1, 0, 1],
        useCustomSlidingRotationalSpeed: true,
        customSlidingRotationalSpeed: -30,
        frictionSlip: 2,
    };

    const wheelInfo1 = {
        ...wheelInfo,
        isFrontWheel: true,
        chassisConnectionPointLocal: [-width / 2, height, front],
    };
    const wheelInfo2 = {
        ...wheelInfo,
        isFrontWheel: true,
        chassisConnectionPointLocal: [width / 2, height, front],
    };
    const wheelInfo3 = {
        ...wheelInfo,
        isFrontWheel: false,
        chassisConnectionPointLocal: [-width / 2, height, back],
    };
    const wheelInfo4 = {
        ...wheelInfo,
        isFrontWheel: false,
        chassisConnectionPointLocal: [width / 2, height, back],
    };

    const [myTank, api] = useRaycastVehicle(() => ({
        // @ts-ignore
        chassisBody: chassis,
        // @ts-ignore
        wheels: [wheel1, wheel2, wheel3, wheel4],
        //@ts-ignore
        wheelInfos: [wheelInfo1, wheelInfo2, wheelInfo3, wheelInfo4],
        indexForwardAxis: 2,
        indexRightAxis: 0,
        indexUpAxis: 1,
    }));

    const { camera, scene } = useThree();

    const pos = useRef([0, 0, 0]);
    const rot = useRef([0, 0, 0]);
    useEffect(() => {
        if (chassis.current) {
            //@ts-ignore
            chassis.current.api.rotation.subscribe((r) => (rot.current = r));
            //@ts-ignore
            chassis.current.api.position.subscribe((p) => (pos.current = p));
        }
    }, [chassis.current]);

    useFrame(() => {
        for (let e = 2; e < 4; e++)
            api.applyEngineForce(
                forward || backward
                    ? force * (forward && !backward ? -1 : 1)
                    : 0,
                2
            );
        for (let s = 0; s < 2; s++)
            api.setSteeringValue(
                leftward || rightward
                    ? steer * (leftward && !rightward ? 1 : -1)
                    : 0,
                s
            );

        const cameraOffset = new Vector3(0, 4, -5);

        var zDimensionNegative = pos.current[2] > -5 ? 0 : 10;

        camera.position
            .copy(
                new Vector3(
                    pos.current[0],
                    pos.current[1],
                    pos.current[2] + zDimensionNegative
                )
            )
            .add(cameraOffset);
    });

    return (
        // @ts-ignore
        <group ref={myTank} position={[0, -0.4, 0]}>
            <Beetle
                ref={chassis}
                //@ts-ignore
                rotation={props.rotation}
                position={props.position}
                angularVelocity={props.angularVelocity}
            />
            <Wheel
                ref={wheel1}
                //@ts-ignore
                radius={radius}
                leftSide
            />
            <Wheel
                ref={wheel2}
                //@ts-ignore
                radius={radius}
            />
            <Wheel
                ref={wheel3}
                //@ts-ignore
                radius={radius}
                leftSide
            />
            <Wheel
                ref={wheel4}
                //@ts-ignore
                radius={radius}
            />
        </group>
    );
}

Ideally Problem My solution

imback0526
  • 164
  • 2
  • 9

1 Answers1

0

Honestly, it was difficult to work with React and ThreeJS. No documentations on usage, no good examples, felt like react fiber was meant for small 3D animation.

But if someone really needs to solve this. Here how I managed to do it:

const MyCamera = () => (
    <PerspectiveCamera
        fov={75}
        rotation={[0, Math.PI, 0]}
        makeDefault={true}
        position={[0, 6, -11]}
    >
        <PointerLockControls
            addEventListener={undefined}
            hasEventListener={undefined}
            removeEventListener={undefined}
            dispatchEvent={undefined}
        />
    </PerspectiveCamera>
);

const Beetle = ({ children }) => {
    return (
        <mesh>
            {children}
            <group dispose={null} scale={0.225}>
                {/**
                 * Car's original meshes and stuff!
                 */}
            </group>
        </mesh>
    );
};

export default function Tank() {
    return (
        // @ts-ignore
        <group ref={myTank} position={[0, -0.4, 0]}>
            <Beetle
                ref={chassis}
                //@ts-ignore
                rotation={props.rotation}
                position={props.position}
                angularVelocity={props.angularVelocity}
            >
                <MyCamera />
            </Beetle>
            <Wheel
                ref={wheel1}
                //@ts-ignore
                radius={radius}
                leftSide
            />
            <Wheel
                ref={wheel2}
                //@ts-ignore
                radius={radius}
            />
            <Wheel
                ref={wheel3}
                //@ts-ignore
                radius={radius}
                leftSide
            />
            <Wheel
                ref={wheel4}
                //@ts-ignore
                radius={radius}
            />
        </group>
    );
}

Basically, what it's doing is just making the camera the child of the Beetle mesh, therefore, the camera inherits positions and vectors of its parent. I don't know how to get the object's world direction or vectors or position. So, making the camera child of an object is the simplest way to inherit its position and its direction!

imback0526
  • 164
  • 2
  • 9