Physics

Starter pack

物理を使用すると、3Dプロジェクトの可能性が広がります。リアルな宇宙やユーザーインタラクション、さらにはゲームを作成できます。

このレッスンでは、シンプルなゲームを作りながら基本的な概念を学びます。

心配しないでください、予備知識は必要ありません。最初から始めます。(ちなみに私は学校で物理の成績は非常に悪かったので、もし私にできるなら、あなたにもできます!)

Physics engines

3Dプロジェクトに物理を追加するためには、物理エンジンを使用します。物理エンジンは、重力、衝突、力などの複雑な数学的処理をすべて担当するライブラリです。

JavaScriptのエコシステムには、多くの物理エンジンが存在します。

非常に人気のあるものとして Cannon.jsRapier.js があります。

Poimandres は、React Three Fiberでこれらのエンジンを使用するための素晴らしいライブラリを2つ提供しています。react-three-rapieruse-cannon です。

このレッスンでは react-three-rapier を使用しますが、これらは非常に似ており、ここで学ぶ概念は両方に適用できます。

インストールするには、次のコマンドを実行します:

yarn add @react-three/rapier

これで始める準備が整いました!

Physics World

ゲームを作成する前に、基本的な概念をカバーしましょう。

まず、physics world を作成する必要があります。この世界には、シーン内のすべての物理オブジェクトが含まれます。react-three-rapier を使用すると、すべてのオブジェクトを <Physics> コンポーネントでラップするだけで済みます:

// ...
import { Physics } from "@react-three/rapier";

function App() {
  return (
    <>
      <Canvas camera={{ position: [0, 6, 6], fov: 60 }} shadows>
        <color attach="background" args={["#171720"]} />
        <Physics>
          <Experience />
        </Physics>
      </Canvas>
    </>
  );
}

export default App;

これで私たちの世界は準備ができましたが、何も起こりません!それは私たちがまだ物理オブジェクトを持っていないからです。

Rigidbody

オブジェクトに物理を追加するには、rigidbodyを追加する必要があります。Rigidbodyは、オブジェクトを物理世界で動かすためのコンポーネントです。

オブジェクトの動きを引き起こすものは何でしょうか?それは、重力衝突、またはユーザーの操作などのです。

Player.jsxにあるキューブが物理オブジェクトであることを物理世界に伝えるために、キューブにrigidbodyを追加してみましょう:

import { RigidBody } from "@react-three/rapier";

export const Player = () => {
  return <RigidBody>{/* ... */}</RigidBody>;
};

これでキューブが重力に反応して落ちるようになりました。しかし、永遠に落ち続けます!

キューブが衝突して落ちるのを止めるために、地面も物理オブジェクトにする必要があります。

Experience.jsxの地面にrigidbodyを追加してみましょう。ただし、キューブのように動かしたくないため、type="fixed"プロパティを追加します:

// ...
import { RigidBody } from "@react-three/rapier";

export const Experience = () => {
  return (
    <>
      {/* ... */}
      <RigidBody type="fixed">
        <mesh position-y={-0.251} receiveShadow>
          <boxGeometry args={[20, 0.5, 20]} />
          <meshStandardMaterial color="mediumpurple" />
        </mesh>
      </RigidBody>
      {/* ... */}
    </>
  );
};

Cube on top of the ground

スタート地点に戻ると、地面の上に動かないキューブがあります。しかし、内部では重力に反応しており、地面との衝突で止まっているキューブがあります。

Forces

物理世界と物理オブジェクトを持っているので、次は力を使って遊んでみましょう。

キーボードの矢印キーでキューブを動かします。これを行うには、イベントレッスンで紹介したKeyboardControlsを使用します:

// ...
import { KeyboardControls } from "@react-three/drei";
import { useMemo } from "react";

export const Controls = {
  forward: "forward",
  back: "back",
  left: "left",
  right: "right",
  jump: "jump",
};

function App() {
  const map = useMemo(
    () => [
      { name: Controls.forward, keys: ["ArrowUp", "KeyW"] },
      { name: Controls.back, keys: ["ArrowDown", "KeyS"] },
      { name: Controls.left, keys: ["ArrowLeft", "KeyA"] },
      { name: Controls.right, keys: ["ArrowRight", "KeyD"] },
      { name: Controls.jump, keys: ["Space"] },
    ],
    []
  );
  return <KeyboardControls map={map}>{/* ... */}</KeyboardControls>;
}

export default App;

これで、Player.jsxコンポーネントで押されたキーを取得できます:

// ...
import { Controls } from "../App";
import { useKeyboardControls } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";

export const Player = () => {
  const [, get] = useKeyboardControls();

  useFrame(() => {
    if (get()[Controls.forward]) {
    }
    if (get()[Controls.back]) {
    }
    if (get()[Controls.left]) {
    }
    if (get()[Controls.right]) {
    }
    if (get()[Controls.jump]) {
    }
  });
  // ...
};

get()はKeyboardControlsコンポーネントで押されたキーを取得する代替方法です。

これで押されたキーがわかりましたので、キューブに力を加えることができます。以下の2つのメソッドを使用して、これを実行できます:

  • applyImpulse: オブジェクトに瞬間的な力を加える
  • setLinVel: オブジェクトの線形速度を設定する

この2つのメソッドを見てみましょう。

RigidBodyuseRefを追加し、正しい方向にキューブを動かすためにインパルスを適用します:

import { useRef } from "react";
import { Vector3 } from "three";
const MOVEMENT_SPEED = 0.5;

export const Player = () => {
  const rb = useRef();
  const [, get] = useKeyboardControls();
  const impulse = new Vector3();
  useFrame(() => {
    impulse.x = 0;
    impulse.y = 0;
    impulse.z = 0;
    if (get()[Controls.forward]) {
      impulse.z -= MOVEMENT_SPEED;
    }
    if (get()[Controls.back]) {
      impulse.z += MOVEMENT_SPEED;
    }
    if (get()[Controls.left]) {
      impulse.x -= MOVEMENT_SPEED;
    }
    if (get()[Controls.right]) {
      impulse.x += MOVEMENT_SPEED;
    }
    if (get()[Controls.jump]) {
    }
    rb.current.applyImpulse(impulse, true);
  });
  return <RigidBody ref={rb}>{/* ... */}</RigidBody>;
};

refをmeshではなくRigidBodyに割り当てることに注意してください。

動作しますが、加速が速すぎ、地面で滑っています。地面の摩擦を増やすことでこれを解決できます:

// ...

export const Experience = () => {
  // ...
  return (
    <>
      {/* ... */}
      <RigidBody type="fixed" friction={5}>
        {/* ... */}
      </RigidBody>
      {/* ... */}
    </>
  );
};

摩擦により、キューブが地面を掴むため回転します。これを解決するには、キューブの回転をロックします:

// ...
export const Player = () => {
  // ...
  return (
    <RigidBody ref={rb} lockRotations>
      {/* ... */}
    </RigidBody>
  );
};

これでかなり良くなりましたが、まだ少し滑っています。このレッスンでは追求しませんが、キューブにlinear dampingを調整することでこれを解決できます。

また、キューブが継続的に加速しないように最大速度も調整する必要があります。キューブを回転させるために左と右のキーを使用する際に問題が発生します。

次に、システムを applyImpulse の代わりに setLinVel を使用するように切り替えましょう:

// ...
import { useRef } from "react";
import { Vector3 } from "three";

const MOVEMENT_SPEED = 5;

export const Player = () => {
  // ...
  const rb = useRef();
  const vel = new Vector3();
  useFrame(() => {
    vel.x = 0;
    vel.y = 0;
    vel.z = 0;
    if (get()[Controls.forward]) {
      vel.z -= MOVEMENT_SPEED;
    }
    if (get()[Controls.back]) {
      vel.z += MOVEMENT_SPEED;
    }
    if (get()[Controls.left]) {
      vel.x -= MOVEMENT_SPEED;
    }
    if (get()[Controls.right]) {
      vel.x += MOVEMENT_SPEED;
    }
    if (get()[Controls.jump]) {
    }
    rb.current.setLinvel(vel, true);
  });
  return <RigidBody ref={rb}>{/* ... */}</RigidBody>;
};

もはや必要ないので地面の摩擦は削除して構いません。

変数名を素早くリネームするために F2 を使用しましょう。

素晴らしい!キューブをキーボードの矢印キーで動かすことができるようになりました。

次に、ユーザーがスペースバーを押したときにジャンプ力を追加しましょう:

// ...
const JUMP_FORCE = 8;

export const Player = () => {
  // ...
  useFrame(() => {
    // ...
    if (get()[Controls.jump]) {
      vel.y += JUMP_FORCE;
    }
    rb.current.setLinvel(vel, true);
  });
  return (
    <RigidBody ref={rb} lockRotations>
      {/* ... */}
    </RigidBody>
  );
};

2つの問題点があります:

  • キューブが重力に正しく反応しない(レッスンの冒頭の落下するキューブと比較してください)
  • キューブが空中にいるときでもジャンプできる

重力問題は、キューブのy軸の速度を手動で設定しているためです。ジャンプするときのみ変更し、それ以外の時間は物理エンジンに任せる必要があります:

// ...

export const Player = () => {
  // ...
  useFrame(() => {
    const curVel = rb.current.linvel();
    if (get()[Controls.jump]) {
      vel.y += JUMP_FORCE;
    } else {
      vel.y = curVel.y;
    }
    rb.current.setLinvel(vel, true);
  });
  // ...
};

rb.current.linvel()でキューブの現在の速度を取得し、ジャンプしていない場合はy軸の速度を現在のものに設定します。

キューブが空中にいるときにジャンプできないようにするためには、再びジャンプできる前にキューブが地面に触れたかどうかを確認します:

// ...
export const Player = () => {
  // ...
  const inTheAir = useRef(false);
  useFrame(() => {
    // ...
    if (get()[Controls.jump] && !inTheAir.current) {
      vel.y += JUMP_FORCE;
      inTheAir.current = true;
    } else {
      vel.y = curVel.y;
    }
    rb.current.setLinvel(vel, true);
  });
  // ...
};

これで一度だけジャンプでき、重力もうまく機能していますが、少し遅いです。

これらを修正する前に、衝突システムの動作を見てみましょう。

End of lesson preview

To get access to the entire lesson, you need to purchase the course.