0.6.7 • Published 1 year ago

@bonuz/react-three-third-person v0.6.7

Weekly downloads
-
License
WTFPL
Repository
github
Last release
1 year ago

react-three-third-person

Third person controls for driving a character model inside a @react-three/fiber app driven by @react-three/cannon physics.

Dependencies

  • react v16.14.0 or higher
  • @react-three/fiber v7.0.26 or higher
  • @react-three/drei v8.7.3 or higher
  • @react-three/cannon v6.3.0 or higher

Installation

npm install react-three-third-person

Usage

import ThirdPersonCharacterControls from "react-three-third-person";
import { useGLTF } from "@react-three/drei";

const PATH = "https://yourhost.com/animations";

const animationPaths = {
  idle: `${PATH}/idle.glb`,
  walk: `${PATH}/walk.glb`,
  run: `${PATH}/run.glb`,
  jump: `${PATH}/jump.glb`,
  landing: `${PATH}/landing.glb`,
  inAir: `${PATH}/falling_idle.glb`,
  backpedal: `${PATH}/backpedal.glb`,
  turnLeft: `${PATH}/turn_left.glb`,
  turnRight: `${PATH}/turn_right.glb`,
  strafeLeft: `${PATH}/strafe_left.glb`,
  strafeRight: `${PATH}/strafe_right.glb`,
};

function ThirdPersonCharacter() {
  const characterObj = useGLTF(`${PATH}/your_model.glb`);
  const characterProps = {
    scale: 1.75,
    velocity: 8,
    radius: 0.5,
  };

  return (
    <ThirdPersonCharacterControls
      cameraOptions={{
        yOffset: 1.6,
        minDistance: 0.6,
        maxDistance: 7,
        collisionFilterMask: 2,
      }}
      characterObj={characterObj}
      characterProps={characterProps}
      animationPaths={animationPaths}
    />
  );
}

Configuration

PropTypeDefaultDescription
cameraOptionsobject{}configuration object for control's camera options
characterPropsobject{}configuration object for character
characterObjTHREE.Object3Dundefinedthree.js object for character model
animationPathsobject{}object for animation clip configuration
onLoadfunction() => {}called when animation clips are done loading

cameraOptions

PropTypeDefaultDescription
yOffsetfloat1.6amount of y added to the camera following the character model
minDistancefloat0.6maximum zoom in capability of camera
maxDistancefloat7maximum zoom out capability of camera
collisionFilterMaskinteger2the cannon.js group given to "world" objects for collision detection
cameraCollisionOnbooleanoffif turned on, will use colllisionFilterMask to add collision to the camera (experimental and unoptimized)

characterProps

PropTypeDefaultDescription
scalefloat1amount to scale the character model
radiusfloat0.3value used for creating character capsule collider
velocityfloat4speed at which character moves

characterObj

you can use any model here compatible with the mixamo rig. For best results, use a model sized to the default mixamo character.

animationPaths

this prop expects an object with key/value pairs pointing to a complete set of animations to use on your character model. Refer to the object below for the full list of animations needed, all of which are available as free animations on mixamo.

{
  idle: 'idle.glb',
  walk: 'walk.glb',
  run: 'run.glb',
  jump: 'jump.glb',
  landing: 'landing.glb',
  inAir: 'falling_idle.glb',
  backpedal: 'backpedal.glb',
  turnLeft: 'turn_left.glb',
  turnRight: 'turn_right.glb',
  strafeLeft: 'strafe_left.glb',
  strafeRight: 'strafe_right.glb',
}

Example App Usage

import React from "react";
import ReactDOM from "react-dom/client";
import { Canvas, useThree } from "@react-three/fiber";
import { Physics, useBox } from "@react-three/cannon";
import manny from "manny";
import ThirdPersonCharacterControls from "react-three-third-person";

const BASE_ANIMATIONS_PATH =
  "https://mannys-game.s3.amazonaws.com/third-person/animations";

const animationPaths = {
  idle: `${BASE_ANIMATIONS_PATH}/idle.glb`,
  walk: `${BASE_ANIMATIONS_PATH}/walk.glb`,
  run: `${BASE_ANIMATIONS_PATH}/run.glb`,
  jump: `${BASE_ANIMATIONS_PATH}/jump.glb`,
  landing: `${BASE_ANIMATIONS_PATH}/landing.glb`,
  inAir: `${BASE_ANIMATIONS_PATH}/falling_idle.glb`,
  backpedal: `${BASE_ANIMATIONS_PATH}/backpedal.glb`,
  turnLeft: `${BASE_ANIMATIONS_PATH}/turn_left.glb`,
  turnRight: `${BASE_ANIMATIONS_PATH}/turn_right.glb`,
  strafeLeft: `${BASE_ANIMATIONS_PATH}/strafe_left.glb`,
  strafeRight: `${BASE_ANIMATIONS_PATH}/strafe_right.glb`,
};

function ThirdPersonCharacter() {
  const mannyObj = manny();

  return (
    <ThirdPersonCharacterControls
      cameraOptions={{
        yOffset: 1.6,
        minDistance: 0.6,
        maxDistance: 7,
        collisionFilterMask: 2,
      }}
      characterObj={mannyObj}
      animationPaths={animationPaths}
    />
  );
}

function Lighting() {
  return (
    <>
      <hemisphereLight
        skyColor={0xffffff}
        groundColor={0x444444}
        position={[0, 0, 0]}
      />
      <directionalLight
        color={0xffffff}
        intensity={0.25}
        castShadow
        position={[0, 200, 100]}
      />
    </>
  );
}

function Floor() {
  const [ref] = useBox(() => ({
    type: "Static",
    args: [25, 0.2, 25],
    mass: 0,
    material: {
      friction: 0,
      name: "floor",
    },
    collisionFilterGroup: 2,
  }));
  return (
    <group>
      <mesh ref={ref}>
        <boxGeometry name="floor-box" />
        <meshPhongMaterial opacity={0} transparent />
      </mesh>
      <gridHelper args={[25, 25]} />
    </group>
  );
}

function Wall({ args, ...props }) {
  const [ref] = useBox(() => ({
    type: "Static",
    args,
    mass: 0,
    material: {
      friction: 0.3,
      name: "wall",
    },
    collisionFilterGroup: 2,
    ...props,
  }));
  return (
    <mesh receiveShadow ref={ref} {...props}>
      <boxGeometry args={args} />
      <meshPhongMaterial color="white" opacity={0.8} transparent />
    </mesh>
  );
}

function App() {
  return (
    <div style={{ height: "100vh", width: "100%" }}>
      <Canvas
        flat
        camera={{
          fov: 75,
          near: 0.1,
          far: 3800,
          position: [0, 11, 11],
        }}
      >
        <Physics gravity={[0, -35, 0]}>
          <ThirdPersonCharacter />
          <Wall args={[25, 3, 0.2]} position={[0, 1.4, -12.6]} />
          <Wall args={[25, 3, 0.2]} position={[0, 1.4, 12.6]} />
          <Wall
            args={[25, 3, 0.2]}
            rotation={[0, -Math.PI / 2, 0]}
            position={[12.6, 1.4, 0]}
          />
          <Wall
            args={[25, 3, 0.2]}
            rotation={[0, -Math.PI / 2, 0]}
            position={[-12.6, 1.4, 0]}
          />
          <Floor />
        </Physics>
        <Lighting />
      </Canvas>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Running Locally

npm run dev

Go to localhost:3000 to see the local test application.

0.6.7

1 year ago

0.5.8

1 year ago

0.6.6

1 year ago

0.5.7

1 year ago

0.5.9

1 year ago

0.6.3

1 year ago

0.6.2

1 year ago

0.6.5

1 year ago

0.5.6

1 year ago

0.6.4

1 year ago

0.5.5

1 year ago

0.6.1

1 year ago

0.6.0

1 year ago

0.5.4

1 year ago

0.5.3

1 year ago

0.5.2

1 year ago

0.5.1

1 year ago

0.5.0

1 year ago