Unity引擎源码-物理系统详解-其一

发布于:2025-05-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

从今天开始我们要对Unity下手了:想要在后续的开发中执行,扩展Unity的内容,我们得先学习Unity的内容。

目前主流的商业引擎中,Unity和UE就是最热门的。我大概率后续会出UE的部分,但是现在让我们先学习Unity引擎吧。Unity的源码并没有完全公开,我们阅读公开的部分。

源码来自于Github。

Unity-Technologies/UnityCsReference: Unity C# reference source code. (github.com)

一个引擎中是有多个系统组成的,比如渲染系统,物理系统,UI系统,动画系统等等,显然我也没有精力进行全面的学习,所以我们先挑一些重要的系统的部分进行理解。

今天带来的就是物理系统:

Unity给的源码中有关物理的部分主要包含以下目录:

可以看到一个是物理系统有专门为3D和2D进行分类,但其实本质上差距不大,只是具体考虑的维度少了一维,结构上是基本一样的,且目前3D物理显然是被更多考虑的一方,所以我就把精力主要放在3D物理上吧。

我们来一点一点介绍:

Physics

从宏观的角度来说,Unity的物理系统其实只是提供了一个C#可以使用的接口,本质上Unity的物理运算其实还是交给底层的C++写成的PhysX物理系统来做。

Managed

我们首先来看Unity的物理系统中力的形式:

ForceMode.cs

namespace UnityEngine
{
    public enum ForceMode
    {
        Force = 0,
        Acceleration = 5,
        Impulse = 1,
        VelocityChange = 2,
    }
}

枚举中关于力的模式分为了四种,Force和Acceleration是持续性的力,区别在于是否考虑质量(前者考虑质量而后者不考虑);Impulse和VelocityChange是瞬时性的力,同样区别在于是否考虑质量(前者考虑质量而后者不考虑)。

然后是Unity中的碰撞检测的形式,也是四种:

CollisionDetectionMode.cs

namespace UnityEngine
{
    public enum CollisionDetectionMode
    {
        Discrete = 0,
        Continuous = 1,
        ContinuousDynamic = 2,
        ContinuousSpeculative = 3
    }
}

分别是:Discrete(离散)模式仅在物理步骤结束时检测碰撞,计算开销最小但可能导致快速物体穿透;Continuous(连续)模式计算物体的完整运动轨迹,防止该物体穿透其他物体;ContinuousDynamic(连续动态)模式提供双向碰撞检测,不仅检测该物体与其他物体的碰撞,也检测其他连续模式物体与它的碰撞,精度最高但计算成本大;ContinuousSpeculative(连续推测)模式使用数学预测而非完整轨迹计算来预防碰撞,在精度和性能间取得平衡。

然后我们来看看Unity里的Collision脚本:

Collision.cs

    public struct ContactPoint
    {
        internal Vector3 m_Point;
        internal Vector3 m_Normal;
        internal Vector3 m_Impulse;
        internal int m_ThisColliderInstanceID;
        internal int m_OtherColliderInstanceID;
        internal float m_Separation;

        public Vector3 point { get { return m_Point; } }
        public Vector3 normal { get { return m_Normal; } }
        public Vector3 impulse { get { return m_Impulse; } }

        public Collider thisCollider { get { return Physics.GetColliderByInstanceID(m_ThisColliderInstanceID); } }
        public Collider otherCollider { get { return Physics.GetColliderByInstanceID(m_OtherColliderInstanceID); } }
        public float separation { get { return m_Separation; } }

        internal ContactPoint(Vector3 point, Vector3 normal, Vector3 impulse, float separation, int thisInstanceID, int otherInstenceID)
        {
            m_Point = point;
            m_Normal = normal;
            m_Impulse = impulse;
            m_Separation = separation;
            m_ThisColliderInstanceID = thisInstanceID;
            m_OtherColliderInstanceID = otherInstenceID;
        }
    }

首先是碰撞检测实际要检测的内容:碰撞点的结构体的定义。

这里几个值得一说的点:用internal修饰了一系列数据类型,作用是只允许指定的程序集进行访问(程序集比如DDL);这里的分离距离指的是两个碰撞体在接触点处的微小间隙或重叠量,正值表示碰撞体之间有微小间隙,尚未完全接触而负值表示碰撞体有轻微重叠/穿透;用internal修饰构造函数的含义也是只允许指定的程序集内部新建该类的实例。

public partial class Collision
    {
        private ContactPairHeader m_Header;
        private ContactPair m_Pair;
        private bool m_Flipped;
        private ContactPoint[] m_LegacyContacts = null;

        public Vector3 impulse => m_Pair.impulseSum;
        public Vector3 relativeVelocity => m_Flipped ? m_Header.m_RelativeVelocity : -m_Header.m_RelativeVelocity;
        public Rigidbody rigidbody => body as Rigidbody;
        public ArticulationBody articulationBody => body as ArticulationBody;
        public Component body => m_Flipped ? m_Header.body : m_Header.otherBody;
        public Collider collider => m_Flipped ? m_Pair.collider : m_Pair.otherCollider;
        public Transform transform { get { return rigidbody != null ? rigidbody.transform : collider.transform; } }
        public GameObject gameObject { get { return body != null ? body.gameObject : collider.gameObject; } }
        internal bool Flipped { get { return m_Flipped; } set { m_Flipped = value; } }

        // The number of contacts available.
        public int contactCount { get { return (int)m_Pair.m_NbPoints; } }

        // The contact points generated by the physics engine.
        // NOTE: This produces garbage and should be avoided.
        public ContactPoint[] contacts
        {
            get
            {
                if (m_LegacyContacts == null)
                {
                    m_LegacyContacts = new ContactPoint[m_Pair.m_NbPoints];
                    m_Pair.ExtractContactsArray(m_LegacyContacts, m_Flipped);
                }

                return m_LegacyContacts;
            }
        }

        public Collision()
        {
            m_Header = new ContactPairHeader();
            m_Pair = new ContactPair();
            m_Flipped = false;

            m_LegacyContacts = null;
        }

        // Assumes we are NOT in the reusing mode
        internal Collision(in ContactPairHeader header, in ContactPair pair, bool flipped)
        {
            m_LegacyContacts = new ContactPoint[pair.m_NbPoints];
            pair.ExtractContactsArray(m_LegacyContacts, flipped);
            m_Header = header;
            m_Pair = pair;
            m_Flipped = flipped;
        }

        // Assumes we are in the reusing mode
        internal void Reuse(in ContactPairHeader header, in ContactPair pair)
        {
            m_Header = header;
            m_Pair = pair;
            m_LegacyContacts = null;
            m_Flipped = false;
        }

        // Get contact at specific index.
        public unsafe ContactPoint GetContact(int index)
        {
            if (index < 0 || index >= contactCount)
                throw new ArgumentOutOfRangeException(String.Format("Cannot get contact at index {0}. There are {1} contact(s).", index, contactCount));

            if (m_LegacyContacts != null)
                return m_LegacyContacts[index];

            float sign = m_Flipped ? -1f : 1f;
            var ptr = m_Pair.GetContactPoint_Internal(index);

            return new ContactPoint(
                    ptr->m_Position,
                    ptr->m_Normal * sign,
                    ptr->m_Impulse,
                    ptr->m_Separation,
                    m_Flipped ? m_Pair.otherColliderInstanceID : m_Pair.colliderInstanceID,
                    m_Flipped ? m_Pair.colliderInstanceID : m_Pair.otherColliderInstanceID);
        }

        // Get contacts for this collision.
        public int GetContacts(ContactPoint[] contacts)
        {
            if (contacts == null)
                throw new NullReferenceException("Cannot get contacts as the provided array is NULL.");

            if (m_LegacyContacts != null)
            {
                int length = Mathf.Min(m_LegacyContacts.Length, contacts.Length);
                Array.Copy(m_LegacyContacts, contacts, length);
                return length;
            }

            return m_Pair.ExtractContactsArray(contacts, m_Flipped);
        }

        // Get contacts for this collision.
        public int GetContacts(List<ContactPoint> contacts)
        {
            if (contacts == null)
                throw new NullReferenceException("Cannot get contacts as the provided list is NULL.");

            contacts.Clear();

            if (m_LegacyContacts != null)
            {
                contacts.AddRange(m_LegacyContacts);
                return m_LegacyContacts.Length;
            }

            int n = (int)m_Pair.m_NbPoints;

            if (n == 0)
                return 0;

            if (contacts.Capacity < n) // Resize here instead of in native
                contacts.Capacity = n;

            return m_Pair.ExtractContacts(contacts, m_Flipped);
        }
    }

这是我们的具体的Collision类,我们在这里封装了一个Collision类对象并把这个对象作为参数传递给其他碰撞检测函数如OnCollisionEnter回调函数。

我们先来看类定义中的关键字partial:

具体来说这个类中包含的数据类型有:

        private ContactPairHeader m_Header;
        private ContactPair m_Pair;
        private bool m_Flipped;
        private ContactPoint[] m_LegacyContacts = null;

        public Vector3 impulse => m_Pair.impulseSum;
        public Vector3 relativeVelocity => m_Flipped ? m_Header.m_RelativeVelocity : -m_Header.m_RelativeVelocity;
        public Rigidbody rigidbody => body as Rigidbody;
        public ArticulationBody articulationBody => body as ArticulationBody;
        public Component body => m_Flipped ? m_Header.body : m_Header.otherBody;
        public Collider collider => m_Flipped ? m_Pair.collider : m_Pair.otherCollider;
        public Transform transform { get { return rigidbody != null ? rigidbody.transform : collider.transform; } }
        public GameObject gameObject { get { return body != null ? body.gameObject : collider.gameObject; } }
        internal bool Flipped { get { return m_Flipped; } set { m_Flipped = value; } }

        // The number of contacts available.
        public int contactCount { get { return (int)m_Pair.m_NbPoints; } }

Collision类存储了碰撞事件的完整信息,其中私有字段包括:碰撞头信息(m_Header)、碰撞对数据(m_Pair)、方向翻转标志(m_Flipped)和缓存的接触点数组(m_LegacyContacts);公开属性则提供了对碰撞物理特性的访问,如碰撞冲量(impulse)、相对速度(relativeVelocity)、接触点数量(contactCount),以及对相关游戏对象的引用,包括刚体(rigidbody)、关节体(articulationBody)、碰撞体(collider)、变换组件(transform)和游戏对象(gameObject),

当碰撞检测发生时,Unity物理引擎创建Collision对象并填充其成员变量:m_Header和m_Pair存储底层物理引擎生成的原始碰撞数据;m_Flipped确定哪个物体是"自己"哪个是"对方";这些数据用于计算impulse(决定碰撞反弹强度)、relativeVelocity(影响碰撞音效音量)、contactCount(确定接触点数量);同时提供对碰撞物体组件的引用(rigidbody、collider等),让开发者能在OnCollisionEnter等回调中获取碰撞物体、施加力、生成特效;transform和gameObject属性提供便捷访问碰撞物体的变换和游戏对象;contacts属性则在首次访问时延迟初始化m_LegacyContacts数组并从原生数据中提取接触点信息,虽然会产生垃圾回收但提供了完整的接触点数据,使开发者能在接触点精确位置生成粒子或应用物理反应。

        public Collision()
        {
            m_Header = new ContactPairHeader();
            m_Pair = new ContactPair();
            m_Flipped = false;

            m_LegacyContacts = null;
        }

        // Assumes we are NOT in the reusing mode
        internal Collision(in ContactPairHeader header, in ContactPair pair, bool flipped)
        {
            m_LegacyContacts = new ContactPoint[pair.m_NbPoints];
            pair.ExtractContactsArray(m_LegacyContacts, flipped);
            m_Header = header;
            m_Pair = pair;
            m_Flipped = flipped;
        }

提供了两个构造函数:默认构造和有参构造,默认构造中新建碰撞头和碰撞对以及m_Flipped,并把缓冲的碰撞点数组初始化为空。有参构造则是接收三个参数:原生碰撞头(header)、碰撞对(pair)和方向标志(flipped)。构造函数首先根据碰撞点数量创建接触点数组,然后立即从原生数据中提取接触点信息填充该数组,这与延迟初始化的public contacts属性不同,表明此构造函数用于需要立即访问接触点的场景;接着存储碰撞头、碰撞对和翻转标志,完成对象初始化。

        internal void Reuse(in ContactPairHeader header, in ContactPair pair)
        {
            m_Header = header;
            m_Pair = pair;
            m_LegacyContacts = null;
            m_Flipped = false;
        }

 想象一下,游戏中有很多物体在不断碰撞,如果每次碰撞都新建一个对象,会很浪费内存,它接收新的碰撞数据(header和pair),更新对象的内部状态,但将m_LegacyContacts设为null并重置m_Flipped标志,这意味着它确实不保留原有的接触点数据,当一个碰撞结束后,不扔掉这个对象,下次有新碰撞时,调用Reuse方法,把新的碰撞数据塞进旧对象里,把旧的接触点数据(m_LegacyContacts)清空,等真正需要时再重新计算。

        public unsafe ContactPoint GetContact(int index)
        {
            if (index < 0 || index >= contactCount)
                throw new ArgumentOutOfRangeException(String.Format("Cannot get contact at index {0}. There are {1} contact(s).", index, contactCount));

            if (m_LegacyContacts != null)
                return m_LegacyContacts[index];

            float sign = m_Flipped ? -1f : 1f;
            var ptr = m_Pair.GetContactPoint_Internal(index);

            return new ContactPoint(
                    ptr->m_Position,
                    ptr->m_Normal * sign,
                    ptr->m_Impulse,
                    ptr->m_Separation,
                    m_Flipped ? m_Pair.otherColliderInstanceID : m_Pair.colliderInstanceID,
                    m_Flipped ? m_Pair.colliderInstanceID : m_Pair.otherColliderInstanceID);
        }

我们先解释一下这个unsafe关键字:

因为C#中的内存管理其实通常是由.NET框架中的运行时自动实现的,所以如果我们要去操作内存或者使用指针,必须是unsafe模式下才可以使用。

这个函数本身就是返回对应索引的接触点数组中的接触点的信息,首先检查索引是否合理,不合理则直接抛出异常,然后是如果接触点数组不为空的话就返回该接触点,如果没有的话我们就需要去从底层的物理引擎处获取这个点的数据:我们用指针直接访问存储接触点数据的内存以避免不必要的复制,最后返回新生成的接触点。

        public int GetContacts(ContactPoint[] contacts)
        {
            if (contacts == null)
                throw new NullReferenceException("Cannot get contacts as the provided array is NULL.");

            if (m_LegacyContacts != null)
            {
                int length = Mathf.Min(m_LegacyContacts.Length, contacts.Length);
                Array.Copy(m_LegacyContacts, contacts, length);
                return length;
            }

            return m_Pair.ExtractContactsArray(contacts, m_Flipped);
        }

这个函数则是接收接触点数组之后检查缓冲的接触点数组,我们找到缓冲的数组和新接收的数组哪个更小,把缓冲的接触点数组复制给接收的新数组之后返回实际复制的接触点个数;如果缓冲接触点数组为空我们直接从原生的接触对象中提取接触点之后放入接收的数组中后返回这个复制的数量。

        public int GetContacts(List<ContactPoint> contacts)
        {
            if (contacts == null)
                throw new NullReferenceException("Cannot get contacts as the provided list is NULL.");

            contacts.Clear();

            if (m_LegacyContacts != null)
            {
                contacts.AddRange(m_LegacyContacts);
                return m_LegacyContacts.Length;
            }

            int n = (int)m_Pair.m_NbPoints;

            if (n == 0)
                return 0;

            if (contacts.Capacity < n) // Resize here instead of in native
                contacts.Capacity = n;

            return m_Pair.ExtractContacts(contacts, m_Flipped);
        }

与之前类似,只是这一次接收的参数是一个组成元素为接触点的链表,如果这个链表为空抛出异常,我们首先清空链表,如果缓冲的接触点数组不为空则直接把数组的内容全部给到链表并返回接触点数组的长度表示接触点的个数,否则我们获取当前接触对的总接触点个数,这个接触点为0时直接返回0,否则如果链表的容量小于接触点的总个数我们更新链表的容量,最后我们把接触点都放进链表中并返回接触点个数。

不难发现Collision类的成员函数本质上都是帮助你去获取接触点信息的:Reuse函数帮助你更新缓冲的接触点数组以及接触对的头部和接触对本身实现当前类的实例的复用,三个GetContacts重载函数帮助你把缓冲的接触点输入到对应的容器中并返回接触点的个数。

JointConstraints.cs

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using System.ComponentModel;

namespace UnityEngine
{
    // The definition of limits that can be applied to either ConfigurableJoint or CharacterJoint
    public partial struct SoftJointLimit
    {
        private float m_Limit;
        private float m_Bounciness;
        private float m_ContactDistance;

        public float limit { get { return m_Limit; } set { m_Limit = value; } }
        public float bounciness { get { return m_Bounciness; } set { m_Bounciness = value; } }
        public float contactDistance { get { return m_ContactDistance; } set { m_ContactDistance = value; } }
    }

    public struct SoftJointLimitSpring
    {
        private float m_Spring;
        private float m_Damper;

        public float spring { get { return m_Spring; } set { m_Spring = value; } }
        public float damper { get { return m_Damper; } set { m_Damper = value; } }
    }

    // How the joint's movement will behave along a local axis
    public partial struct JointDrive
    {
        private float m_PositionSpring;
        private float m_PositionDamper;
        private float m_MaximumForce;
        private int m_UseAcceleration;

        public float positionSpring { get { return m_PositionSpring; } set { m_PositionSpring = value; } }
        public float positionDamper { get { return m_PositionDamper; } set { m_PositionDamper = value; } }
        public float maximumForce { get { return m_MaximumForce; } set { m_MaximumForce = value; } }
        public bool useAcceleration { get { return m_UseAcceleration == 1; } set { m_UseAcceleration = value ? 1 : 0; } }
    }

    // The JointMotor is used to motorize a joint.
    public struct JointMotor
    {
        private float m_TargetVelocity;
        private float m_Force;
        private int m_FreeSpin;

        public float targetVelocity { get { return m_TargetVelocity; } set { m_TargetVelocity = value; } }
        public float force { get { return m_Force; } set { m_Force = value; } }
        public bool freeSpin { get { return m_FreeSpin == 1; } set { m_FreeSpin = value ? 1 : 0; } }
    }

    // JointSpring is used add a spring force to HingeJoint.
    public struct JointSpring
    {
        public float spring;
        public float damper;
        public float targetPosition;

        // We have to keep those as public variables because of a bug in the C# raycast sample.
    }

    public struct JointLimits
    {
        private float m_Min;
        private float m_Max;
        private float m_Bounciness;
        private float m_BounceMinVelocity;
        private float m_ContactDistance;

        public float min { get { return m_Min; } set { m_Min = value; } }
        public float max { get { return m_Max; } set { m_Max = value; } }
        public float bounciness { get { return m_Bounciness; } set { m_Bounciness = value; } }
        public float bounceMinVelocity { get { return m_BounceMinVelocity; } set { m_BounceMinVelocity = value; } }
        public float contactDistance { get { return m_ContactDistance; } set { m_ContactDistance = value; } }

        // NB - member fields can't be in other partial structs, so we cannot move this out; work out a plan to remove them then
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("minBounce and maxBounce are replaced by a single JointLimits.bounciness for both limit ends.", true)]
        public float minBounce;

        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("minBounce and maxBounce are replaced by a single JointLimits.bounciness for both limit ends.", true)]
        public float maxBounce;
    }
}

这个脚本的主要内容就是定义了Unity物理系统中用于关节约束的各种结构体,它们用于控制物理关节的行为特性。所谓的关节就是指连接两个刚体之间的结构,我们在脚本中定义了一系列不同种类的关节结构,有软质关节,软质关节弹簧,关节驱动等等。

namespace UnityEngine
{
    //TODO: We should move this type into the VehicleModule assembly when possible.
    // WheelFrictionCurve is used by the WheelCollider to describe friction properties of the wheel tire.
    public struct WheelFrictionCurve
    {
        private float m_ExtremumSlip;
        private float m_ExtremumValue;
        private float m_AsymptoteSlip;
        private float m_AsymptoteValue;
        private float m_Stiffness;

        public float extremumSlip { get { return m_ExtremumSlip; } set { m_ExtremumSlip = value; } }
        public float extremumValue { get { return m_ExtremumValue; } set { m_ExtremumValue = value; } }
        public float asymptoteSlip { get { return m_AsymptoteSlip; } set { m_AsymptoteSlip = value; } }
        public float asymptoteValue { get { return m_AsymptoteValue; } set { m_AsymptoteValue = value; } }
        public float stiffness { get { return m_Stiffness; } set { m_Stiffness = value; } }
    }
}

Unity的Managed部分的代码就到这了,综合来说,我们Unity的Physics部分的Managed部分的内容都是在C#部分处运作,主要负责C#层面的内容。

现在我们要来学习ScriptsBindings部分了,但是如果你现在打开源码,会看到密密麻麻的茫茫一片脚本,显然我们没有时间也没有精力阅读完这么多东西,所以我们的思路是阅读一些比较常用的部分的代码,然后学习其思路,最后尝试自己实现乃至创新修改。

ScriptsBindings

我们首先来梳理一下整个ScriptsBindings的大体组成,来看看一个物理系统里的组成部分。

说到我们的物理系统,自然绕不开刚体:

Rigidbody.bindings.cs

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine.Bindings;
using UnityEngine.Internal;

namespace UnityEngine
{
    public enum RigidbodyConstraints
    {
        None = 0,
        FreezePositionX = 1 << 1,
        FreezePositionY = 1 << 2,
        FreezePositionZ = 1 << 3,
        FreezeRotationX = 1 << 4,
        FreezeRotationY = 1 << 5,
        FreezeRotationZ = 1 << 6,
        FreezePosition = FreezePositionX | FreezePositionY | FreezePositionZ,
        FreezeRotation = FreezeRotationX | FreezeRotationY | FreezeRotationZ,
        FreezeAll = FreezePosition | FreezeRotation
    }

    public enum RigidbodyInterpolation
    {
        None = 0,
        Interpolate = 1,
        Extrapolate = 2
    }

    [RequireComponent(typeof(Transform))]
    [NativeHeader("Modules/Physics/Rigidbody.h")]
    public partial class Rigidbody : Component
    {
        extern public Vector3 linearVelocity { get; set; }
        extern public Vector3 angularVelocity { get; set; }
        extern public float linearDamping { get; set; }
        extern public float angularDamping { get; set; }
        extern public float mass { get; set; }
        extern public bool useGravity { get; set; }
        extern public float maxDepenetrationVelocity { get; set; }
        extern public bool isKinematic { get; set; }
        public bool freezeRotation
        {
            get => constraints.HasFlag(RigidbodyConstraints.FreezeRotation);
            set
            {
                if (value)
                    constraints |= RigidbodyConstraints.FreezeRotation;
                else
                    constraints &= RigidbodyConstraints.FreezePosition;
            }
        }
        extern public RigidbodyConstraints constraints { get; set; }
        extern public CollisionDetectionMode collisionDetectionMode { get; set; }
        extern public bool automaticCenterOfMass { get; set; }
        extern public Vector3 centerOfMass { get; set; }
        extern public Vector3 worldCenterOfMass { get; }
        extern public bool automaticInertiaTensor { get; set; }
        extern public Quaternion inertiaTensorRotation { get; set; }
        extern public Vector3 inertiaTensor { get; set; }
        extern internal Matrix4x4 worldInertiaTensorMatrix { get; }
        extern public bool detectCollisions { get; set; }
        extern public Vector3 position { get; set; }
        extern public Quaternion rotation { get; set; }
        extern public RigidbodyInterpolation interpolation { get; set; }
        extern public int solverIterations { get; set; }
        extern public float sleepThreshold { get; set; }
        extern public float maxAngularVelocity { get; set; }
        extern public float maxLinearVelocity { get; set; }
        extern public void MovePosition(Vector3 position);
        extern public void MoveRotation(Quaternion rot);
        extern public void Move(Vector3 position, Quaternion rotation);
        extern public void Sleep();
        extern public bool IsSleeping();
        extern public void WakeUp();
        extern public void ResetCenterOfMass();
        extern public void ResetInertiaTensor();
        extern public Vector3 GetRelativePointVelocity(Vector3 relativePoint);
        extern public Vector3 GetPointVelocity(Vector3 worldPoint);
        extern public int solverVelocityIterations { get; set; }
        extern public void PublishTransform();
        extern public LayerMask excludeLayers { get; set; }
        extern public LayerMask includeLayers { get; set; }
        extern public Vector3 GetAccumulatedForce([DefaultValue("Time.fixedDeltaTime")] float step);

        [ExcludeFromDocs]
        public Vector3 GetAccumulatedForce()
        {
            return GetAccumulatedForce(Time.fixedDeltaTime);
        }

        extern public Vector3 GetAccumulatedTorque([DefaultValue("Time.fixedDeltaTime")] float step);

        [ExcludeFromDocs]
        public Vector3 GetAccumulatedTorque()
        {
            return GetAccumulatedTorque(Time.fixedDeltaTime);
        }

        extern public void AddForce(Vector3 force, [DefaultValue("ForceMode.Force")] ForceMode mode);

        [ExcludeFromDocs]
        public void AddForce(Vector3 force)
        {
            AddForce(force, ForceMode.Force);
        }

        public void AddForce(float x, float y, float z, [DefaultValue("ForceMode.Force")] ForceMode mode) { AddForce(new Vector3(x, y, z), mode); }

        [ExcludeFromDocs]
        public void AddForce(float x, float y, float z)
        {
            AddForce(new Vector3(x, y, z), ForceMode.Force);
        }

        extern public void AddRelativeForce(Vector3 force, [DefaultValue("ForceMode.Force")] ForceMode mode);

        [ExcludeFromDocs]
        public void AddRelativeForce(Vector3 force)
        {
            AddRelativeForce(force, ForceMode.Force);
        }

        public void AddRelativeForce(float x, float y, float z, [DefaultValue("ForceMode.Force")] ForceMode mode) { AddRelativeForce(new Vector3(x, y, z), mode); }

        [ExcludeFromDocs]
        public void AddRelativeForce(float x, float y, float z)
        {
            AddRelativeForce(new Vector3(x, y, z), ForceMode.Force);
        }

        extern public void AddTorque(Vector3 torque, [DefaultValue("ForceMode.Force")] ForceMode mode);

        [ExcludeFromDocs]
        public void AddTorque(Vector3 torque)
        {
            AddTorque(torque, ForceMode.Force);
        }

        public void AddTorque(float x, float y, float z, [DefaultValue("ForceMode.Force")] ForceMode mode) { AddTorque(new Vector3(x, y, z), mode); }

        [ExcludeFromDocs]
        public void AddTorque(float x, float y, float z)
        {
            AddTorque(new Vector3(x, y, z), ForceMode.Force);
        }

        extern public void AddRelativeTorque(Vector3 torque, [DefaultValue("ForceMode.Force")] ForceMode mode);

        [ExcludeFromDocs]
        public void AddRelativeTorque(Vector3 torque)
        {
            AddRelativeTorque(torque, ForceMode.Force);
        }

        public void AddRelativeTorque(float x, float y, float z, [DefaultValue("ForceMode.Force")] ForceMode mode) { AddRelativeTorque(new Vector3(x, y, z), mode); }

        [ExcludeFromDocs]
        public void AddRelativeTorque(float x, float y, float z)
        {
            AddRelativeTorque(x, y, z, ForceMode.Force);
        }

        extern public void AddForceAtPosition(Vector3 force, Vector3 position, [DefaultValue("ForceMode.Force")] ForceMode mode);

        [ExcludeFromDocs]
        public void AddForceAtPosition(Vector3 force, Vector3 position)
        {
            AddForceAtPosition(force, position, ForceMode.Force);
        }

        extern public void AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius, [DefaultValue("0.0f")] float upwardsModifier, [DefaultValue("ForceMode.Force)")] ForceMode mode);

        [ExcludeFromDocs]
        public void AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius, float upwardsModifier)
        {
            AddExplosionForce(explosionForce, explosionPosition, explosionRadius, upwardsModifier, ForceMode.Force);
        }

        [ExcludeFromDocs]
        public void AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius)
        {
            AddExplosionForce(explosionForce, explosionPosition, explosionRadius, 0.0f, ForceMode.Force);
        }

        [NativeName("ClosestPointOnBounds")]
        extern private void Internal_ClosestPointOnBounds(Vector3 point, ref Vector3 outPos, ref float distance);

        public Vector3 ClosestPointOnBounds(Vector3 position)
        {
            float dist = 0f;
            Vector3 outpos = Vector3.zero;
            Internal_ClosestPointOnBounds(position, ref outpos, ref dist);
            return outpos;
        }

        extern private RaycastHit SweepTest(Vector3 direction, float maxDistance, QueryTriggerInteraction queryTriggerInteraction, ref bool hasHit);

        public bool SweepTest(Vector3 direction, out RaycastHit hitInfo, [DefaultValue("Mathf.Infinity")] float maxDistance, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction)
        {
            float dirLength = direction.magnitude;

            if (dirLength > float.Epsilon)
            {
                Vector3 normalizedDirection = direction / dirLength;
                bool hasHit = false;
                hitInfo = SweepTest(normalizedDirection, maxDistance, queryTriggerInteraction, ref hasHit);
                return hasHit;
            }
            else
            {
                hitInfo = new RaycastHit();
                return false;
            }
        }

        [ExcludeFromDocs]
        public bool SweepTest(Vector3 direction, out RaycastHit hitInfo, float maxDistance)
        {
            return SweepTest(direction, out hitInfo, maxDistance, QueryTriggerInteraction.UseGlobal);
        }

        [ExcludeFromDocs]
        public bool SweepTest(Vector3 direction, out RaycastHit hitInfo)
        {
            return SweepTest(direction, out hitInfo, Mathf.Infinity, QueryTriggerInteraction.UseGlobal);
        }

        [NativeName("SweepTestAll")]
        extern private RaycastHit[] Internal_SweepTestAll(Vector3 direction, float maxDistance, QueryTriggerInteraction queryTriggerInteraction);

        public RaycastHit[] SweepTestAll(Vector3 direction, [DefaultValue("Mathf.Infinity")] float maxDistance, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction)
        {
            float dirLength = direction.magnitude;
            if (dirLength > float.Epsilon)
            {
                Vector3 normalizedDirection = direction / dirLength;
                return Internal_SweepTestAll(normalizedDirection, maxDistance, queryTriggerInteraction);
            }
            else
            {
                return new RaycastHit[0];
            }
        }

        [ExcludeFromDocs]
        public RaycastHit[] SweepTestAll(Vector3 direction, float maxDistance)
        {
            return SweepTestAll(direction, maxDistance, QueryTriggerInteraction.UseGlobal);
        }

        [ExcludeFromDocs]
        public RaycastHit[] SweepTestAll(Vector3 direction)
        {
            return SweepTestAll(direction, Mathf.Infinity, QueryTriggerInteraction.UseGlobal);
        }
    }
}

显然,这个代码还是很长,那么我们进行一些提炼,只解析属于刚体代码中的必备要素:这里的必备要素就是指假如我想简单重现一个刚体,我所需要的内容。

// 位置和旋转 - 刚体在空间中的基本状态
Vector3 position;
Quaternion rotation;

// 线性和角速度 - 描述运动状态
Vector3 linearVelocity;
Vector3 angularVelocity;

// 质量 - 影响物体对力的响应
float mass;

// 阻尼 - 模拟空气阻力等环境因素
float linearDamping;
float angularDamping;

// 是否受重力影响
bool useGravity;

// 是否为运动学刚体(由用户控制而非物理系统)
bool isKinematic;

这个是一个刚体所必需的属性变量:位置与旋转,线性速度和角速度,质量,阻尼以及两个布尔变量判断是否受重力影响以及是否为运动学刚体。

什么是运动学刚体?

// 碰撞检测模式 - 决定如何检测碰撞
CollisionDetectionMode collisionDetectionMode;

// 是否检测碰撞
bool detectCollisions;

一个刚体的碰撞模式也是必需的,具体有哪些见之前CollisionDetectionMode.cs。

// 质心位置 - 影响旋转行为
Vector3 centerOfMass;

// 惯性张量 - 描述物体在不同轴向上的旋转阻力
Vector3 inertiaTensor;

一个刚体必须要考虑质心位置惯性张量。

// 添加力 - 基本的力应用方法
void AddForce(Vector3 force, ForceMode mode);

// 添加扭矩 - 使物体旋转的力
void AddTorque(Vector3 torque, ForceMode mode);

力的应用方法,详见ForceMode.cs。

// 约束枚举 - 限制特定方向的移动或旋转
enum RigidbodyConstraints
{
    None = 0,
    FreezePositionX = 1 << 1,
    FreezePositionY = 1 << 2,
    FreezePositionZ = 1 << 3,
    FreezeRotationX = 1 << 4,
    FreezeRotationY = 1 << 5,
    FreezeRotationZ = 1 << 6,
    FreezePosition = FreezePositionX | FreezePositionY | FreezePositionZ,
    FreezeRotation = FreezeRotationX | FreezeRotationY | FreezeRotationZ,
    FreezeAll = FreezePosition | FreezeRotation
}

// 约束属性
RigidbodyConstraints constraints;

约束系统,限制刚体某个轴或者某个方向的旋转。

// 睡眠阈值 - 当运动低于此值时物体进入睡眠状态
float sleepThreshold;

// 睡眠控制方法
void Sleep();
bool IsSleeping();
void WakeUp();

睡眠系统,当物体满足判断条件时进入睡眠状态。

// 直接设置位置和旋转的方法(考虑物理约束)
void MovePosition(Vector3 position);
void MoveRotation(Quaternion rotation);

最后就是我们的控制刚体运动方法

现在让我们展开这些部分的具体实现看看。

// 位置和旋转
extern public Vector3 position { get; set; }
extern public Vector3 rotation { get; set; }

// 线性和角速度
extern public Vector3 linearVelocity { get; set; }
extern public Vector3 angularVelocity { get; set; }

// 质量
extern public float mass { get; set; }

// 阻尼
extern public float linearDamping { get; set; }
extern public float angularDamping { get; set; }

// 重力和运动学设置
extern public bool useGravity { get; set; }
extern public bool isKinematic { get; set; }

// 速度限制
extern public float maxLinearVelocity { get; set; }
extern public float maxAngularVelocity { get; set; }

// 穿透修正速度上限
extern public float maxDepenetrationVelocity { get; set; }

在这里我们需要先声明一下extern具体引用的外部文件是哪个外部文件:

[NativeHeader("Modules/Physics/Rigidbody.h")]

NativeHeader的作用就是指定包含原生函数声明的C++文件,这是一种允许C#脚本去调用C++脚本内容的方法。 

基本的属性,这里应该没有很难理解的部分,角速度就是用来规定旋转速度的,而角速度阻尼则是用来逐步减缓旋转的。穿透修正速度上限用于限制解决物体相互穿透时的修正速度,防止物体弹飞。

然后是我们的碰撞检测:

// 碰撞检测模式
extern public CollisionDetectionMode collisionDetectionMode { get; set; }

// 是否检测碰撞
extern public bool detectCollisions { get; set; }

// 碰撞层级过滤
extern public LayerMask excludeLayers { get; set; }
extern public LayerMask includeLayers { get; set; }

// 碰撞扫描测试
public bool SweepTest(Vector3 direction, out RaycastHit hitInfo, float maxDistance, QueryTriggerInteraction queryTriggerInteraction)
{
    float dirLength = direction.magnitude;

    if (dirLength > float.Epsilon)
    {
        Vector3 normalizedDirection = direction / dirLength;
        bool hasHit = false;
        hitInfo = SweepTest(normalizedDirection, maxDistance, queryTriggerInteraction, ref hasHit);
        return hasHit;
    }
    else
    {
        hitInfo = new RaycastHit();
        return false;
    }
}

用extern关键字修饰表明大多数函数都是引用自文件外部,有三个参数:碰撞检测模式,是否检车碰撞以及碰撞层级过滤。然后是一个碰撞的扫描测试,我们检查给定的方向向量大小是否合理,如果有效(大于极小值(Epsilon))我们获取单位方向向量并用这个单位方向向量作为参数调用这个函数的重载版本获取碰撞信息,最后返回是否有碰撞;否则我们生成一个空的射线碰撞给到碰撞信息并返回false。

总的来说就是实现了一个物体形状的射线检测,判断这个物体沿着方向向量移动时会不会有碰撞发生。

// 质心设置
extern public bool automaticCenterOfMass { get; set; }
extern public Vector3 centerOfMass { get; set; }
extern public Vector3 worldCenterOfMass { get; }
extern public void ResetCenterOfMass();

// 惯性张量
extern public bool automaticInertiaTensor { get; set; }
extern public Vector3 inertiaTensor { get; set; }
extern public Quaternion inertiaTensorRotation { get; set; }
extern internal Matrix4x4 worldInertiaTensorMatrix { get; }
extern public void ResetInertiaTensor();

这里的质量属性可以看到是清一色的extern,也就是都是引用自外部的内容,大体上的构成如图,这里我们解释下这两个属性的作用与可能作用的场景。

一言以蔽之,质心位置最大的特点是我们任何外力作用在质心上都只会产生平移的效果,而惯性张量则是用来描述刚体实现旋转时的抵抗程度。

力的应用方式:

// 基本力
extern public void AddForce(Vector3 force, ForceMode mode);

// 相对力(基于物体自身坐标系)
extern public void AddRelativeForce(Vector3 force, ForceMode mode);

// 扭矩
extern public void AddTorque(Vector3 torque, ForceMode mode);

// 相对扭矩
extern public void AddRelativeTorque(Vector3 torque, ForceMode mode);

// 在指定位置添加力
extern public void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode);

// 爆炸力
extern public void AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius, float upwardsModifier, ForceMode mode);

// 获取累积的力和扭矩
extern public Vector3 GetAccumulatedForce(float step);
extern public Vector3 GetAccumulatedTorque(float step);

这个没什么好说的,具体的作用已经注释。

// 约束枚举定义
public enum RigidbodyConstraints
{
    None = 0,
    FreezePositionX = 1 << 1,
    FreezePositionY = 1 << 2,
    FreezePositionZ = 1 << 3,
    FreezeRotationX = 1 << 4,
    FreezeRotationY = 1 << 5,
    FreezeRotationZ = 1 << 6,
    FreezePosition = FreezePositionX | FreezePositionY | FreezePositionZ,
    FreezeRotation = FreezeRotationX | FreezeRotationY | FreezeRotationZ,
    FreezeAll = FreezePosition | FreezeRotation
}

// 约束属性
extern public RigidbodyConstraints constraints { get; set; }

// 冻结旋转快捷方式
public bool freezeRotation
{
    get => constraints.HasFlag(RigidbodyConstraints.FreezeRotation);
    set
    {
        if (value)
            constraints |= RigidbodyConstraints.FreezeRotation;
        else
            constraints &= RigidbodyConstraints.FreezePosition;
    }
}

约束系统,用来限制刚体某个方向或者某个轴的旋转,首先是约束的枚举我们可以看到涉及到了位运算,Pos上的xyz以及Rotation上的xyz分别用位左移运算符来获取一个二进制位,然后通过或运算得到结果。我们有一个返回约束枚举的相关函数,然后根据这个函数的结果来实时地修改约束。

// 睡眠阈值
extern public float sleepThreshold { get; set; }

// 睡眠控制方法
extern public void Sleep();
extern public bool IsSleeping();
extern public void WakeUp();

睡眠系统,也是基本引用自外部的函数:

// 直接设置位置和旋转(考虑物理约束)
extern public void MovePosition(Vector3 position);
extern public void MoveRotation(Quaternion rot);
extern public void Move(Vector3 position, Quaternion rotation);

// 发布变换到物理引擎
extern public void PublishTransform();

这个是运动控制刚体的方法,所谓的运动控制刚体其实就是指我们把刚体的控制权完全交给代码而不是去考虑物理的计算,这样我们可以利用代码更灵活地使用刚体,但是缺点是可能出现不符合基本物理规律的情况。

以上就是我们的刚体的基本组成部分。

Collider.bindings.cs

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine.Bindings;

namespace UnityEngine
{
    [NativeHeader("Modules/Physics/Collider.h")]
    public partial class Collider : Component
    {
        extern public bool enabled { get; set; }
        extern public Rigidbody attachedRigidbody { [NativeMethod("GetRigidbody")] get; }
        extern public ArticulationBody attachedArticulationBody { [NativeMethod("GetArticulationBody")] get; }
        extern public bool isTrigger { get; set; }
        extern public float contactOffset { get; set; }
        extern public Vector3 ClosestPoint(Vector3 position);
        extern public Bounds bounds { get; }
        extern public bool hasModifiableContacts { get; set; }
        extern public bool providesContacts { get; set; }
        extern public int layerOverridePriority { get; set; }
        extern public LayerMask excludeLayers { get; set; }
        extern public LayerMask includeLayers { get; set; }
        extern public LowLevelPhysics.GeometryHolder GeometryHolder { get; }

        public T GetGeometry<T>() where T : struct, LowLevelPhysics.IGeometry
        {
            return GeometryHolder.As<T>();
        }

        [NativeMethod("Material")]
        extern public PhysicsMaterial sharedMaterial { get; set; }
        extern public PhysicsMaterial material
        {
            [NativeMethod("GetClonedMaterial")]
            get;
            [NativeMethod("SetMaterial")]
            set;
        }

        extern private RaycastHit Raycast(Ray ray, float maxDistance, ref bool hasHit);

        public bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance)
        {
            bool hasHit = false;
            hitInfo = Raycast(ray, maxDistance, ref hasHit);
            return hasHit;
        }

        [NativeName("ClosestPointOnBounds")]
        extern private void Internal_ClosestPointOnBounds(Vector3 point, ref Vector3 outPos, ref float distance);

        public Vector3 ClosestPointOnBounds(Vector3 position)
        {
            float dist = 0f;
            Vector3 outpos = Vector3.zero;
            Internal_ClosestPointOnBounds(position, ref outpos, ref dist);
            return outpos;
        }
    }
}

然后当然是我们的碰撞体乃至碰撞检测部分了,碰撞体总的来说有很多种,Collider则是其中的基类,看懂基类之后后续的各种继承的类就更好理解。

        extern public bool enabled { get; set; }
        extern public Rigidbody attachedRigidbody { [NativeMethod("GetRigidbody")] get; }
        extern public ArticulationBody attachedArticulationBody { [NativeMethod("GetArticulationBody")] get; }
        extern public bool isTrigger { get; set; }
        extern public float contactOffset { get; set; }
        extern public Vector3 ClosestPoint(Vector3 position);
        extern public Bounds bounds { get; }
        extern public bool hasModifiableContacts { get; set; }
        extern public bool providesContacts { get; set; }
        extern public int layerOverridePriority { get; set; }
        extern public LayerMask excludeLayers { get; set; }
        extern public LayerMask includeLayers { get; set; }
        extern public LowLevelPhysics.GeometryHolder GeometryHolder { get; }
        public T GetGeometry<T>() where T : struct, LowLevelPhysics.IGeometry
        {
            return GeometryHolder.As<T>();
        }

这是碰撞体基类脚本中的一系列变量,包括是否开启碰撞,碰撞体关联的刚体和关节体,是否为触发器,碰撞偏差,碰撞发生时最接近指定点的点,碰撞体的包围盒,是否是可修正的接触点,是否提供接触点,具有优先级的层级,排除碰撞的层级,包含碰撞的层级,一个低物理等级的几何持有者,以及一个带有约束的泛型访问底层几何数据的方法。

        [NativeMethod("Material")]
        extern public PhysicsMaterial sharedMaterial { get; set; }
        extern public PhysicsMaterial material
        {
            [NativeMethod("GetClonedMaterial")]
            get;
            [NativeMethod("SetMaterial")]
            set;
        }

定义了两种材质:

简单的说就是一种材质是共享的而另一种则是不对其他碰撞体产生影响的。

        extern private RaycastHit Raycast(Ray ray, float maxDistance, ref bool hasHit);

        public bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance)
        {
            bool hasHit = false;
            hitInfo = Raycast(ray, maxDistance, ref hasHit);
            return hasHit;
        }

射线检测部分,返回最大距离内是否有物体。

[NativeName("ClosestPointOnBounds")]
        extern private void Internal_ClosestPointOnBounds(Vector3 point, ref Vector3 outPos, ref float distance);

        public Vector3 ClosestPointOnBounds(Vector3 position)
        {
            float dist = 0f;
            Vector3 outpos = Vector3.zero;
            Internal_ClosestPointOnBounds(position, ref outpos, ref dist);
            return outpos;
        }

计算给定的点到包围盒最近的点。

看完碰撞体的基类我们来看看继承这个基类的种种其他碰撞体:

using UnityEngine.Bindings;

namespace UnityEngine
{
    [RequireComponent(typeof(Transform))]
    [NativeHeader("Modules/Physics/BoxCollider.h")]
    public partial class BoxCollider : Collider
    {
        extern public Vector3 center { get; set; }
        extern public Vector3 size { get; set; }
    }
}
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine.Bindings;

namespace UnityEngine
{
    [RequireComponent(typeof(Transform))]
    [NativeHeader("Modules/Physics/CapsuleCollider.h")]
    public class CapsuleCollider : Collider
    {
        extern public Vector3 center { get; set; }
        extern public float radius { get; set; }
        extern public float height { get; set; }
        extern public int direction { get; set; }
    }
}
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine.Bindings;

namespace UnityEngine
{
    [RequireComponent(typeof(Transform))]
    [NativeHeader("Modules/Physics/SphereCollider.h")]
    public class SphereCollider : Collider
    {
        extern public Vector3 center { get; set; }
        extern public float radius { get; set; }
    }
}

 你惊讶地发现,盒形碰撞体,胶囊形碰撞体以及球形碰撞体居然都只是简单地定义了一下基本参数。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using UnityEngine.Bindings;

namespace UnityEngine
{
    [Flags]
    public enum MeshColliderCookingOptions
    {
        None,
        [Obsolete("No longer used because the problem this was trying to solve is gone since Unity 2018.3", true)] InflateConvexMesh = 1 << 0,
        CookForFasterSimulation = 1 << 1,
        EnableMeshCleaning = 1 << 2,
        WeldColocatedVertices = 1 << 3,
        UseFastMidphase = 1 << 4
    }

    [RequireComponent(typeof(Transform))]
    [NativeHeader("Modules/Physics/MeshCollider.h")]
    [NativeHeader("Runtime/Graphics/Mesh/Mesh.h")]
    public partial class MeshCollider : Collider
    {
        extern public Mesh sharedMesh { get; set; }
        extern public bool convex { get; set; }

        extern public MeshColliderCookingOptions cookingOptions { get; set; }
    }
}

网格碰撞体则是要麻烦一点,他会多一个CookingOptions的枚举,其中的Flags代表的是可以组合使用的意思。

然后是网格碰撞体中的一些参数:

至此我们的碰撞检测就完成了,相信你也发现了,比起大多数内容由自己定义的刚体,Unity的碰撞检测的源码就是基本都在调底层物理系统的API,Unity基本只负责定义基本的属性然后通过底层物理API来计算得到结果后使用,因此假如我们想真正深入地了解Unity整个物理系统的全貌,我们是不能不深入底层的。如果在这里继续展开的话显然篇幅有点过长了,后续我会继续学习的。

Joint.bindings.cs

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine.Bindings;

namespace UnityEngine
{
    [NativeHeader("Modules/Physics/Joint.h")]
    [NativeClass("Unity::Joint")]
    public class Joint : Component
    {
        extern public Rigidbody connectedBody { get; set; }
        extern public ArticulationBody connectedArticulationBody { get; set; }
        extern public Vector3 axis { get; set; }
        extern public Vector3 anchor { get; set; }
        extern public Vector3 connectedAnchor { get; set; }
        extern public bool autoConfigureConnectedAnchor { get; set; }
        extern public float breakForce { get; set; }
        extern public float breakTorque { get; set; }
        extern public bool enableCollision { get; set; }
        extern public bool enablePreprocessing { get; set; }
        extern public float massScale { get; set; }
        extern public float connectedMassScale { get; set; }

        extern private void GetCurrentForces(ref Vector3 linearForce, ref Vector3 angularForce);

        public Vector3 currentForce
        {
            get
            {
                Vector3 force = Vector3.zero;
                Vector3 torque = Vector3.zero;
                GetCurrentForces(ref force, ref torque);
                return force;
            }
        }

        public Vector3 currentTorque
        {
            get
            {
                Vector3 force = Vector3.zero;
                Vector3 torque = Vector3.zero;
                GetCurrentForces(ref force, ref torque);
                return torque;
            }
        }

        extern internal Matrix4x4 GetLocalPoseMatrix(int bodyIndex);
    }
}

来到我们的关节系统,其实客观地说关节系统在Unity平时的开发中感觉运用得不算很多,但是它依然是我们涉及到物理系统就需要的部分。

可以看到关节的基类由一堆参数组成:

然后就是一系列关节系统的子类:

using UnityEngine.Bindings;

namespace UnityEngine
{
    [RequireComponent(typeof(Rigidbody))]
    [NativeHeader("Modules/Physics/CharacterJoint.h")]
    [NativeClass("Unity::CharacterJoint")]
    public partial class CharacterJoint : Joint
    {
        extern public Vector3 swingAxis { get; set; }
        extern public SoftJointLimitSpring twistLimitSpring { get; set; }
        extern public SoftJointLimitSpring swingLimitSpring { get; set; }
        extern public SoftJointLimit lowTwistLimit { get; set; }
        extern public SoftJointLimit highTwistLimit { get; set; }
        extern public SoftJointLimit swing1Limit { get; set; }
        extern public SoftJointLimit swing2Limit { get; set; }
        extern public bool enableProjection { get; set; }
        extern public float projectionDistance { get; set; }
        extern public float projectionAngle { get; set; }
    }
}

参数含义如下:

这个类作为Joint的子类,是一种特殊的关节类型,主要用于角色骨骼系统的物理模拟。想象一下提线木偶,这个类就是用来描述连接木偶各个部分的关节的。

using UnityEngine.Bindings;

namespace UnityEngine
{
    [RequireComponent(typeof(Rigidbody))]
    [NativeHeader("Modules/Physics/FixedJoint.h")]
    [NativeClass("Unity::FixedJoint")]
    public class FixedJoint : Joint {}
}

然后是我们的固定关节,可以看到非常的朴素啊,什么也没有,毕竟你都是固定关节了,也不用再去费劲调整什么参数什么轴了。

using UnityEngine.Bindings;

namespace UnityEngine
{
    [RequireComponent(typeof(Rigidbody))]
    [NativeHeader("Modules/Physics/HingeJoint.h")]
    [NativeClass("Unity::HingeJoint")]
    public class HingeJoint : Joint
    {
        extern public JointMotor motor { get; set; }
        extern public JointLimits limits { get; set; }
        extern public JointSpring spring { get; set; }
        extern public bool useMotor { get; set; }
        extern public bool useLimits { get; set; }
        extern public bool extendedLimits { get; set; }
        extern public bool useSpring { get; set; }
        extern public float velocity { get; }
        extern public float angle { get; }
        extern public bool useAcceleration { get; set; }
    }
}

这段代码定义了Unity物理系统中的HingeJoint(铰链关节)类,它是一种模拟门铰链、轮轴等单轴旋转连接的物理组件。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine.Bindings;

namespace UnityEngine
{
    [RequireComponent(typeof(Rigidbody))]
    [NativeHeader("Modules/Physics/SpringJoint.h")]
    [NativeClass("Unity::SpringJoint")]
    public class SpringJoint : Joint
    {
        extern public float spring { get; set; }
        extern public float damper { get; set; }
        extern public float minDistance { get; set; }
        extern public float maxDistance { get; set; }
        extern public float tolerance { get; set; }
    }
}

这段代码定义了Unity物理系统中的SpringJoint(弹簧关节)类,它是一种模拟弹性连接的物理组件,可以将两个刚体以弹簧方式连接起来。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using System.ComponentModel;
using UnityEngine.Bindings;

namespace UnityEngine
{
    // Determines how to snap physics joints back to its constrained position when it drifts off too much. Note: PositionOnly is not supported anymore!
    // TODO: We should just move to a flag and remove this enum
    public enum JointProjectionMode
    {
        None = 0,
        PositionAndRotation = 1,

        // Snap Position only
        [EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("JointProjectionMode.PositionOnly is no longer supported", true)]
        PositionOnly = 2
    }

    public enum RotationDriveMode
    {
        XYAndZ = 0,
        Slerp = 1
    }

    public enum ConfigurableJointMotion
    {
        Locked = 0,
        Limited = 1,
        Free = 2
    }

    [RequireComponent(typeof(Rigidbody))]
    [NativeHeader("Modules/Physics/ConfigurableJoint.h")]
    [NativeClass("Unity::ConfigurableJoint")]
    public class ConfigurableJoint : Joint
    {
        extern public Vector3 secondaryAxis { get; set; }
        extern public ConfigurableJointMotion xMotion { get; set; }
        extern public ConfigurableJointMotion yMotion { get; set; }
        extern public ConfigurableJointMotion zMotion { get; set; }
        extern public ConfigurableJointMotion angularXMotion { get; set; }
        extern public ConfigurableJointMotion angularYMotion { get; set; }
        extern public ConfigurableJointMotion angularZMotion { get; set; }
        extern public SoftJointLimitSpring linearLimitSpring { get; set; }
        extern public SoftJointLimitSpring angularXLimitSpring { get; set; }
        extern public SoftJointLimitSpring angularYZLimitSpring { get; set; }
        extern public SoftJointLimit linearLimit { get; set; }
        extern public SoftJointLimit lowAngularXLimit { get; set; }
        extern public SoftJointLimit highAngularXLimit { get; set; }
        extern public SoftJointLimit angularYLimit { get; set; }
        extern public SoftJointLimit angularZLimit { get; set; }
        extern public Vector3 targetPosition { get; set; }
        extern public Vector3 targetVelocity { get; set; }
        extern public JointDrive xDrive { get; set; }
        extern public JointDrive yDrive { get; set; }
        extern public JointDrive zDrive { get; set; }
        extern public Quaternion targetRotation { get; set; }
        extern public Vector3 targetAngularVelocity { get; set; }
        extern public RotationDriveMode rotationDriveMode { get; set; }
        extern public JointDrive angularXDrive { get; set; }
        extern public JointDrive angularYZDrive { get; set; }
        extern public JointDrive slerpDrive { get; set; }
        extern public JointProjectionMode projectionMode { get; set; }
        extern public float projectionDistance { get; set; }
        extern public float projectionAngle { get; set; }
        extern public bool configuredInWorldSpace { get; set; }
        extern public bool swapBodies { get; set; }
    }
}

这段代码定义了Unity物理系统中最灵活、功能最强大的关节类型 - ConfigurableJoint(可配置关节)。它提供了极其全面的控制选项,可以模拟几乎任何类型的物理连接,换句话说,用这个关节可以模拟出以上所有类型的关节类型。

首先是三个枚举:JointProjectionMode,RotationDriveMode以及ConfigurableJointMotion。

然后是大量的参数,参数多到我有点懒得去写了。

PhysicsMaterial.bindings.cs

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using UnityEngine.Bindings;

namespace UnityEngine
{
    public enum PhysicsMaterialCombine
    {
        Average = 0,
        Multiply,
        Minimum,
        Maximum
    }

    [NativeHeader("Modules/Physics/PhysicsMaterial.h")]
    public class PhysicsMaterial : UnityEngine.Object
    {
        public PhysicsMaterial() { Internal_CreateDynamicsMaterial(this, "DynamicMaterial"); }
        public PhysicsMaterial(string name) { Internal_CreateDynamicsMaterial(this, name); }
        extern private static void Internal_CreateDynamicsMaterial([Writable] PhysicsMaterial mat, string name);

        extern public float bounciness { get; set; }
        extern public float dynamicFriction { get; set; }
        extern public float staticFriction { get; set; }
        extern public PhysicsMaterialCombine frictionCombine { get; set; }
        extern public PhysicsMaterialCombine bounceCombine { get; set; }
    }
}

这个脚本用来定义物体的物理材质,主要包含的属性有摩擦力等。

首先是一个枚举值,用来决定当两个物理材质混合时具体的混合形式,如平均,相乘,取最小值和取最大值。

然后是我们定义的属性,有弹力,静摩擦力以及动摩擦力,最后是两个组合策略,分别定义弹力和摩擦力的组合方式。

CharacterController.bindings.cs

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System.Runtime.InteropServices;
using UnityEngine.Bindings;
using UnityEngine.Scripting;

namespace UnityEngine
{
    // CollisionFlags is a bitmask returned by CharacterController.Move.
    public enum CollisionFlags
    {
        None = 0,
        Sides = 1,
        Above = 2,
        Below = 4,
        CollidedSides = 1,
        CollidedAbove = 2,
        CollidedBelow = 4
    }

    // ControllerColliderHit is used by CharacterController.OnControllerColliderHit to give detailed information about the collision and how to deal with it.
    [StructLayout(LayoutKind.Sequential)]
    [RequiredByNativeCode]
    public partial class ControllerColliderHit
    {
        internal CharacterController m_Controller;
        internal Collider m_Collider;
        internal Vector3 m_Point;
        internal Vector3 m_Normal;
        internal Vector3 m_MoveDirection;
        internal float m_MoveLength;
        internal int m_Push;

        public CharacterController controller { get { return m_Controller; } }
        public Collider collider { get { return m_Collider; } }
        public Rigidbody rigidbody { get { return m_Collider.attachedRigidbody; } }
        public GameObject gameObject { get { return m_Collider.gameObject; } }
        public Transform transform { get { return m_Collider.transform; } }
        public Vector3 point { get { return m_Point; } }
        public Vector3 normal { get { return m_Normal; } }
        public Vector3 moveDirection { get { return m_MoveDirection; } }
        public float moveLength { get { return m_MoveLength; } }
        private bool push { get { return m_Push != 0; } set { m_Push = value ? 1 : 0; } }
    }

    [NativeHeader("Modules/Physics/CharacterController.h")]
    public class CharacterController : Collider
    {
        extern public bool SimpleMove(Vector3 speed);
        extern public CollisionFlags Move(Vector3 motion);
        extern public Vector3 velocity { get; }
        extern public bool isGrounded { [NativeName("IsGrounded")] get; }
        extern public CollisionFlags collisionFlags { get; }
        extern public float radius { get; set; }
        extern public float height { get; set; }
        extern public Vector3 center { get; set; }
        extern public float slopeLimit { get; set; }
        extern public float stepOffset { get; set; }
        extern public float skinWidth { get; set; }
        extern public float minMoveDistance { get; set; }
        extern public bool detectCollisions { get; set; }
        extern public bool enableOverlapRecovery { get; set; }
        extern internal bool isSupported { get; }
    }
}

这是一个专门为角色定义的碰撞体控制器,它不一定遵守其他物理组件的规则而是很多东西自行判断。

在ControllerColliderHit类中的参数中分为两部分,一部分是internal关键字修饰的只能在该程序集中使用的变量:首先是发生碰撞的面,有无(None),上面,底面,边;然后是控制器的碰撞类,这个类中定义了除了发生碰撞的控制器本身以及碰撞类本身,还定义了碰撞点,移动方向,法线(谁的法线),移动的长度,以及一个Push(这是干嘛的,返回产生碰撞后是否有反作用力吗?)。

然后是从C#中可以取到的一系列变量,这一部分变量用来获取Internal变量供外部函数使用。

然后在CharacterController类中(可以看到继承自Collider)我们定义了一系列跟角色相关的参数,分别代表:是否应用重力的移动方式,速度,是否在地面上,碰撞面,半径,高度,中心位置,坡度限制,允许跨越的最大台阶,碰撞体表面偏差,最小移动距离,是否检测碰撞,是否解决重叠问题以及是否支持角色控制器。

PhysicsScene.bindings.cs

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using UnityEngine.Bindings;
using System.Runtime.InteropServices;
using UnityEngine.Internal;
using UnityEngine.SceneManagement;

namespace UnityEngine
{
    [NativeHeader("Modules/Physics/PhysicsQuery.h")]
    [NativeHeader("Modules/Physics/Public/PhysicsSceneHandle.h")]
    [StructLayout(LayoutKind.Sequential)]
    public partial struct PhysicsScene : IEquatable<PhysicsScene>
    {
        private int m_Handle;

        public override string ToString() { return string.Format("({0})", m_Handle); }
        public static bool operator ==(PhysicsScene lhs, PhysicsScene rhs) { return lhs.m_Handle == rhs.m_Handle; }
        public static bool operator !=(PhysicsScene lhs, PhysicsScene rhs) { return lhs.m_Handle != rhs.m_Handle; }
        public override int GetHashCode() { return m_Handle; }
        public override bool Equals(object other)
        {
            if (!(other is PhysicsScene))
                return false;

            PhysicsScene rhs = (PhysicsScene)other;
            return m_Handle == rhs.m_Handle;
        }

        public bool Equals(PhysicsScene other)
        {
            return m_Handle == other.m_Handle;
        }

        public bool IsValid() { return IsValid_Internal(this); }
        [StaticAccessor("GetPhysicsManager()", StaticAccessorType.Dot)]
        [NativeMethod("IsPhysicsSceneValid")]
        extern private static bool IsValid_Internal(PhysicsScene physicsScene);

        public bool IsEmpty()
        {
            if (IsValid())
                return IsEmpty_Internal(this);

            throw new InvalidOperationException("Cannot check if physics scene is empty as it is invalid.");
        }

        [StaticAccessor("GetPhysicsManager()", StaticAccessorType.Dot)]
        [NativeMethod("IsPhysicsWorldEmpty")]
        extern private static bool IsEmpty_Internal(PhysicsScene physicsScene);

        // Perform a manual simulation step.
        public void Simulate(float step)
        {
            if (IsValid())
            {
                // Only check auto-simulation if simulating the default physics scene.
                if (this == Physics.defaultPhysicsScene && Physics.simulationMode != SimulationMode.Script)
                {
                    Debug.LogWarning("PhysicsScene.Simulate(...) was called but simulation mode is not set to Script. You should set simulation mode to Script first before calling this function therefore the simulation was not run.");
                    return;
                }

                Physics.Simulate_Internal(this, step, SimulationStage.All, SimulationOption.All);
                return;
            }

            throw new InvalidOperationException("Cannot simulate the physics scene as it is invalid.");
        }

        public void RunSimulationStages(float step, SimulationStage stages, [DefaultValue("SimulationOption.All")] SimulationOption options = SimulationOption.All)
        {
            if (!IsValid())
                throw new InvalidOperationException("Cannot simulate the physics scene as it is invalid.");

            // Only check auto-simulation if simulating the default physics scene.
            if (this == Physics.defaultPhysicsScene && Physics.simulationMode != SimulationMode.Script)
            {
                Debug.LogWarning("PhysicsScene.Simulate(...) was called but simulation mode is not set to Script. You should set simulation mode to Script first before calling this function therefore the simulation was not run.");
                return;
            }

            Physics.Simulate_Internal(this, step, stages, options);
        }

        public void InterpolateBodies()
        {
            if (!IsValid())
                throw new InvalidOperationException("Cannot interpolate the physics scene as it is invalid.");

            if (this == Physics.defaultPhysicsScene)
            {
                Debug.LogWarning("PhysicsScene.InterpolateBodies() was called on the default Physics Scene. This is done automatically and the call will be ignored");
                return;
            }

            Physics.InterpolateBodies_Internal(this);
        }

        public void ResetInterpolationPoses()
        {
            if (!IsValid())
                throw new InvalidOperationException("Cannot reset poses of the physics scene as it is invalid.");

            if (this == Physics.defaultPhysicsScene)
            {
                Debug.LogWarning("PhysicsScene.ResetInterpolationPoses() was called on the default Physics Scene. This is done automatically and the call will be ignored");
                return;
            }

            Physics.ResetInterpolationPoses_Internal(this);
        }

        // Hit Test.
        public bool Raycast(Vector3 origin, Vector3 direction, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("Physics.DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            float dirLength = direction.magnitude;
            if (dirLength > float.Epsilon)
            {
                Vector3 normalizedDirection = direction / dirLength;
                Ray ray = new Ray(origin, normalizedDirection);
                return Internal_RaycastTest(this, ray, maxDistance, layerMask, queryTriggerInteraction);
            }

            return false;
        }

        [FreeFunction("Physics::RaycastTest")]
        extern private static bool Internal_RaycastTest(PhysicsScene physicsScene, Ray ray, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

        // Single hit.
        public bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("Physics.DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            hitInfo = new RaycastHit();

            float dirLength = direction.magnitude;
            if (dirLength > float.Epsilon)
            {
                Vector3 normalizedDirection = direction / dirLength;
                Ray ray = new Ray(origin, normalizedDirection);

                return Internal_Raycast(this, ray, maxDistance, ref hitInfo, layerMask, queryTriggerInteraction);
            }
            else
                return false;
        }

        [FreeFunction("Physics::Raycast")]
        extern private static bool Internal_Raycast(PhysicsScene physicsScene, Ray ray, float maxDistance, ref RaycastHit hit, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

        // Multiple hits.
        public int Raycast(Vector3 origin, Vector3 direction, RaycastHit[] raycastHits, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("Physics.DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            float dirLength = direction.magnitude;

            if (dirLength > float.Epsilon)
            {
                Ray ray = new Ray(origin, direction.normalized);
                return Internal_RaycastNonAlloc(this, ray, raycastHits, maxDistance, layerMask, queryTriggerInteraction);
            }

            return 0;
        }

        [FreeFunction("Physics::RaycastNonAlloc")]
        extern private static int Internal_RaycastNonAlloc(PhysicsScene physicsScene, Ray ray, RaycastHit[] raycastHits, float maxDistance, int mask, QueryTriggerInteraction queryTriggerInteraction);

        [FreeFunction("Physics::CapsuleCast")]
        extern private static bool Query_CapsuleCast(PhysicsScene physicsScene, Vector3 point1, Vector3 point2, float radius, Vector3 direction, float maxDistance, ref RaycastHit hitInfo, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

        private static bool Internal_CapsuleCast(PhysicsScene physicsScene, Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction)
        {
            float dirLength = direction.magnitude;
            hitInfo = new RaycastHit();
            if (dirLength > float.Epsilon)
            {
                Vector3 normalizedDirection = direction / dirLength;

                return Query_CapsuleCast(physicsScene, point1, point2, radius, normalizedDirection, maxDistance, ref hitInfo, layerMask, queryTriggerInteraction);
            }
            else
                return false;
        }

        public bool CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            return Internal_CapsuleCast(this, point1, point2, radius, direction, out hitInfo, maxDistance, layerMask, queryTriggerInteraction);
        }

        [FreeFunction("Physics::CapsuleCastNonAlloc")]
        extern private static int Internal_CapsuleCastNonAlloc(PhysicsScene physicsScene, Vector3 p0, Vector3 p1, float radius, Vector3 direction, RaycastHit[] raycastHits, float maxDistance, int mask, QueryTriggerInteraction queryTriggerInteraction);

        public int CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, RaycastHit[] results, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            float dirLength = direction.magnitude;

            if (dirLength > float.Epsilon)
            {
                return Internal_CapsuleCastNonAlloc(this, point1, point2, radius, direction, results, maxDistance, layerMask, queryTriggerInteraction);
            }
            else
            {
                return 0;
            }
        }

        [FreeFunction("Physics::OverlapCapsuleNonAlloc")]
        extern private static int OverlapCapsuleNonAlloc_Internal(PhysicsScene physicsScene, Vector3 point0, Vector3 point1, float radius, [Unmarshalled] Collider[] results, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

        public int OverlapCapsule(Vector3 point0, Vector3 point1, float radius, Collider[] results, [DefaultValue("AllLayers")] int layerMask = Physics.AllLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            return OverlapCapsuleNonAlloc_Internal(this, point0, point1, radius, results, layerMask, queryTriggerInteraction);
        }

        [FreeFunction("Physics::SphereCast")]
        extern private static bool Query_SphereCast(PhysicsScene physicsScene, Vector3 origin, float radius, Vector3 direction, float maxDistance, ref RaycastHit hitInfo, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

        private static bool Internal_SphereCast(PhysicsScene physicsScene, Vector3 origin, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction)
        {
            float dirLength = direction.magnitude;
            hitInfo = new RaycastHit();
            if (dirLength > float.Epsilon)
            {
                Vector3 normalizedDirection = direction / dirLength;

                return Query_SphereCast(physicsScene, origin, radius, normalizedDirection, maxDistance, ref hitInfo, layerMask, queryTriggerInteraction);
            }
            else
                return false;
        }

        public bool SphereCast(Vector3 origin, float radius, Vector3 direction, out RaycastHit hitInfo, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            return Internal_SphereCast(this, origin, radius, direction, out hitInfo, maxDistance, layerMask, queryTriggerInteraction);
        }

        [FreeFunction("Physics::SphereCastNonAlloc")]
        extern private static int Internal_SphereCastNonAlloc(PhysicsScene physicsScene, Vector3 origin, float radius, Vector3 direction, RaycastHit[] raycastHits, float maxDistance, int mask, QueryTriggerInteraction queryTriggerInteraction);

        public int SphereCast(Vector3 origin, float radius, Vector3 direction, RaycastHit[] results, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            float dirLength = direction.magnitude;

            if (dirLength > float.Epsilon)
            {
                return Internal_SphereCastNonAlloc(this, origin, radius, direction, results, maxDistance, layerMask, queryTriggerInteraction);
            }
            else
            {
                return 0;
            }
        }

        [FreeFunction("Physics::OverlapSphereNonAlloc")]
        extern private static int OverlapSphereNonAlloc_Internal(PhysicsScene physicsScene, Vector3 position, float radius, [Unmarshalled] Collider[] results, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

        public int OverlapSphere(Vector3 position, float radius, Collider[] results, [DefaultValue("AllLayers")] int layerMask, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction)
        {
            return OverlapSphereNonAlloc_Internal(this, position, radius, results, layerMask, queryTriggerInteraction);
        }

        [FreeFunction("Physics::BoxCast")]
        extern static private bool Query_BoxCast(PhysicsScene physicsScene, Vector3 center, Vector3 halfExtents, Vector3 direction, Quaternion orientation, float maxDistance, ref RaycastHit outHit, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

        private static bool Internal_BoxCast(PhysicsScene physicsScene, Vector3 center, Vector3 halfExtents, Quaternion orientation, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction)
        {
            float dirLength = direction.magnitude;
            hitInfo = new RaycastHit();
            if (dirLength > float.Epsilon)
            {
                Vector3 normalizedDirection = direction / dirLength;

                return Query_BoxCast(physicsScene, center, halfExtents, normalizedDirection, orientation, maxDistance, ref hitInfo, layerMask, queryTriggerInteraction);
            }
            else
                return false;
        }

        public bool BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction, out RaycastHit hitInfo, [DefaultValue("Quaternion.identity")] Quaternion orientation, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            return Internal_BoxCast(this, center, halfExtents, orientation, direction, out hitInfo, maxDistance, layerMask, queryTriggerInteraction);
        }

        [ExcludeFromDocs]
        public bool BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction, out RaycastHit hitInfo)
        {
            return Internal_BoxCast(this, center, halfExtents, Quaternion.identity, direction, out hitInfo, Mathf.Infinity, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
        }

        [FreeFunction("Physics::OverlapBoxNonAlloc")]
        extern private static int OverlapBoxNonAlloc_Internal(PhysicsScene physicsScene, Vector3 center, Vector3 halfExtents, [Unmarshalled] Collider[] results, Quaternion orientation, int mask, QueryTriggerInteraction queryTriggerInteraction);

        public int OverlapBox(Vector3 center, Vector3 halfExtents, Collider[] results, [DefaultValue("Quaternion.identity")] Quaternion orientation, [DefaultValue("DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            return OverlapBoxNonAlloc_Internal(this, center, halfExtents, results, orientation, layerMask, queryTriggerInteraction);
        }

        [ExcludeFromDocs]
        public int OverlapBox(Vector3 center, Vector3 halfExtents, Collider[] results)
        {
            return OverlapBoxNonAlloc_Internal(this, center, halfExtents, results, Quaternion.identity, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
        }

        [FreeFunction("Physics::BoxCastNonAlloc")]
        private static extern int Internal_BoxCastNonAlloc(PhysicsScene physicsScene, Vector3 center, Vector3 halfExtents, Vector3 direction, RaycastHit[] raycastHits, Quaternion orientation, float maxDistance, int mask, QueryTriggerInteraction queryTriggerInteraction);

        public int BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction, RaycastHit[] results, [DefaultValue("Quaternion.identity")] Quaternion orientation, [DefaultValue("Mathf.Infinity")] float maxDistance = Mathf.Infinity, [DefaultValue("DefaultRaycastLayers")] int layerMask = Physics.DefaultRaycastLayers, [DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
        {
            float dirLength = direction.magnitude;

            if (dirLength > float.Epsilon)
            {
                return Internal_BoxCastNonAlloc(this, center, halfExtents, direction, results, orientation, maxDistance, layerMask, queryTriggerInteraction);
            }
            else
            {
                return 0;
            }
        }

        [ExcludeFromDocs]
        public int BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction, RaycastHit[] results)
        {
            return BoxCast(center, halfExtents, direction, results, Quaternion.identity, Mathf.Infinity, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);
        }
    }

    public static class PhysicsSceneExtensions
    {
        public static PhysicsScene GetPhysicsScene(this Scene scene)
        {
            if (!scene.IsValid())
                throw new ArgumentException("Cannot get physics scene; Unity scene is invalid.", "scene");

            PhysicsScene physicsScene = GetPhysicsScene_Internal(scene);
            if (physicsScene.IsValid())
                return physicsScene;

            throw new Exception("The physics scene associated with the Unity scene is invalid.");
        }

        [StaticAccessor("GetPhysicsManager()", StaticAccessorType.Dot)]
        [NativeMethod("GetPhysicsSceneFromUnityScene")]
        extern private static PhysicsScene GetPhysicsScene_Internal(Scene scene);
    }
}

这个是物理场景类,所谓的物理场景与之对应的是Unity场景,一般来说一个Unity场景中只能有一个物理场景,就像一颗星球上使用一个重力,但是实际上我们我们可以在相同的场景中添加多个物理场景,而不同的物理场景使用的物理规则就可以不相同。物理场景可以在空间上重叠,不过针对具体空间中的物体,一个物体在一个时刻只能使用一个物理场景的规则进行计算。

而这个脚本内定义的就是一个物理场景内所需要的内容。

最后让我们来看看核心的管理类,也是主角:Physics类

Physics.bindings.cs

虽然很想把代码拿过来,但是这个脚本有上千行代码,感觉有些过大了,总的来说,Unity的Physics类是物理系统的中枢,它提供全局物理设置(如重力、碰撞参数),管理物理场景,实现丰富的碰撞检测功能(射线、形状投射和重叠检测),控制碰撞过滤,提供物理引擎集成信息,支持布料物理特性,并包含实用工具方法和内部功能支持;本质上,它是连接游戏逻辑和底层物理引擎的桥梁,让开发者能够通过简洁的API访问复杂的物理模拟功能。

那么至此,我们整个Physics的内容就差不多了,我们可以简单的总结一下:

Physics文件实现了使用C#调用底层C++写成的物理引擎的计算方法的功能,核心包括碰撞检测,常见的方式有射线检测,形状检测等,关节的实现,刚体的实现,碰撞体的实现,以及物理场景等。但是就现在公开的源代码中,我们其实对最底层的物理计算并不了解,而只是一味地调用API,因此在后续的深入中,我们还需要对Unity的底层物理引擎进行学习。

虽然之前说Unity的源码中有四个部分关于物理系统,但是其实PhysicsEditor中的内容主要负责在编辑器中实现物理系统的可视化,虽然这也是非常重要的一部分,但是我还是想优先学习底层的物理引擎进行学习,所以PhysX的优先级会大于PhyscisEditor。

虽然还想接着往下写,但是似乎因为我这篇帖子的字数过多导致现在打字都非常卡,所以PhysX的内容我们留给下篇帖子吧。可能内容里似乎没有对每个脚本的代码内容进行非常深入的了解,这是因为在我看来,别人的代码终究是别人的,写得再好学得再仔细也不算自己的创新,所以比起更细致地学习我更希望在理解基本的逻辑框架之后去尝试自己做。后续的时间里我很快就会着手开始做,希望一切顺利。