使用 clone() 的原因
1. React 组件复用的最佳实践
在 React 中,如果一个组件可能被多次使用:
// 场景:多个玩家
<Canvas>
<Player position={[0, 0, 0]} />
<Player position={[5, 0, 0]} /> {/* ❌ 第二个玩家会出问题 */}
<Player position={[10, 0, 0]} />
</Canvas>
不使用 clone 的问题:
// Player.tsx - 不用 clone
const { scene } = useGLTF("/models/Actor/actor.gltf");
return <primitive object={scene} /> // ❌ 所有组件共享同一个 scene 对象Three.js 的限制:
一个 Object3D 只能有一个父节点
第二个
<Player>会把 scene 从第一个父节点移除结果:只有最后一个玩家显示
2. Three.js 官方推荐
Three.js 文档明确说明:
如果需要在场景中多次使用同一个几何体/材质,应该使用 clone() 或 InstancedMesh
3. 避免状态污染
// 不用 clone 的风险
const { scene } = useGLTF("/models/Actor/actor.gltf");
// 组件 A 修改了模型
scene.rotation.y = Math.PI;
// 组件 B 也会受影响(因为是同一个对象)为什么在当前情况下不需要 clone
当前场景分析
// 你的代码 - 只有一个 Player
<Canvas>
<Player /> {/* 只使用了一次 */}
</Canvas>
单实例场景:
✅ 只有一个玩家
✅ 不会复用组件
✅ 不需要 clone
clone() 在 R3F + Rapier 中的问题
const clonedScene = scene.clone();
// 问题 1: R3F 的内部引用跟踪
clonedScene.__r3f = undefined; // clone 后丢失 R3F 的内部状态
// 问题 2: Rapier 的刚体绑定
// RigidBody 需要稳定的对象引用,clone 会破坏这个引用
// 问题 3: 性能开销
// clone() 会深拷贝所有几何体、材质、纹理(3.4MB)正确的使用场景
场景 A: 单个实例 → 不需要 clone ✅
function Player() {
const { scene } = useGLTF("/models/Actor/actor.gltf");
return (
<RigidBody>
<primitive object={scene} /> {/* ✅ 直接使用 */}
</RigidBody>
);
}
// 使用
<Canvas>
<Player /> {/* 只用一次 */}
</Canvas>
场景 B: 多个实例 → 需要特殊处理
方案 1: 使用 clone(如果不用物理引擎)
function Enemy({ position }: { position: [number, number, number] }) {
const { scene } = useGLTF("/models/enemy.gltf");
const clonedScene = useMemo(() => scene.clone(), [scene]);
return <primitive object={clonedScene} position={position} />;
}
// 使用
<Canvas>
<Enemy position={[0, 0, 0]} />
<Enemy position={[5, 0, 0]} />
<Enemy position={[10, 0, 0]} />
</Canvas>
方案 2: 使用 Instances(性能最好)
import { Instances, Instance } from "@react-three/drei";
function Enemies() {
const { nodes } = useGLTF("/models/enemy.gltf");
return (
<Instances geometry={nodes.Mesh.geometry} material={nodes.Mesh.material}>
<Instance position={[0, 0, 0]} />
<Instance position={[5, 0, 0]} />
<Instance position={[10, 0, 0]} />
</Instances>
);
}方案 3: 多个独立组件(各自加载)
function Player1() {
const { scene } = useGLTF("/models/player1.gltf");
return <primitive object={scene} />;
}
function Player2() {
const { scene } = useGLTF("/models/player2.gltf");
return <primitive object={scene} />;
}场景 C: 物理引擎 + 多实例
// 每个实例需要独立的物理刚体
function Enemy({ position }: { position: [number, number, number] }) {
const { nodes, materials } = useGLTF("/models/enemy.gltf");
return (
<RigidBody position={position}>
{/* 不 clone scene,而是重建网格 */}
<mesh geometry={nodes.Mesh.geometry} material={materials.Material} />
</RigidBody>
);
}推荐逻辑
┌─────────────────────────────────────┐
│ 需要多个相同模型吗? │
└─────────┬───────────────────────────┘
│
┌─────┴─────┐
│ 是 │ 否 → 直接用 scene ✅
│ │
▼ │
┌───────────────┐
│ 有物理刚体吗? │
└───┬───────────┘
│
┌───┴────┐
│ 是 │ 否 → 用 clone() ✅
│ │
▼ │
使用 Instances 或
重建 mesh ✅为什么会有误导
我之前的推荐基于通用最佳实践:
✅ 防御性编程 - 假设代码可能被复用
✅ Three.js 官方建议 - clone 是标准做法
❌ 忽略了 R3F + Rapier 的特殊性 - 这个组合下 clone 有副作用
正确的建议应该是
import { useGLTF } from "@react-three/drei";
import { CapsuleCollider, RigidBody } from "@react-three/rapier";
useGLTF.preload("/models/Actor/actor.gltf");
function Player() {
const { scene } = useGLTF("/models/Actor/actor.gltf");
return (
<RigidBody
colliders={false}
type="dynamic"
enabledRotations={[false, false, false]}
position={[0, 4, 0]}
lockRotations
>
{/* ✅ 单实例 + 物理引擎 → 直接用 scene */}
<primitive object={scene} />
<CapsuleCollider args={[0.6, 0.3]} />
</RigidBody>
);
}
export default Player;如果未来需要多个玩家,再改为:
import { useGLTF } from "@react-three/drei";
import { CapsuleCollider, RigidBody } from "@react-three/rapier";
function Player({ position }: { position: [number, number, number] }) {
const { nodes, materials } = useGLTF("/models/Actor/actor.gltf");
// 使用 nodes 重建,而不是 clone scene
return (
<RigidBody position={position}>
<group>
{Object.values(nodes).map((node: any, i) =>
node.isMesh ? (
<mesh key={i} geometry={node.geometry} material={materials[node.material.name]} />
) : null
)}
</group>
<CapsuleCollider args={[0.6, 0.3]} />
</RigidBody>
);
}总结
直接用 scene 就是最优解