RigidBody和RapierRigidBody

RigidBody和RapierRigidBody

_

1. RigidBody vs RapierRigidBody

基本概念

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

// RigidBody - React 组件(JSX 元素)
<RigidBody type="dynamic" position={[0, 5, 0]}>
  <mesh />
</RigidBody>
// RapierRigidBody - TypeScript 类型(引用对象)
const ref = useRef<RapierRigidBody>(null);

名称

类型

作用

RigidBody

React 组件

声明式创建物理刚体,包裹 Three.js 对象

RapierRigidBody

TypeScript 类型

刚体实例的类型定义,用于 ref 操作

2. RigidBody 组件详解

作用

  1. 桥接 Three.js 和 Rapier 物理引擎

  2. 自动同步视觉对象和物理对象的位置

  3. 声明式配置物理属性

实现原理(简化版源码)

// @react-three/rapier 简化实现
import { useEffect, useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import * as RAPIER from '@dimforge/rapier3d-compat';

function RigidBody({ 
  children, 
  type = 'dynamic', 
  position = [0, 0, 0],
  colliders = 'cuboid',
  ...props 
}) {
  const groupRef = useRef();
  const rigidBodyRef = useRef();
  const { world } = useRapier(); // 获取物理世界实例
  
  // 1. 创建物理刚体
  useEffect(() => {
    // 创建刚体描述符
    const rigidBodyDesc = type === 'dynamic'
      ? RAPIER.RigidBodyDesc.dynamic()
      : RAPIER.RigidBodyDesc.fixed();
    
    // 设置初始位置
    rigidBodyDesc.setTranslation(...position);
    
    // 在物理世界中创建刚体
    const rigidBody = world.createRigidBody(rigidBodyDesc);
    rigidBodyRef.current = rigidBody;
    
    // 2. 创建碰撞器(如果指定)
    if (colliders === 'cuboid') {
      const colliderDesc = RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5);
      world.createCollider(colliderDesc, rigidBody);
    }
    
    return () => {
      // 清理
      world.removeRigidBody(rigidBody);
    };
  }, []);
  
  // 3. 每帧同步:物理世界 → Three.js 视觉对象
  useFrame(() => {
    if (!rigidBodyRef.current || !groupRef.current) return;
    
    // 从物理引擎获取位置
    const translation = rigidBodyRef.current.translation();
    const rotation = rigidBodyRef.current.rotation();
    
    // 更新 Three.js 对象的位置
    groupRef.current.position.set(
      translation.x,
      translation.y,
      translation.z
    );
    
    // 更新旋转
    groupRef.current.quaternion.set(
      rotation.x,
      rotation.y,
      rotation.z,
      rotation.w
    );
  });
  
  // 4. 返回 Three.js group 包裹子元素
  return (
    <group ref={groupRef}>
      {children}
    </group>
  );
}

3. 工作流程图

┌─────────────────────────────────────────────┐
│  React 组件层                                │
│  <RigidBody type="dynamic" position={[0,5,0]}>│
│    <mesh>...</mesh>                         │
│  </RigidBody>                               │
└─────────────┬───────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────┐
│  物理世界初始化                               │
│  ┌─────────────────────────────────────┐    │
│  │ RAPIER.World.createRigidBody()      │    │
│  │  - 创建物理刚体                      │    │
│  │  - 设置质量、摩擦力等                 │    │
│  └─────────────────────────────────────┘    │
│  ┌─────────────────────────────────────┐    │
│  │ RAPIER.World.createCollider()       │    │
│  │  - 创建碰撞形状                      │    │
│  │  - 绑定到刚体                        │    │
│  └─────────────────────────────────────┘    │
└─────────────┬───────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────┐
│  每帧同步循环 (useFrame)                      │
│                                             │
│  物理计算:                                   │
│  ┌─────────────────────────────────────┐    │
│  │ world.step(deltaTime)               │    │
│  │  - 重力影响                          │    │
│  │  - 碰撞检测                          │    │
│  │  - 力的计算                          │    │
│  │  → 更新刚体位置                      │    │
│  └─────────────────────────────────────┘    │
│              ▼                              │
│  同步到 Three.js:                           │
│  ┌─────────────────────────────────────┐    │
│  │ const pos = rigidBody.translation() │    │
│  │ mesh.position.set(pos.x, pos.y, pos.z)│  │
│  └─────────────────────────────────────┘    │
└─────────────────────────────────────────────┘

4. RapierRigidBody 类型详解

定义

// @react-three/rapier 类型定义
interface RapierRigidBody {
  // 位置控制
  translation(): { x: number; y: number; z: number };
  setTranslation(pos: { x: number; y: number; z: number }, wakeUp: boolean): void;
  
  // 速度控制
  linvel(): { x: number; y: number; z: number };
  setLinvel(vel: { x: number; y: number; z: number }, wakeUp: boolean): void;
  
  // 旋转控制
  rotation(): { x: number; y: number; z: number; w: number };
  setRotation(quat: Quaternion, wakeUp: boolean): void;
  
  // 力的施加
  applyImpulse(impulse: { x: number; y: number; z: number }, wakeUp: boolean): void;
  applyForce(force: { x: number; y: number; z: number }, wakeUp: boolean): void;
  applyTorqueImpulse(torque: { x: number; y: number; z: number }, wakeUp: boolean): void;
  
  // 其他属性
  mass(): number;
  isSleeping(): boolean;
  wakeUp(): void;
  // ... 更多方法
}

使用示例

import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { RigidBody, RapierRigidBody } from "@react-three/rapier";
import { useKeyboardControls } from "@react-three/drei";

function Player() {
  // ✅ 使用 RapierRigidBody 类型定义 ref
  const rigidBodyRef = useRef<RapierRigidBody>(null);
  const [, getKeys] = useKeyboardControls();
  
  useFrame((state, delta) => {
    if (!rigidBodyRef.current) return;
    
    const { forward, backward, left, right, jump } = getKeys();
    
    // 1. 读取当前状态
    const position = rigidBodyRef.current.translation();
    const velocity = rigidBodyRef.current.linvel();
    
    console.log('位置:', position);
    console.log('速度:', velocity);
    
    // 2. 控制移动
    const speed = 5;
    const moveVector = { x: 0, y: velocity.y, z: 0 };
    
    if (forward) moveVector.z = -speed;
    if (backward) moveVector.z = speed;
    if (left) moveVector.x = -speed;
    if (right) moveVector.x = speed;
    
    // 设置线性速度
    rigidBodyRef.current.setLinvel(moveVector, true);
    
    // 3. 跳跃(施加冲量)
    if (jump && Math.abs(velocity.y) < 0.1) {
      rigidBodyRef.current.applyImpulse(
        { x: 0, y: 5, z: 0 },
        true
      );
    }
    
    // 4. 限制最大速度
    if (velocity.y < -20) {
      rigidBodyRef.current.setLinvel(
        { x: velocity.x, y: -20, z: velocity.z },
        true
      );
    }
    
    // 5. 重置位置(掉出世界时)
    if (position.y < -10) {
      rigidBodyRef.current.setTranslation(
        { x: 0, y: 5, z: 0 },
        true
      );
      rigidBodyRef.current.setLinvel(
        { x: 0, y: 0, z: 0 },
        true
      );
    }
  });
  
  return (
    // ✅ 将 ref 绑定到 RigidBody 组件
    <RigidBody
      ref={rigidBodyRef}
      type="dynamic"
      colliders={false}
      position={[0, 4, 0]}
    >
      <mesh>
        <capsuleGeometry args={[0.5, 1, 8, 16]} />
        <meshStandardMaterial color="orange" />
      </mesh>
      <CapsuleCollider args={[0.6, 0.3]} />
    </RigidBody>
  );
}

5. 双向同步机制

物理 → 视觉(自动)

// RigidBody 组件内部每帧执行
useFrame(() => {
  // 从物理引擎读取
  const pos = rapierRigidBody.translation();
  const rot = rapierRigidBody.rotation();
  
  // 同步到 Three.js 对象
  threeJsGroup.position.copy(pos);
  threeJsGroup.quaternion.copy(rot);
});

视觉 → 物理(手动)

// 你的代码控制物理对象
rigidBodyRef.current.setTranslation({ x: 10, y: 0, z: 0 }, true);

// 下一帧自动同步回视觉
// threeJsGroup.position 会变成 (10, 0, 0)

6. 完整的物理引擎架构

┌──────────────────────────────────────────────┐
│  React Three Fiber (R3F)                     │
│  ┌────────────────────────────────────────┐  │
│  │  <Canvas>                              │  │
│  │    <Physics>  ← 创建 RAPIER.World      │  │
│  │      <RigidBody> ← 创建 RAPIER.RigidBody│ │
│  │        <mesh /> ← Three.js 视觉对象     │  │
│  │      </RigidBody>                      │  │
│  │    </Physics>                          │  │
│  │  </Canvas>                             │  │
│  └────────────────────────────────────────┘  │
└──────────────┬───────────────────────────────┘
               │
               ▼
┌──────────────────────────────────────────────┐
│  Rapier 物理引擎                              │
│  ┌────────────────────────────────────────┐  │
│  │  World (物理世界)                       │  │
│  │    ├── RigidBody 1 (刚体)              │  │
│  │    │     └── Collider (碰撞器)         │  │
│  │    ├── RigidBody 2                     │  │
│  │    │     └── Collider                  │  │
│  │    └── step() 物理计算                 │  │
│  └────────────────────────────────────────┘  │
└──────────────┬───────────────────────────────┘
               │
               ▼
┌──────────────────────────────────────────────┐
│  每帧更新 (useFrame)                          │
│  1. world.step(deltaTime)                    │
│  2. 碰撞检测、重力计算                         │
│  3. 更新刚体位置                              │
│  4. 同步到 Three.js mesh.position            │
└──────────────────────────────────────────────┘

7. 核心 API 对比

RigidBody 组件的 Props

<RigidBody
  // 类型
  type="dynamic"  // 'dynamic' | 'kinematic' | 'fixed'
  
  // 位置和旋转
  position={[0, 5, 0]}
  rotation={[0, 0, 0]}
  
  // 物理属性
  mass={1}
  gravityScale={1}
  linearDamping={0.5}
  angularDamping={0.5}
  
  // 约束
  enabledRotations={[true, true, true]}
  enabledTranslations={[true, true, true]}
  lockRotations  // 快捷方式,等于 enabledRotations={[false, false, false]}
  
  // 碰撞器
  colliders="cuboid"  // 'cuboid' | 'ball' | 'trimesh' | false
  
  // 其他
  friction={0.5}
  restitution={0.3}  // 弹性系数
  ccd={true}  // 连续碰撞检测
/>

RapierRigidBody ref 的方法

const ref = useRef<RapierRigidBody>(null);

// 位置
ref.current.translation()  // 读取
ref.current.setTranslation({ x, y, z }, true)  // 设置

// 速度
ref.current.linvel()  // 线性速度
ref.current.setLinvel({ x, y, z }, true)
ref.current.angvel()  // 角速度
ref.current.setAngvel({ x, y, z }, true)

// 力
ref.current.applyImpulse({ x, y, z }, true)  // 冲量
ref.current.applyForce({ x, y, z }, true)    // 持续力
ref.current.applyTorqueImpulse({ x, y, z }, true)  // 扭矩

// 状态
ref.current.isSleeping()  // 是否休眠
ref.current.wakeUp()      // 唤醒
ref.current.mass()        // 质量

8. 实际应用示例

import { useGLTF, useKeyboardControls } from "@react-three/drei";
import { CapsuleCollider, RigidBody, RapierRigidBody } from "@react-three/rapier";
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";

useGLTF.preload("/models/Actor/actor.gltf");

function Player() {
  const { scene } = useGLTF("/models/Actor/actor.gltf");
  const rigidBodyRef = useRef<RapierRigidBody>(null);
  const [, getKeys] = useKeyboardControls();
  
  useFrame((state, delta) => {
    if (!rigidBodyRef.current) return;
    
    const { forward, backward, left, right, jump } = getKeys();
    const velocity = rigidBodyRef.current.linvel();
    
    // 移动逻辑
    const speed = 5;
    const impulse = { x: 0, y: velocity.y, z: 0 };
    
    if (forward) impulse.z = -speed;
    if (backward) impulse.z = speed;
    if (left) impulse.x = -speed;
    if (right) impulse.x = speed;
    
    rigidBodyRef.current.setLinvel(impulse, true);
    
    // 跳跃
    if (jump && Math.abs(velocity.y) < 0.1) {
      rigidBodyRef.current.applyImpulse({ x: 0, y: 5, z: 0 }, true);
    }
  });
  
  return (
    <RigidBody
      ref={rigidBodyRef}
      colliders={false}
      type="dynamic"
      enabledRotations={[false, false, false]}
      position={[0, 4, 0]}
    >
      <primitive object={scene} />
      <CapsuleCollider args={[0.6, 0.3]} />
    </RigidBody>
  );
}

export default Player;

总结

概念

作用

层级

RigidBody

React 组件,声明式创建物理对象

React 层

RapierRigidBody

物理刚体实例类型,命令式控制

Rapier 层

同步机制

自动将物理计算结果同步到 Three.js

useFrame 循环

核心流程:

  1. <RigidBody> 创建 Rapier 刚体

  2. ref={rigidBodyRef} 获取刚体实例

  3. useFrame 中读取/修改物理状态

  4. Rapier 计算物理

  5. 自动同步到 Three.js 视觉对象

这就是 React Three Fiber + Rapier 的完整物理引擎架构!

r3f使用clone绑定的问题原因 2025-12-30
@react-three/fiber的useThree 2025-12-30

评论区