Unity物理引擎基础

在虚拟现实游戏开发中,物理引擎是实现逼真效果和自然交互的关键组件之一。Unity 引擎内置了强大的物理引擎,可以通过简单的配置和脚本编写来实现复杂的游戏物理效果。本节将详细介绍 Unity 物理引擎的基本原理和内容,包括刚体、碰撞器、物理材质、重力、力和冲力等核心概念。

刚体(Rigidbody)

刚体是物理引擎中最基本的组件之一,用于将对象纳入物理模拟系统。一旦对象添加了刚体组件,物理引擎将自动处理其运动、碰撞和其他物理行为。

刚体属性

刚体组件具有多个属性,这些属性决定了对象在物理世界中的行为:

  • Mass(质量):对象的质量,影响其受力和速度。

  • Drag(阻力):空气阻力,减缓物体运动速度。

  • Angular Drag(角阻力):减缓物体旋转速度。

  • Use Gravity(使用重力):启用或禁用重力。

  • Is Kinematic(是否为运动学):启用时,刚体不受物理引擎影响,但可以检测碰撞。

  • Interpolation(插值):用于平滑网络同步或高速移动物体的插值设置。

  • Collision Detection(碰撞检测):设置碰撞检测模式,适用于不同类型的物体。

示例代码

以下是一个简单的示例,展示如何在脚本中控制刚体的运动:


using UnityEngine;



public class RigidbodyController : MonoBehaviour

{

    public Rigidbody rb; // 引用刚体组件

    public float force = 100f; // 应用力的大小

    public float torque = 50f; // 应用扭矩的大小



    void Start()

    {

        // 确保刚体组件存在

        if (rb == null)

        {

            rb = GetComponent<Rigidbody>();

        }

    }



    void Update()

    {

        // 按下W键时向前施加力

        if (Input.GetKeyDown(KeyCode.W))

        {

            rb.AddForce(transform.forward * force);

        }



        // 按下A键时施加逆时针扭矩

        if (Input.GetKeyDown(KeyCode.A))

        {

            rb.AddTorque(Vector3.up * torque);

        }



        // 按下S键时向后施加力

        if (Input.GetKeyDown(KeyCode.S))

        {

            rb.AddForce(-transform.forward * force);

        }



        // 按下D键时施加顺时针扭矩

        if (Input.GetKeyDown(KeyCode.D))

        {

            rb.AddTorque(-Vector3.up * torque);

        }

    }

}

代码解析

  1. 引用刚体组件:通过 public Rigidbody rb; 声明一个公共变量,可以在 Inspector 中直接引用刚体组件。

  2. 施加力:使用 rb.AddForce(transform.forward * force); 向前施加力,transform.forward 是对象的前方向向量。

  3. 施加扭矩:使用 rb.AddTorque(Vector3.up * torque); 施加逆时针扭矩,Vector3.up 是向上的向量。

碰撞器(Collider)

碰撞器用于定义对象的物理形状,使其能够与其他对象发生碰撞。Unity 提供了多种类型的碰撞器,包括 Box Collider、Sphere Collider、Capsule Collider、Mesh Collider 等。

碰撞器属性

碰撞器组件具有多个属性,这些属性决定了对象的碰撞行为:

  • Is Trigger(是否为触发器):启用时,碰撞器不产生物理碰撞,而是触发事件。

  • Material(材质):物理材质,影响摩擦力和弹力。

  • Center(中心):碰撞器的中心位置,相对于对象的中心。

  • Size(大小):碰撞器的大小,适用于 Box Collider、Capsule Collider 等。

  • Radius(半径):碰撞器的半径,适用于 Sphere Collider、Capsule Collider 等。

示例代码

以下是一个简单的示例,展示如何检测碰撞并做出响应:


using UnityEngine;



public class CollisionDetector : MonoBehaviour

{

    void OnCollisionEnter(Collision collision)

    {

        // 检测到碰撞时,打印碰撞对象的名称

        Debug.Log("Collision with: " + collision.gameObject.name);

    }



    void OnTriggerEnter(Collider other)

    {

        // 检测到触发器时,打印触发对象的名称

        Debug.Log("Trigger with: " + other.gameObject.name);

    }

}

代码解析

  1. OnCollisionEnter:当对象发生物理碰撞时,该方法被调用。Collision 对象包含碰撞信息,如碰撞点、碰撞法线等。

  2. OnTriggerEnter:当对象进入触发器时,该方法被调用。Collider 对象包含触发器的信息。

物理材质(Physics Material)

物理材质用于定义对象表面的物理属性,如摩擦力和弹力。通过物理材质,可以更精细地控制对象的物理行为。

物理材质属性

物理材质组件具有多个属性,这些属性决定了对象表面的物理行为:

  • Dynamic Friction(动态摩擦力):对象在运动时的摩擦力。

  • Static Friction(静态摩擦力):对象静止时的摩擦力。

  • Bounciness(弹性):对象的弹性,影响反弹效果。

  • Friction Combine(摩擦力组合模式):当多个物理材质组合时,如何计算摩擦力。

  • Bounce Combine(弹性组合模式):当多个物理材质组合时,如何计算弹性。

示例代码

以下是一个简单的示例,展示如何为对象分配物理材质:


using UnityEngine;



public class PhysicsMaterialExample : MonoBehaviour

{

    public Rigidbody rb; // 引用刚体组件

    public PhysicsMaterial material; // 引用物理材质



    void Start()

    {

        // 确保刚体组件存在

        if (rb == null)

        {

            rb = GetComponent<Rigidbody>();

        }



        // 为刚体分配物理材质

        rb.sharedMaterial = material;

    }

}

代码解析

  1. 引用物理材质:通过 public PhysicsMaterial material; 声明一个公共变量,可以在 Inspector 中直接引用物理材质。

  2. 分配物理材质:使用 rb.sharedMaterial = material; 为刚体分配物理材质。

重力(Gravity)

重力是物理引擎中的一个重要概念,用于模拟物体在地球或其他天体上的自然下落。在 Unity 中,可以通过刚体组件的 useGravity 属性来启用或禁用重力。

重力属性

  • Gravity:物理引擎的重力,默认值为 (0, -9.81, 0),表示每秒 9.81 米的重力加速度。

  • Use Gravity:刚体组件的属性,启用或禁用重力。

示例代码

以下是一个简单的示例,展示如何启用和禁用重力:


using UnityEngine;



public class GravityController : MonoBehaviour

{

    public Rigidbody rb; // 引用刚体组件



    void Start()

    {

        // 确保刚体组件存在

        if (rb == null)

        {

            rb = GetComponent<Rigidbody>();

        }



        // 禁用重力

        rb.useGravity = false;

    }



    void Update()

    {

        // 按下G键时切换重力状态

        if (Input.GetKeyDown(KeyCode.G))

        {

            rb.useGravity = !rb.useGravity;

            Debug.Log("Gravity: " + (rb.useGravity ? "Enabled" : "Disabled"));

        }

    }

}

代码解析

  1. 禁用重力:在 Start 方法中,使用 rb.useGravity = false; 禁用重力。

  2. 切换重力状态:在 Update 方法中,按下 G 键时切换重力状态,并打印当前状态。

力(Force)

力是改变物体运动状态的重要手段。在 Unity 中,可以通过 Rigidbody 组件的方法来施加力,如 AddForceAddTorque 等。

力属性

  • Force Mode:力的模式,包括 Force、Impulse、Acceleration 和 Velocity Change。

示例代码

以下是一个简单的示例,展示如何使用不同的力模式施加力:


using UnityEngine;



public class ForceExample : MonoBehaviour

{

    public Rigidbody rb; // 引用刚体组件

    public float force = 100f; // 应用力的大小



    void Start()

    {

        // 确保刚体组件存在

        if (rb == null)

        {

            rb = GetComponent<Rigidbody>();

        }

    }



    void Update()

    {

        // 按下F键时施加力

        if (Input.GetKeyDown(KeyCode.F))

        {

            rb.AddForce(Vector3.up * force, ForceMode.Force);

        }



        // 按下I键时施加冲力

        if (Input.GetKeyDown(KeyCode.I))

        {

            rb.AddForce(Vector3.up * force, ForceMode.Impulse);

        }



        // 按下A键时施加加速度

        if (Input.GetKeyDown(KeyCode.A))

        {

            rb.AddForce(Vector3.up * force, ForceMode.Acceleration);

        }



        // 按下V键时施加速度变化

        if (Input.GetKeyDown(KeyCode.V))

        {

            rb.AddForce(Vector3.up * force, ForceMode.VelocityChange);

        }

    }

}

代码解析

  1. 施加力:使用 rb.AddForce(Vector3.up * force, ForceMode.Force); 施加力。

  2. 施加冲力:使用 rb.AddForce(Vector3.up * force, ForceMode.Impulse); 施加冲力。

  3. 施加加速度:使用 rb.AddForce(Vector3.up * force, ForceMode.Acceleration); 施加加速度。

  4. 施加速度变化:使用 rb.AddForce(Vector3.up * force, ForceMode.VelocityChange); 施加速度变化。

碰撞检测模式(Collision Detection Mode)

碰撞检测模式决定了物理引擎如何检测物体的碰撞。Unity 提供了四种碰撞检测模式:Discrete(离散)、Continuous(连续)、Continuous Dynamic(动态连续)和 Continuous Speculative(推测连续)。

碰撞检测模式属性

  • Discrete:默认模式,适用于大多数物体。

  • Continuous:适用于高速移动的物体,可以检测到更细微的碰撞。

  • Continuous Dynamic:适用于动态高速移动的物体,可以与其他连续检测模式的物体进行更精确的碰撞检测。

  • Continuous Speculative:适用于高速移动的物体,通过预测运动路径来检测碰撞。

示例代码

以下是一个简单的示例,展示如何设置碰撞检测模式:


using UnityEngine;



public class CollisionDetectionModeExample : MonoBehaviour

{

    public Rigidbody rb; // 引用刚体组件



    void Start()

    {

        // 确保刚体组件存在

        if (rb == null)

        {

            rb = GetComponent<Rigidbody>();

        }



        // 设置碰撞检测模式为Continuous

        rb.collisionDetectionMode = CollisionDetectionMode.Continuous;

    }

}

代码解析

  1. 创建射线:使用 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 从鼠标位置创建射线。

  2. 进行射线检测:使用 Physics.Raycast(ray, out hit, maxDistance, layerMask); 进行射线检测,hit 包含检测到的对象信息。

  3. 打印检测结果:如果检测到对象,打印对象名称并绘制红色射线;否则,打印“未检测到对象”并绘制绿色射线。

物理查询(Physics Queries)续

物理查询方法

  • Raycast:射线检测,用于检测射线路径上的对象。

  • SphereCast:球形射线检测,用于检测球形路径上的对象。

  • OverlapSphere:球形重叠检测,用于检测指定半径内的对象。

  • CheckSphere:球形检测,用于检测指定半径内是否有对象。

示例代码

以下是一个简单的示例,展示如何使用球形重叠检测:


using UnityEngine;



public class OverlapSphereExample : MonoBehaviour

{

    public LayerMask layerMask; // 引用物理层

    public float radius = 5f; // 球形检测的半径



    void Update()

    {

        // 按下空格键时进行球形重叠检测

        if (Input.GetKeyDown(KeyCode.Space))

        {

            Collider[] colliders = Physics.OverlapSphere(transform.position, radius, layerMask);



            if (colliders.Length > 0)

            {

                Debug.Log("Objects within the sphere:");

                foreach (Collider collider in colliders)

                {

                    Debug.Log(collider.gameObject.name);

                }

            }

            else

            {

                Debug.Log("No objects within the sphere");

            }

        }

    }



    void OnDrawGizmos()

    {

        // 在场景视图中绘制检测球形

        Gizmos.color = Color.blue;

        Gizmos.DrawWireSphere(transform.position, radius);

    }

}

代码解析

  1. 进行球形重叠检测:使用 Collider[] colliders = Physics.OverlapSphere(transform.position, radius, layerMask); 检测指定半径内的对象。

  2. 打印检测结果:如果检测到对象,打印每个对象的名称;否则,打印“未检测到对象”。

  3. 绘制检测球形:使用 OnDrawGizmos 方法在场景视图中绘制检测球形,方便调试。

物理层(Physics Layers)续

物理层用于控制不同对象之间的碰撞和触发器检测。通过物理层,可以更精细地管理对象之间的交互。

物理层属性

  • Layer:对象所在的物理层。

  • Layer Collision Matrix:物理层之间的碰撞矩阵,用于控制哪些层可以碰撞。

示例代码

以下是一个更完整的示例,展示如何使用物理层控制碰撞和触发器检测:

  1. 创建物理层:在 Project Settings -> Tags and Layers 中创建新的物理层。

  2. 设置物理层:在 Inspector 中为对象设置物理层。

  3. 控制碰撞:在 Project Settings -> Physics 中设置物理层之间的碰撞矩阵。

  4. 脚本控制:编写脚本来检测指定物理层上的碰撞和触发器事件。


using UnityEngine;



public class PhysicsLayerExample : MonoBehaviour

{

    public Rigidbody rb; // 引用刚体组件

    public LayerMask layerMask; // 引用物理层



    void Start()

    {

        // 确保刚体组件存在

        if (rb == null)

        {

            rb = GetComponent<Rigidbody>();

        }



        // 检查对象是否在指定的物理层上

        if (1 << layerMask.value == (1 << layerMask.value & 1 << gameObject.layer))

        {

            Debug.Log("Object is in the specified layer");

        }

        else

        {

            Debug.Log("Object is not in the specified layer");

        }

    }



    void OnCollisionEnter(Collision collision)

    {

        // 检查碰撞对象是否在指定的物理层上

        if (1 << layerMask.value == (1 << layerMask.value & 1 << collision.gameObject.layer))

        {

            Debug.Log("Collision with object in the specified layer: " + collision.gameObject.name);

        }

    }



    void OnTriggerEnter(Collider other)

    {

        // 检查触发对象是否在指定的物理层上

        if (1 << layerMask.value == (1 << layerMask.value & 1 << other.gameObject.layer))

        {

            Debug.Log("Trigger with object in the specified layer: " + other.gameObject.name);

        }

    }

}

代码解析

  1. 检查对象是否在指定的物理层上:使用 1 << layerMask.value == (1 << layerMask.value & 1 << gameObject.layer) 检查对象是否在指定的物理层上。

  2. 检查碰撞对象是否在指定的物理层上:使用 1 << layerMask.value == (1 << layerMask.value & 1 << collision.gameObject.layer) 检查碰撞对象是否在指定的物理层上。

  3. 检查触发对象是否在指定的物理层上:使用 1 << layerMask.value == (1 << layerMask.value & 1 << other.gameObject.layer) 检查触发对象是否在指定的物理层上。

物理关节(Physics Joints)续

物理关节用于连接两个刚体,实现关节效果。Unity 提供了多种类型的物理关节,包括 Hinge Joint、Fixed Joint、Configurable Joint 等。

物理关节属性

  • Connected Body:连接的刚体。

  • Anchor:关节的锚点位置。

  • Axis:关节的旋转轴。

  • Limits:关节的运动限制。

  • Spring:关节的弹性复位。

示例代码

以下是一个更完整的示例,展示如何使用 Configurable Joint 连接两个刚体:

  1. 创建两个对象:在场景中创建两个对象,并分别添加刚体组件。

  2. 添加 Configurable Joint:为其中一个对象添加 Configurable Joint 组件,并设置连接的刚体和关节属性。


using UnityEngine;



public class ConfigurableJointExample : MonoBehaviour

{

    public Rigidbody rb1; // 引用第一个刚体组件

    public Rigidbody rb2; // 引用第二个刚体组件



    void Start()

    {

        // 确保刚体组件存在

        if (rb1 == null || rb2 == null)

        {

            Debug.LogError("Rigidbody components are missing");

            return;

        }



        // 创建 Configurable Joint

        ConfigurableJoint configurableJoint = gameObject.AddComponent<ConfigurableJoint>();

        configurableJoint.connectedBody = rb2; // 设置连接的刚体

        configurableJoint.anchor = Vector3.zero; // 设置锚点位置

        configurableJoint.axis = Vector3.up; // 设置旋转轴



        // 设置关节限制

        SoftJointLimit linearLimit = new SoftJointLimit();

        linearLimit.limit = 10f;

        configurableJoint.linearLimit = linearLimit;



        // 设置关节弹簧

        JointSpring jointSpring = new JointSpring();

        jointSpring.spring = 50f;

        jointSpring.damper = 5f;

        jointSpring.targetPosition = 0f;

        configurableJoint.xDrive = new JointDrive { spring = jointSpring.spring, damper = jointSpring.damper, maximumForce = 1000f };



        // 设置关节阻力

        configurableJoint.angularXDrive = new JointDrive { spring = 50f, damper = 5f, maximumForce = 1000f };

    }

}

代码解析

  1. 创建 Configurable Joint:使用 ConfigurableJoint configurableJoint = gameObject.AddComponent<ConfigurableJoint>(); 为对象添加 Configurable Joint 组件。

  2. 设置连接的刚体:使用 configurableJoint.connectedBody = rb2; 设置连接的刚体。

  3. 设置锚点位置:使用 configurableJoint.anchor = Vector3.zero; 设置锚点位置。

  4. 设置旋转轴:使用 configurableJoint.axis = Vector3.up; 设置旋转轴。

  5. 设置关节限制:使用 SoftJointLimit 结构体设置关节的线性限制。

  6. 设置关节弹簧:使用 JointSpring 结构体设置关节的弹性复位。

  7. 设置关节阻力:使用 JointDrive 结构体设置关节的线性和角阻力。

物理引擎优化

物理引擎的性能优化是确保游戏流畅运行的关键。以下是一些常见的优化方法:

优化方法

  1. 减少刚体数量:尽量减少场景中具有刚体组件的对象数量,尤其是对于不经常移动或交互的对象。

  2. 使用离散碰撞检测:对于大多数不高速移动的对象,使用离散碰撞检测模式(Discrete)可以提高性能。

  3. 优化碰撞器:选择合适的碰撞器类型,避免使用复杂的 Mesh Collider。对于不规则形状的对象,可以使用多个简单的碰撞器来近似其形状。

  4. 限制物理模拟的时间步长:在 Project Settings -> Time 中,可以设置 Fixed Timestep 来控制物理模拟的时间步长。

  5. 使用 Layer 功能:通过物理层功能,可以更精细地控制对象之间的碰撞和触发器检测,减少不必要的计算。

示例代码

以下是一个示例,展示如何减少刚体数量并优化碰撞器:


using UnityEngine;



public class PhysicsOptimizationExample : MonoBehaviour

{

    public GameObject[] objects; // 引用需要优化的对象数组



    void Start()

    {

        foreach (GameObject obj in objects)

        {

            Rigidbody rb = obj.GetComponent<Rigidbody>();



            // 如果对象不需要物理模拟,禁用刚体

            if (obj.name.Contains("Static"))

            {

                if (rb != null)

                {

                    rb.isKinematic = true;

                }

            }



            // 优化碰撞器

            Collider collider = obj.GetComponent<Collider>();

            if (collider is MeshCollider)

            {

                // 将复杂的 Mesh Collider 替换为简单的碰撞器

                BoxCollider boxCollider = obj.AddComponent<BoxCollider>();

                boxCollider.size = new Vector3(1f, 1f, 1f);

                Destroy(collider);

            }

        }

    }

}

代码解析

  1. 禁用刚体:对于不需要物理模拟的静态对象,使用 rb.isKinematic = true; 将其设置为运动学刚体。

  2. 优化碰撞器:将复杂的 Mesh Collider 替换为简单的 Box Collider,减少物理计算的复杂度。

总结

Unity 的物理引擎提供了丰富的功能和属性,通过合理配置刚体、碰撞器、物理材质、重力、力和冲力等,可以实现逼真的物理效果和自然的交互。同时,通过物理层和碰撞检测模式的设置,可以更精细地控制对象之间的交互。最后,优化物理引擎的性能是确保游戏流畅运行的重要步骤,可以通过减少刚体数量、选择合适的碰撞器类型和限制物理模拟的时间步长来实现。

希望本节内容能帮助你更好地理解和使用 Unity 物理引擎,为你的虚拟现实游戏开发带来更多的可能性。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐