Skip to content

物理系统

ColliderRigidbody 分别是什么?

collider-vs-rigidbody

一句话:Collider 负责“碰撞形状和检测”,Rigidbody 负责“物理运动和受力”。

组件作用类比
Collider定义物体的物理外壳“身体轮廓”
Rigidbody让物体进入物理模拟“质量、速度、重力、受力系统”

Collider 是什么

Collider 告诉物理引擎:这个物体占据什么空间,能不能被射线打到,能不能和别的物体发生碰撞。

常见类型:

c
BoxCollider
SphereCollider
CapsuleCollider
MeshCollider

如果勾选 Is Trigger,它就不再阻挡物体,只负责触发事件:

c
void OnTriggerEnter(Collider other)
{
    Debug.Log("进入触发区");
}

Rigidbody 是什么

Rigidbody 让物体变成物理世界里的刚体,拥有:

c
mass       质量
velocity   速度
drag       阻力
useGravity 是否受重力
isKinematic 是否由代码控制

例如:

c
rb.AddForce(Vector3.forward * 10f, ForceMode.Impulse);

组合关系

只有 Collider

c
通常是静态物体:地面、墙、障碍物
能挡住别人,但自己不受力运动

Collider + Rigidbody

c
动态物体:箱子、球、掉落物、物理 projectile
会受重力、力、碰撞影响

只有 Rigidbody 没有 Collider

可以有速度和重力,但没有碰撞外壳,基本碰不到东西

面试加分说法

NOTE

Collider 定义物体参与物理检测的形状,Rigidbody 定义物体是否参与动力学模拟。静态场景物体通常只需要 Collider;需要被重力、力、碰撞推动的物体,需要 Collider 加 Rigidbody。Trigger 本质也是 Collider,只是不产生物理阻挡,只产生进入、停留、离开的事件。

TriggerCollision 区别是什么?

trigger-vs-collision

零基础理解

Trigger感应门

你走进自动门的感应范围,门知道你来了,但是这个感应范围不会把你撞回去。

Collision

你撞到墙,真的过不去,物理系统会处理阻挡、反弹、摩擦、接触点。

Unity 里怎么区分?

看 Collider 上的这个选项:

c
Is Trigger

如果勾上:

c
这个 Collider 就是 Trigger
只触发 OnTriggerEnter / Stay / Exit
不产生物理阻挡

如果不勾:

c
这个 Collider 是普通碰撞体
发生真实接触时触发 OnCollisionEnter / Stay / Exit
会产生阻挡

举例

金币拾取:

玩家走进金币范围
金币消失
玩家不会被金币挡住

所以金币用 Trigger

墙壁:

玩家走到墙前
玩家不能穿过去

所以墙用 Collision

Trigger 代码

c
using UnityEngine;

public class Coin : MonoBehaviour
{
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            Debug.Log("玩家吃到金币");
            Destroy(gameObject);
        }
    }
}

金币设置:

c
Coin GameObject
- SphereCollider
  - Is Trigger 勾选

玩家通常需要:

c
Player GameObject
- Collider
- Rigidbody 或 CharacterController

Collision 代码

c
using UnityEngine;

public class HitWall : MonoBehaviour
{
    private void OnCollisionEnter(Collision collision)
    {
        Debug.Log("我撞到了:" + collision.gameObject.name);

        ContactPoint contact = collision.contacts[0];
        Debug.Log("撞击点:" + contact.point);
        Debug.Log("撞击面的法线:" + contact.normal);
    }
}

面试高分说法

TIP

Trigger 是触发检测,不产生物理阻挡,适合拾取、范围检测、技能区域。Collision 是真实碰撞,会进入物理求解,产生接触点、法线、阻挡和碰撞响应,适合墙、地面、箱子、球等真实物理交互。


OnTriggerEnterOnCollisionEnter 的触发条件是什么?

ontriggerenter-oncollisionenter-conditions

OnTriggerEnter 条件

要触发 OnTriggerEnter,通常需要:

1. 两个物体都有 Collider
2. 至少一个 Collider 勾选 Is Trigger
3. 至少一方有 Rigidbody
4. 两个物体的 Layer 在碰撞矩阵中允许交互

代码:

c
private void OnTriggerEnter(Collider other)
{
    Debug.Log("进入触发器的是:" + other.name);
}

参数 Collider other 是:

进入我触发范围的那个 Collider

OnCollisionEnter 条件

要触发 OnCollisionEnter,通常需要:

1. 两个物体都有 Collider
2. 双方 Collider 都不是 Trigger
3. 至少一方有 Rigidbody
4. 两者真实接触
5. Layer 碰撞矩阵允许碰撞

代码:

c
private void OnCollisionEnter(Collision collision)
{
    Debug.Log("撞到的是:" + collision.gameObject.name);
}

参数 Collision collision 里面信息更多:

c
collision.gameObject;       // 撞到的对象
collision.collider;         // 撞到的 Collider
collision.contacts;         // 接触点数组
collision.relativeVelocity; // 相对速度

为什么至少一方要有 Rigidbody?

零基础可以这样理解:

Collider 只是外壳。
Rigidbody 才让物体进入物理事件系统。

两个只有 Collider 的静态物体放在一起,Unity 一般把它们当成场景结构,不会当成“正在发生动态碰撞的两个物体”。

常见错误

错误 1:金币有 Trigger,但玩家没有 Rigidbody。

结果:OnTriggerEnter 可能不触发

错误 2:想走 OnCollisionEnter,结果 Collider 勾了 Is Trigger

结果:不会走 OnCollisionEnter,会走 OnTriggerEnter

错误 3:Layer 碰撞矩阵关闭了。

结果:两个物体看起来碰到了,但事件不来

面试高分说法

IMPORTANT

OnTriggerEnter 需要双方有 Collider,至少一个是 Trigger,并且通常至少一方有关联 Rigidbody。OnCollisionEnter 需要双方是非 Trigger Collider,并发生真实物理接触,也通常要求至少一方有 Rigidbody。Trigger 返回 Collider 信息,Collision 返回更完整的接触信息,比如接触点、法线和相对速度。


静态碰撞体、动态碰撞体、运动学刚体区别是什么?

static-dynamic-kinematic-collider

先看三种物体

静态碰撞体:Collider,没有 Rigidbody
动态碰撞体:Collider + Rigidbody,isKinematic = false
运动学刚体:Collider + Rigidbody,isKinematic = true

静态碰撞体 Static Collider

像:

地面

建筑
大石头
不可移动障碍物

组件:

BoxCollider / MeshCollider / CapsuleCollider
没有 Rigidbody

特点:

不受重力
不受 AddForce
不应该频繁移动
适合作为场景里的固定碰撞环境

例子:

地板有 BoxCollider
玩家站在地板上
地板不需要 Rigidbody

动态碰撞体 Dynamic Rigidbody

像:


箱子
掉落物
可被炸飞的木桶

组件:

Collider
Rigidbody
isKinematic = false

特点:

会受重力影响
会被力推动
会和其他 Collider 发生真实碰撞
位置由物理引擎更新

代码:

c
using UnityEngine;

public class PushBox : MonoBehaviour
{
    public Rigidbody rb;

    private void Start()
    {
        rb.AddForce(Vector3.forward * 10f, ForceMode.Impulse);
    }
}

运动学刚体 Kinematic Rigidbody

像:

移动平台
电梯
自动门
动画控制的机关
过场动画里的角色

组件:

Collider
Rigidbody
isKinematic = true

特点:

不被重力自动拉下去
不被 AddForce 推动
由代码或动画控制移动
可以影响动态刚体

移动平台例子:

c
using UnityEngine;

public class MovingPlatform : MonoBehaviour
{
    public Rigidbody rb;
    public Vector3 start;
    public Vector3 end;
    public float speed = 2f;

    private void FixedUpdate()
    {
        float t = (Mathf.Sin(Time.time * speed) + 1f) * 0.5f;
        Vector3 target = Vector3.Lerp(start, end, t);

        rb.MovePosition(target);
    }
}

平台设置:

Rigidbody
- Is Kinematic 勾选
- Use Gravity 关闭
Collider
- 不勾 Is Trigger

常见坑

不要频繁移动静态 Collider:

// 不推荐:一个没有 Rigidbody 的墙,每帧改位置
transform.position += Vector3.right * Time.deltaTime;

原因:

Unity 会把静态 Collider 当作场景物理结构。
你频繁移动它,会导致物理系统不断更新内部结构,性能不好。

如果要移动,请加 Rigidbody,并设置成 Kinematic。

面试高分说法

TIP

静态碰撞体只有 Collider,适合不会动的场景物体;动态刚体是 Collider 加非 Kinematic Rigidbody,会受重力、力和碰撞影响;运动学刚体是 Rigidbody 设置 isKinematic,由代码或动画控制,不受物理力驱动,适合移动平台和动画机关。


Rigidbody.isKinematic 有什么作用?

rigidbody-iskinematic

零基础理解

isKinematic 可以理解成:

c
false:交给物理引擎管
true:我自己用代码或动画控制

isKinematic = false

c
rb.isKinematic = false;

意思是:

这个 Rigidbody 是动态刚体
会受重力影响
会受 AddForce 影响
会被碰撞推动

例子:

c
using UnityEngine;

public class FallingBall : MonoBehaviour
{
    public Rigidbody rb;

    private void Start()
    {
        rb.isKinematic = false;
        rb.useGravity = true;
    }
}

球会掉下来。

isKinematic = true

c
rb.isKinematic = true;

意思是:

物理引擎不再自动控制它的运动
重力不会让它掉落
AddForce 不会把它推走
它的位置通常由代码、动画或 MovePosition 控制

例子:

c
using UnityEngine;

public class KinematicDoor : MonoBehaviour
{
    public Rigidbody rb;
    public Vector3 openPosition;
    public float speed = 3f;

    private void Awake()
    {
        rb.isKinematic = true;
    }

    private void FixedUpdate()
    {
        rb.MovePosition(Vector3.MoveTowards(
            rb.position,
            openPosition,
            speed * Time.fixedDeltaTime
        ));
    }
}

布娃娃例子

角色活着时:

Animator 控制动作
骨骼 Rigidbody 设置 isKinematic = true

角色死亡时:

关闭 Animator
骨骼 Rigidbody 设置 isKinematic = false
让角色变成 ragdoll

代码示意:

c
using UnityEngine;

public class RagdollSwitch : MonoBehaviour
{
    public Animator animator;
    public Rigidbody[] ragdollBodies;

    private void Awake()
    {
        SetRagdoll(false);
    }

    public void Die()
    {
        animator.enabled = false;
        SetRagdoll(true);
    }

    private void SetRagdoll(bool enabled)
    {
        foreach (Rigidbody rb in ragdollBodies)
        {
            rb.isKinematic = !enabled;
        }
    }
}

面试高分说法

TIP

isKinematic 决定 Rigidbody 是否由物理动力学模拟。false 时由物理系统根据重力、力和碰撞更新;true 时不再受力驱动,而由脚本、动画或 MovePosition 控制。它常用于移动平台、动画机关、过场控制和 ragdoll 切换。


AddForce 和直接改 velocity 区别是什么?

addforce-vs-velocity

零基础理解

AddForce

我推你一下,至于你动多快,让物理系统算。

velocity

我直接告诉你:你现在速度就是这个。

AddForce 例子

c
using UnityEngine;

public class ExplosionPush : MonoBehaviour
{
    public Rigidbody rb;
    public float force = 10f;

    public void Push(Vector3 direction)
    {
        rb.AddForce(direction.normalized * force, ForceMode.Impulse);
    }
}

适合:

爆炸
推箱子
击飞
车辆加速
物理感明显的运动

ForceMode 是什么?

c
Force:持续施加力,受质量影响
Acceleration:持续加速度,不受质量影响
Impulse:瞬间冲量,受质量影响
VelocityChange:瞬间速度变化,不受质量影响

例子:

c
rb.AddForce(Vector3.forward * 10f, ForceMode.Force);
rb.AddForce(Vector3.forward * 10f, ForceMode.Impulse);
rb.AddForce(Vector3.forward * 10f, ForceMode.VelocityChange);

简单记:

c
Force:慢慢推
Impulse:猛推一下
VelocityChange:直接让速度变一下

直接改 velocity 例子

跳跃常用:

c
using UnityEngine;

public class RigidbodyJump : MonoBehaviour
{
    public Rigidbody rb;
    public float jumpSpeed = 6f;
    public bool isGrounded;

    private void Update()
    {
        if (isGrounded && Input.GetKeyDown(KeyCode.Space))
        {
            Vector3 v = rb.velocity;
            v.y = jumpSpeed;
            rb.velocity = v;
        }
    }
}

为什么跳跃适合改 velocity

因为跳跃通常希望起跳速度是明确的。
不想因为质量不同导致跳跃高度乱变。

常见错误

错误:

c
rb.velocity = moveDir * speed * Time.deltaTime;

原因:

velocity 本身就是“每秒移动多少米”
不能再乘 Time.deltaTime

正确:

c
rb.velocity = moveDir * speed;

如果只想改水平速度,保留 y 轴:

c
Vector3 v = rb.velocity;
v.x = moveDir.x * speed;
v.z = moveDir.z * speed;
rb.velocity = v;

面试高分说法

NOTE

AddForce 是给刚体施加力,由物理系统根据质量、时间步和 ForceMode 计算速度变化;velocity 是直接指定当前速度,控制更强但会覆盖物理结果。需要真实受力感时用 AddForce,需要明确速度,比如跳跃、固定移动、击退初速度时可以直接改 velocity。


射线检测如何过滤 Layer?

raycast-layer-filter

零基础理解

Raycast 像发出一条激光。

但你可能不希望它打到所有东西。

比如:

开枪只想打 Enemy
点地板只想点 Ground
交互只想检测 NPC 和 Door

这时候用 LayerMask

Inspector 方式

c
using UnityEngine;

public class ClickRaycast : MonoBehaviour
{
    public Camera mainCamera;
    public LayerMask hitMask;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out RaycastHit hit, 100f, hitMask))
            {
                Debug.Log("射线打到了:" + hit.collider.name);
            }
        }
    }
}

然后在 Inspector 里选择:

c
hitMask = Enemy / Ground / Interactable

代码方式

c
int mask = LayerMask.GetMask("Enemy", "Ground");

if (Physics.Raycast(ray, out RaycastHit hit, 100f, mask))
{
    Debug.Log(hit.collider.name);
}

只检测 Enemy 层

c
int enemyLayer = LayerMask.NameToLayer("Enemy");
int mask = 1 << enemyLayer;

为什么要 1 << layer

因为 LayerMask 是位掩码。

c
Layer 编号:第几层
LayerMask:哪些层被选中

排除 Player 层

c
int playerLayer = LayerMask.NameToLayer("Player");
int mask = ~(1 << playerLayer);

Physics.Raycast(ray, out RaycastHit hit, 100f, mask);

是否检测 Trigger

c
Physics.Raycast(
    ray,
    out RaycastHit hit,
    100f,
    hitMask,
    QueryTriggerInteraction.Ignore
);

可选:

c
UseGlobal:使用全局设置
Ignore:忽略 Trigger
Collide:检测 Trigger

面试高分说法

TIP

Raycast 可以通过 LayerMask 参数过滤检测层。项目里通常会把 Enemy、Ground、Interactable、Player 等分层,射线只查询需要的层,避免打到自己、特效或无关碰撞体。对于 Trigger,还可以用 QueryTriggerInteraction 控制是否参与查询。


碰撞矩阵有什么用?

physics-collision-matrix

零基础理解

碰撞矩阵像一个“班级座位规则表”。

它告诉 Unity:

Player 和 Enemy 要不要碰?
Player 和 Player 要不要碰?
Bullet 和 Player 要不要碰?
Bullet 和 Bullet 要不要碰?

位置:

c
Project Settings → Physics → Layer Collision Matrix

例子

你有这些 Layer:

c
Player
Enemy
PlayerBullet
EnemyBullet
Ground
TriggerZone

你可以设置:

c
PlayerBullet 只碰 Enemy
EnemyBullet 只碰 Player
Player 不和 Player 碰撞
Enemy 不和 Enemy 碰撞
TriggerZone 只检测 Player

为什么不用代码 if 判断?

你当然可以写:

c
if (other.CompareTag("Player"))
{
}

但碰撞矩阵更早过滤。

意思是:

物理系统一开始就知道这两层不用碰
直接减少计算
也减少误触发

运行时忽略 Layer 碰撞

c
int playerLayer = LayerMask.NameToLayer("Player");
int enemyLayer = LayerMask.NameToLayer("Enemy");

Physics.IgnoreLayerCollision(playerLayer, enemyLayer, true);

恢复碰撞:

c
Physics.IgnoreLayerCollision(playerLayer, enemyLayer, false);

忽略两个具体 Collider

比如玩家手里的武器不要打到自己:

c
Physics.IgnoreCollision(playerCollider, weaponCollider, true);

面试高分说法

IMPORTANT

碰撞矩阵是 Unity 物理系统的全局 Layer 过滤规则,用来控制哪些层之间可以发生碰撞或触发。它比在回调里写 if 更底层,能减少不必要的物理检测,也能让子弹、角色、地形、技能区域之间的交互关系更清晰。


为什么快速移动物体会穿透?怎么解决?

fast-moving-tunneling-solution

零基础理解

Unity 物理不是无时无刻都检查。

它是一格一格检查的。

假设子弹很快:

第 1 次物理检查:子弹在墙左边
第 2 次物理检查:子弹已经在墙右边

Unity 没看到它中间穿过墙的那一瞬间,于是就穿透了。

这叫:

Tunneling,隧穿

容易穿透的情况

物体速度太快
墙太薄
Collider 太小
Fixed Timestep 太大
用 transform.position 硬改位置
普通 Discrete 碰撞检测不够

解决方案 1:开启连续碰撞检测

c
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

常见模式:

c
Discrete:默认,离散检测,性能好,但高速可能穿透
Continuous:适合高速物体撞静态物体
ContinuousDynamic:适合高速动态刚体
ContinuousSpeculative:预测式连续检测,运动学刚体也常用

解决方案 2:子弹用 Raycast

很多游戏的子弹不是靠刚体飞过去撞,而是用射线判断有没有命中。

c
using UnityEngine;

public class BulletRaycast : MonoBehaviour
{
    public Vector3 lastPosition;
    public LayerMask hitMask;
    public float damage = 10f;

    private void Start()
    {
        lastPosition = transform.position;
    }

    private void Update()
    {
        Vector3 currentPosition = transform.position;
        Vector3 movement = currentPosition - lastPosition;
        float distance = movement.magnitude;

        if (distance > 0f)
        {
            Ray ray = new Ray(lastPosition, movement.normalized);

            if (Physics.Raycast(ray, out RaycastHit hit, distance, hitMask))
            {
                Debug.Log("子弹命中:" + hit.collider.name);
                Destroy(gameObject);
            }
        }

        lastPosition = currentPosition;
    }
}

解决方案 3:有体积的子弹用 SphereCast

c
Physics.SphereCast(
    lastPosition,
    0.2f,
    movement.normalized,
    out RaycastHit hit,
    distance,
    hitMask
);

Raycast 是一条线。

SphereCast 像一个球沿路径扫过去,更适合有体积的物体。

解决方案 4:减少 Fixed Timestep

c
Project Settings → Time → Fixed Timestep

比如从:

0.02 秒

改成:

0.01 秒

物理检查更频繁,但 CPU 更累。

注意:Interpolation 不是解决穿透

Interpolation 只是让画面看起来更平滑。

它解决视觉抖动
不等于解决物理穿透

面试高分说法

NOTE

高速物体穿透的本质是离散物理步采样不足,物体在两个 FixedUpdate 之间跨过了碰撞体。解决方式包括开启连续碰撞检测、降低 Fixed Timestep、增厚 Collider,以及对子弹这类高速对象使用 Raycast 或 SphereCast 做路径检测。项目中子弹命中通常不会完全依赖刚体碰撞,而会用射线保证稳定性。


物理帧率如何设置?

physics-fixed-timestep

零基础理解

Unity 有两种常见更新:

Update:画面每刷新一帧调用一次
FixedUpdate:物理系统按固定时间间隔调用

如果游戏画面是 60 FPS:

Update 大约每秒 60 次

如果 Fixed Timestep 是 0.02:

FixedUpdate 每秒约 50 次

因为:

1 / 0.02 = 50

在哪里设置?

c
Project Settings → Time → Fixed Timestep

代码里:

c
Time.fixedDeltaTime = 0.02f;

Rigidbody 相关代码放哪里?

物理操作一般放 FixedUpdate

c
using UnityEngine;

public class PhysicsMove : MonoBehaviour
{
    public Rigidbody rb;
    public float force = 10f;

    private void FixedUpdate()
    {
        rb.AddForce(Vector3.forward * force);
    }
}

输入可以放 Update,再在 FixedUpdate 用:

c
using UnityEngine;

public class RigidbodyInputMove : MonoBehaviour
{
    public Rigidbody rb;
    public float speed = 5f;

    private Vector3 inputDir;

    private void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");

        inputDir = new Vector3(h, 0f, v).normalized;
    }

    private void FixedUpdate()
    {
        Vector3 velocity = inputDir * speed;
        velocity.y = rb.velocity.y;

        rb.velocity = velocity;
    }
}

Fixed Timestep 小一点会怎样?

比如:

0.02 → 0.01

结果:

物理每秒 50 次 → 100 次
碰撞更细
高速物体更不容易漏检
CPU 开销更高

Fixed Timestep 大一点会怎样?

比如:

0.02 → 0.04

结果:

物理每秒 50 次 → 25 次
CPU 更轻松
物理更粗糙
碰撞更容易不稳定

Maximum Allowed Timestep

还有一个相关设置:

Maximum Allowed Timestep

它的作用是:

当游戏卡顿时,限制 Unity 最多补多少物理时间。
避免一帧里疯狂补很多次 FixedUpdate。

面试高分说法

TIP

Unity 的物理系统按固定时间步更新,由 Fixed Timestep 决定频率,默认常见值是 0.02 秒,也就是约 50Hz。Rigidbody 的受力、速度和物理移动通常放在 FixedUpdate。调小 Fixed Timestep 可以提高物理精度,但会增加 CPU 成本;调大则省性能,但模拟更粗糙,快速物体更容易出现问题。


最后给你一版“面试官问,我怎么答”的完整口述

你可以这样说:

IMPORTANT

Unity 物理里,Collider 决定物体的碰撞形状,Rigidbody 决定物体是否参与动力学模拟。Trigger 是 Collider 的一种触发模式,只检测进入、停留、离开,不产生物理阻挡;Collision 是真实碰撞,会有接触点、法线和碰撞响应。触发这些事件通常要求双方有 Collider,并且至少一方有 Rigidbody。静态碰撞体适合地面和墙,动态刚体适合箱子和球,运动学刚体适合移动平台和动画机关。AddForce 是施加力,交给物理系统计算速度变化;velocity 是直接指定速度。射线检测可以用 LayerMask 过滤目标,碰撞矩阵可以从全局控制哪些 Layer 之间发生碰撞。高速物体穿透是因为离散物理步采样不足,可以用连续碰撞检测、Raycast/SphereCast、降低 Fixed Timestep 等方式解决。物理逻辑一般放在 FixedUpdate,因为 Unity 物理系统按固定时间步运行。

按顺序背就行:Collider → Rigidbody → Trigger/Collision → Kinematic → Force/Velocity → Layer → Matrix → CCD → FixedUpdate

文章评价

读完这篇,留下你的看法

暂无审核通过的评价。

登录账号后才能评价。

本站访客数0总站访问量0本页访问量0