Threejs案例
three-player-controller
three-player-controller

Three.js 本身是一个渲染库,没有内置的角色控制器。在大型场景里让角色走路、跳跃、不穿墙,涉及碰撞检测、物理响应、动画同步、摄像机管理四个方向,随便哪一个单独做都不简单。

three-player-controller 是一个开箱即用的第一/第三人称角色控制器,基于 Three.js 和 three-mesh-bvh,用胶囊体碰撞 + BVH 加速方案解决了其中最核心的性能瓶颈。目前在 GitHub 上有 121 个 Star,已发布至 npm。

引入完整物理引擎的代价

常见的 Three.js 角色控制方案是接入 Rapier、Cannon.js 或 Ammo.js 等物理引擎。这类方案将角色建模为刚体,碰撞、重力、弹跳都由物理引擎统一计算。

代价是显而易见的:完整物理引擎的 bundle 体积通常在几百 KB 到几 MB 之间,WASM 版本还需要额外的加载初始化流程。更关键的是,角色在游戏里的行为和真实物理规律是相悖的——人走路不会弹跳,碰到斜面要平滑滑过而不是翻滚,跳跃曲线往往需要人为干预才符合手感。引入完整物理引擎反而要花大量时间和配置去「反物理化」。

另一条路是直接用 Three.js 的 Raycaster 做射线碰撞,把若干根射线从角色位置往地面和四周打出去,检测是否触碰几何体。这个方案实现简单,但在复杂大场景里每帧对所有三角面做遍历,性能开销随场景规模线性增长,很快会成为瓶颈。

three-player-controller 的做法

这个库的核心思路是:用胶囊体表示角色的碰撞体积,用 BVH 加速静态场景的碰撞查询,对动态物体(载具)才引入 Rapier。

胶囊体碰撞的设计逻辑

胶囊体是一个两端带半球的圆柱体,是游戏引擎里角色控制器的标准碰撞形状。Unity 的 CharacterController、Unreal 的 Character 用的都是这个。

选择它的原因在于几何上的便利性:胶囊体底部是曲面,接触斜坡或台阶时自然产生向上的分力,角色会沿斜面滑动而不是卡住。同时胶囊体和任意三角面的距离计算是闭合公式,没有近似误差,适合做精确的穿透检测和位置修正。

参数 capsuleRadiusRatio 允许按角色模型尺寸比例调整胶囊半径,不需要手动测量像素值。

BVH 对静态场景的作用

BVH(Bounding Volume Hierarchy,层次包围盒)是一种将场景几何体组织成树状结构的加速数据结构。叶节点存储实际三角面,中间节点存储子树的包围盒。碰撞查询时从根节点开始,只有当查询形状和当前节点的包围盒相交才继续向下递归,否则整个子树跳过。

这把碰撞查询从 O(n) 降到 O(log n),n 是场景三角面数量。场景越大、越复杂,BVH 的优势越明显。

实际使用时,通过 buildStaticCollider 方法把场景静态几何体(地形、建筑等)预处理成 BVH 结构,后续每帧的碰撞查询都在这棵树上进行,不重新遍历原始几何。动态物体(会移动的障碍物)通过 addDynamicColliderremoveDynamicCollider 单独维护,可以按需更新。

载具系统引入 Rapier 的边界

步行、飞行模式完全不依赖物理引擎,碰撞和运动计算都在 three-player-controller 内部完成。载具模式才需要额外安装 @dimforge/rapier3d-compat,因为车辆需要真实的悬挂、轮胎摩擦和刚体动力学,手写这套系统的成本远高于直接使用物理引擎。

这个边界划分使基础用法保持轻量——不需要车辆的场景,依赖树里没有 Rapier。

摄像机碰撞规避

第三人称摄像机的经典问题:角色靠近墙时,摄像机穿进墙里。这个库内置了摄像机障碍物检测,在角色和摄像机之间打射线,命中场景几何体时自动缩短摄像机距离,角色远离墙后再平滑恢复。setMinCamDistancesetMaxCamDistance 控制缩放范围。

动画与视角系统

动画层面,库内置了一套 Locomotion Set(运动集)的概念——把步行、奔跑、跳跃等基础动画注册进去,控制器根据运动状态自动切换,不需要手动管理 AnimationMixer 的每次播放和过渡。也可以通过 playPlayerAnimationByName 手动触发特定动画,或者用 registerLocomotionSet 注册多套动画集(比如不同武器状态下的不同动作),运行时通过 switchLocomotionSet 切换。

视角切换(V 键)在第一人称和第三人称之间来回,切换时触发 onBeforeViewChangeonViewChange 事件,可以在这里处理 UI 变化或摄像机参数调整。第三人称提供了越肩视角(Over-the-Shoulder)选项,常见于射击游戏。

写在最后

three-player-controller 目前处于活跃开发阶段,API 在近期的 commit 里持续有变化,用于生产项目需要关注版本更新。

已知的局限:物理系统不处理角色和角色之间的动态碰撞,多角色场景需要自行处理;载具功能依赖 Rapier,首次加载需要等待 WASM 初始化完成。

这个库适合以下两类场景:在 Three.js 项目里需要基础角色控制能力,但不想引入完整物理引擎增加复杂度;或者正在做 WebGL 场景展示、建筑漫游、虚拟展厅一类的应用,需要自由移动的视角控制。

如果你之前尝试过用纯射线做角色碰撞,在大场景里遇到性能问题,BVH 方案值得看一下具体实现。

GitHub:https://github.com/hh-hang/three-player-controller (opens in a new tab)