使用react-three-fiber的PointerLockControls无法更新相机位置求助
修复PointerLockControls下相机无法随按键移动的问题
问题根源分析
- PointerLockControls的位置接管:默认情况下
PointerLockControls会维护一个包含相机的分组,直接修改camera.position会被Controls的内置逻辑覆盖,无法同步物理体位置。 - Mesh位置冲突:手动给mesh设置
position={[3,3,3]}会和Cannon物理引擎的位置同步逻辑冲突,导致物理体位置不更新。 - 订阅依赖缺失:
api.velocity.subscribe的useEffect依赖为空,可能导致订阅失效。 - 方向向量计算错误:原代码使用
subVectors合并前后/左右向量,逻辑错误导致移动方向异常或无速度输出。
具体修复步骤
1. 让PointerLockControls跟随物理体
通过Controls的ref同步物理体位置,确保相机所在的分组随物理体移动:
// 新增Controls的ref const controlsRef = useRef<PointerLockControls>(null); // 在useFrame中同步位置 useFrame(() => { if (ref.current && controlsRef.current) { controlsRef.current.position.copy(ref.current.position); } // ...其他逻辑 }); // 绑定ref到PointerLockControls <PointerLockControls ref={controlsRef} />
2. 移除Mesh手动位置设置
删除mesh上的position属性,让React-Three-Cannon自动同步物理体和Mesh的位置:
// 错误写法 <mesh ref={ref} position={[3, 3, 3]}></mesh> // 修复后 <mesh ref={ref}></mesh>
3. 修复Velocity订阅的依赖与清理
补全useEffect依赖,并添加订阅清理逻辑:
useEffect(() => { const unsubscribe = api.velocity.subscribe((v) => { velocity.current = v; }); return unsubscribe; // 组件卸载时清理订阅 }, [api]) // 添加api到依赖数组
4. 修正方向向量计算逻辑
使用addVectors合并前后/左右向量,确保移动方向正确:
useFrame(() => { // ...同步Controls位置逻辑 const direction = new Vector3(); const frontVector = new Vector3(0, 0, Number(moveBackward) - Number(moveForward)); const sideVector = new Vector3(Number(moveLeft) - Number(moveRight), 0, 0); // 合并向量并归一化,设置移动速度后应用相机旋转 direction.addVectors(frontVector, sideVector).normalize(); direction.multiplyScalar(5); direction.applyEuler(camera.rotation); // 保留Y轴原有速度(避免覆盖重力等物理效果) api.velocity.set(direction.x, velocity.current[1], direction.z); });
5. 验证usePlayerControls的正确性
确保按键监听hook正确捕获输入状态,示例实现:
// playerControls.hook.ts import { useState, useEffect } from 'react'; export function usePlayerControls() { const [moveForward, setMoveForward] = useState(false); const [moveBackward, setMoveBackward] = useState(false); const [moveLeft, setMoveLeft] = useState(false); const [moveRight, setMoveRight] = useState(false); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { switch(e.key) { case 'w': setMoveForward(true); break; case 's': setMoveBackward(true); break; case 'a': setMoveLeft(true); break; case 'd': setMoveRight(true); break; } }; const handleKeyUp = (e: KeyboardEvent) => { switch(e.key) { case 'w': setMoveForward(false); break; case 's': setMoveBackward(false); break; case 'a': setMoveLeft(false); break; case 'd': setMoveRight(false); break; } }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, []); return { moveForward, moveBackward, moveLeft, moveRight }; }
完整修复后的代码
import { useEffect, useRef } from "react"; import { Vector3 } from "three"; import { useSphere } from "@react-three/cannon"; import { useFrame, useThree } from "@react-three/fiber" import { PointerLockControls } from "@react-three/drei"; import { usePlayerControls } from "../hooks/playerControls.hook"; export default function Player(): JSX.Element { const { moveForward, moveBackward, moveLeft, moveRight } = usePlayerControls(); const { camera } = useThree(); const controlsRef = useRef<PointerLockControls>(null); const [ref, api] = useSphere(() => ({ mass: 1, type: "Dynamic", position: [0, 1, 0], })); const velocity = useRef([0, 0, 0]); useEffect(() => { const unsubscribe = api.velocity.subscribe((v) => { velocity.current = v; }); return unsubscribe; }, [api]) useFrame(() => { if (ref.current && controlsRef.current) { controlsRef.current.position.copy(ref.current.position); } const direction = new Vector3(); const frontVector = new Vector3(0, 0, Number(moveBackward) - Number(moveForward)); const sideVector = new Vector3(Number(moveLeft) - Number(moveRight), 0, 0); direction.addVectors(frontVector, sideVector).normalize(); direction.multiplyScalar(5); direction.applyEuler(camera.rotation); api.velocity.set(direction.x, velocity.current[1], direction.z); }); return ( <> <PointerLockControls ref={controlsRef} /> <mesh ref={ref} /> </> ) }
内容的提问来源于stack exchange,提问作者Space
相关产品推荐
相关产品推荐

