如何用Cannon.js与Three.js实现物体间的引力吸引效果?
实现天体间引力模拟的实用方案
核心:基于万有引力定律的力计算
先明确核心公式:F = G * (m1*m2)/r²,其中G是引力常数,m1/m2为两个物体质量,r是质心间距。实际编码要注意两个关键点:
- 先计算方向向量:从当前物体指向目标物体的向量,归一化后再乘以力的大小,保证力的方向正确
- 防除零崩溃:当两个物体重合或距离过近时,直接跳过计算,避免数值异常
以下是通用逻辑的伪代码(以Unity为例,其他引擎可直接复用逻辑):
// 给每个天体挂载的引力组件 public class GravitationalBody : MonoBehaviour { public float mass; private Rigidbody _rb; void Awake() { _rb = GetComponent<Rigidbody>(); _rb.mass = mass; // 自动加入全局管理列表 GravitationManager.RegisterBody(this); } void OnDestroy() { GravitationManager.UnregisterBody(this); } public Rigidbody GetRigidbody() => _rb; } // 全局引力管理类,彻底解决手动添加的麻烦 public static class GravitationManager { private static List<GravitationalBody> _allBodies = new(); // 真实引力常数太小,游戏里可根据场景缩放调整,比如改成100 public const float G = 6.67430e-11f; public static void RegisterBody(GravitationalBody body) { if (!_allBodies.Contains(body)) _allBodies.Add(body); } public static void UnregisterBody(GravitationalBody body) { _allBodies.Remove(body); } // 每帧批量计算所有物体间的引力 public static void UpdateAllGravity() { // 双层循环避免重复计算(A对B和B对A的力只算一次) for (int i = 0; i < _allBodies.Count; i++) { var bodyA = _allBodies[i]; for (int j = i + 1; j < _allBodies.Count; j++) { var bodyB = _allBodies[j]; ApplyMutualGravity(bodyA, bodyB); } } } private static void ApplyMutualGravity(GravitationalBody a, GravitationalBody b) { Vector3 direction = b.transform.position - a.transform.position; float distanceSq = direction.sqrMagnitude; // 距离过近时跳过,防止力过大导致物体飞出去 if (distanceSq < 0.1f) return; float forceMag = G * (a.mass * b.mass) / distanceSq; Vector3 force = direction.normalized * forceMag; a.GetRigidbody().AddForce(force); b.GetRigidbody().AddForce(-force); // 作用力与反作用力 } }
解决“逐个添加物体”的繁琐问题
- 自动注册机制:每个天体脚本在初始化时自动加入全局管理列表,销毁时自动移除,完全不用手动维护
- 扩展方案:如果是批量生成的天体,可以给它们统一加标签,启动时遍历场景中带该标签的物体完成注册;或者结合对象池,在生成物体时自动调用注册方法
性能与体验优化建议
- 避免重复计算:用
i从0开始,j从i+1开始的双层循环,减少一半计算量 - 适配游戏场景:真实引力常数G极小,游戏里可根据天体大小、移动速度放大数值(比如改成100),否则引力效果会不明显
- 海量天体优化:如果场景内天体数量超过50个,可采用空间分区算法(比如四叉树、八叉树),只计算邻近天体间的引力,大幅降低计算量
内容的提问来源于stack exchange,提问作者Majesto
相关产品推荐
相关产品推荐

