物理系统
Unity 使用 NVIDIA PhysX 物理引擎,提供了强大的物理模拟功能。本章将深入讲解刚体、碰撞器、物理材质等核心概念。
物理系统概述
Unity 物理系统主要包含以下组件:
物理系统核心组件
├── Rigidbody(刚体) - 赋予物体物理属性
├── Collider(碰撞器) - 定义碰撞形状
├── PhysicMaterial(物理材质)- 定义摩擦力和弹性
├── Joint(关节) - 连接物体
└── Constant Force(恒力) - 施加持续力
Rigidbody 刚体
Rigidbody 组件使游戏对象受物理引擎控制,可以受到力、扭矩的影响。
添加和配置 Rigidbody
在 Inspector 中点击 Add Component > Physics > Rigidbody。
public class RigidbodyExample : MonoBehaviour
{
private Rigidbody rb;
void Awake()
{
rb = GetComponent<Rigidbody>();
// ========== 基本属性 ==========
rb.mass = 1f; // 质量(千克)
rb.drag = 0f; // 空气阻力
rb.angularDrag = 0.05f; // 旋转阻力
rb.useGravity = true; // 是否受重力影响
rb.isKinematic = false; // 是否运动学(不受物理影响)
rb.interpolation = RigidbodyInterpolation.Interpolate; // 插值
rb.collisionDetectionMode = CollisionDetectionMode.Continuous; // 碰撞检测模式
}
}
Rigidbody 属性详解
| 属性 | 说明 | 建议值 |
|---|---|---|
| Mass | 质量 | 0.1-10(过大影响性能) |
| Drag | 空气阻力 | 0(无阻力)到 ∞(立即停止) |
| Angular Drag | 旋转阻力 | 0.05(默认) |
| Use Gravity | 重力影响 | 通常开启 |
| Is Kinematic | 运动学模式 | 动画控制或需要手动移动时开启 |
| Interpolation | 插值 | Interpolate(平滑)或 Extrapolate(预测) |
| Collision Detection | 碰撞检测 | Continuous(高速物体)或 Discrete(默认) |
施加力
public class ForceApplication : MonoBehaviour
{
public Rigidbody rb;
public float forceAmount = 10f;
void Update()
{
// ========== AddForce - 施加力 ==========
// ForceMode.Force: 持续力(默认),受质量影响,每帧调用
rb.AddForce(Vector3.forward * forceAmount, ForceMode.Force);
// ForceMode.Acceleration: 持续加速度,不受质量影响
rb.AddForce(Vector3.forward * forceAmount, ForceMode.Acceleration);
// ForceMode.Impulse: 瞬间冲量,受质量影响,单次调用
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
// ForceMode.VelocityChange: 瞬间速度变化,不受质量影响
rb.AddForce(Vector3.forward * forceAmount, ForceMode.VelocityChange);
// ========== 施加扭矩(旋转力)==========
rb.AddTorque(Vector3.up * rotationForce); // 力矩
rb.AddRelativeTorque(Vector3.up * rotationForce); // 相对力矩
// ========== 直接设置 ==========
rb.velocity = new Vector3(0, 5, 0); // 设置速度
rb.angularVelocity = Vector3.up * 10f; // 设置角速度
}
void FixedUpdate()
{
// 物理操作应在 FixedUpdate 中进行
Vector3 force = new Vector3(
Input.GetAxis("Horizontal"),
0,
Input.GetAxis("Vertical")
) * moveForce;
rb.AddForce(force);
}
}
ForceMode 详解
| 模式 | 描述 | 使用场景 |
|---|---|---|
| Force | 持续力,受质量影响 | 推力、风力(每帧调用) |
| Acceleration | 持续加速度,不受质量影响 | 重力效果、恒定的加速度 |
| Impulse | 瞬间冲量,受质量影响 | 跳跃、爆炸、碰撞反弹 |
| VelocityChange | 瞬间速度变化,不受质量影响 | 传送、瞬间移动 |
Collider 碰撞器
Collider 定义了物体的碰撞形状,用于碰撞检测。
碰撞器类型
// ========== 3D 碰撞器 ==========
// Box Collider - 盒子碰撞器
// 适用于:箱子、建筑物、简单几何体
BoxCollider box = gameObject.AddComponent<BoxCollider>();
box.size = new Vector3(2, 1, 2);
box.center = new Vector3(0, 0.5f, 0);
// Sphere Collider - 球体碰撞器
// 适用于:球、炸弹、圆形物体
SphereCollider sphere = gameObject.AddComponent<SphereCollider>();
sphere.radius = 0.5f;
sphere.center = Vector3.zero;
// Capsule Collider - 胶囊碰撞器
// 适用于:角色、圆柱形物体
CapsuleCollider capsule = gameObject.AddComponent<CapsuleCollider>();
capsule.radius = 0.5f;
capsule.height = 2f;
capsule.direction = 1; // 0=X, 1=Y, 2=Z
// Mesh Collider - 网格碰撞器
// 适用于:复杂形状、静态环境
MeshCollider meshCol = gameObject.AddComponent<MeshCollider>();
meshCol.sharedMesh = GetComponent<MeshFilter>().sharedMesh;
meshCol.convex = true; // 必须设为 true 才能与其他 Mesh Collider 碰撞
// Terrain Collider - 地形碰撞器
// 适用于:Unity 地形系统
TerrainCollider terrainCol = terrain.AddComponent<TerrainCollider>();
terrainCol.terrainData = terrainData;
// ========== 2D 碰撞器 ==========
BoxCollider2D box2D = gameObject.AddComponent<BoxCollider2D>();
CircleCollider2D circle2D = gameObject.AddComponent<CircleCollider2D>();
PolygonCollider2D poly2D = gameObject.AddComponent<PolygonCollider2D>();
EdgeCollider2D edge2D = gameObject.AddComponent<EdgeCollider2D>();
CapsuleCollider2D capsule2D = gameObject.AddComponent<CapsuleCollider2D>();
碰撞器属性
public class ColliderProperties : MonoBehaviour
{
void Start()
{
Collider col = GetComponent<Collider>();
// ========== 基本属性 ==========
col.enabled = true; // 是否启用
col.isTrigger = false; // 是否为触发器
col.contactOffset = 0.01f; // 接触偏移
// ========== 物理材质 ==========
col.material = physicMaterial; // 设置物理材质
// ========== 边界信息 ==========
Bounds bounds = col.bounds; // 世界空间的包围盒
Vector3 center = bounds.center; // 中心点
Vector3 size = bounds.size; // 大小
Vector3 extents = bounds.extents; // 半边长
// ========== 射线检测 ==========
Ray ray = new Ray(transform.position, Vector3.down);
if (col.Raycast(ray, out RaycastHit hit, 10f))
{
Debug.Log("射线击中碰撞器");
}
}
}
触发器(Trigger)
触发器用于检测物体进入/离开某个区域,不产生物理碰撞效果。
public class TriggerExample : MonoBehaviour
{
void Start()
{
// 将碰撞器设为触发器
GetComponent<Collider>().isTrigger = true;
}
// 当其他碰撞器进入触发器时调用
void OnTriggerEnter(Collider other)
{
Debug.Log($"{other.name} 进入触发区域");
if (other.CompareTag("Player"))
{
// 玩家进入,触发事件
OnPlayerEnterZone();
}
}
// 当其他碰撞器停留在触发器内时,每帧调用
void OnTriggerStay(Collider other)
{
// 持续效果,如治疗、伤害
}
// 当其他碰撞器离开触发器时调用
void OnTriggerExit(Collider other)
{
Debug.Log($"{other.name} 离开触发区域");
}
}
碰撞检测
碰撞回调方法
public class CollisionDetection : MonoBehaviour
{
// ========== 碰撞进入 ==========
void OnCollisionEnter(Collision collision)
{
Debug.Log($"与 {collision.gameObject.name} 发生碰撞");
// 碰撞信息
ContactPoint contact = collision.contacts[0];
Vector3 point = contact.point; // 碰撞点
Vector3 normal = contact.normal; // 碰撞法线
float separation = contact.separation; // 穿透深度
// 相对速度
float relativeVelocity = collision.relativeVelocity.magnitude;
// 碰撞冲击力
if (relativeVelocity > damageThreshold)
{
TakeDamage(relativeVelocity * damageMultiplier);
}
}
// ========== 碰撞持续 ==========
void OnCollisionStay(Collision collision)
{
// 每帧调用,只要保持接触
// 可用于:摩擦力效果、持续伤害
}
// ========== 碰撞离开 ==========
void OnCollisionExit(Collision collision)
{
Debug.Log($"与 {collision.gameObject.name} 分离");
}
// ========== 2D 碰撞 ==========
void OnCollisionEnter2D(Collision2D collision)
{
Debug.Log($"2D 碰撞: {collision.gameObject.name}");
}
void OnCollisionStay2D(Collision2D collision) { }
void OnCollisionExit2D(Collision2D collision) { }
// ========== 2D 触发器 ==========
void OnTriggerEnter2D(Collider2D other)
{
Debug.Log($"2D 触发: {other.name}");
}
void OnTriggerStay2D(Collider2D other) { }
void OnTriggerExit2D(Collider2D other) { }
}
碰撞检测矩阵
| 静态碰撞器 | 刚体碰撞器 | 运动学刚体 | 触发器 | |
|---|---|---|---|---|
| 静态碰撞器 | ✗ | ✓ | ✓ | ✓ |
| 刚体碰撞器 | ✓ | ✓ | ✓ | ✓ |
| 运动学刚体 | ✓ | ✓ | ✗ | ✓ |
| 触发器 | ✓ | ✓ | ✓ | ✗ |
物理材质
物理材质控制物体间的摩擦力和弹性。
创建物理材质
- Project 窗口右键 > Create > Physic Material
- 配置属性
- 拖拽到碰撞器的 Material 槽
物理材质属性
| 属性 | 说明 | 范围 |
|---|---|---|
| Dynamic Friction | 动态摩擦力 | 0-1(0=冰,1=橡胶) |
| Static Friction | 静摩擦力 | 0-1 |
| Bounciness | 弹性 | 0-1(0=无弹性,1=完全弹性) |
| Friction Combine | 摩擦力组合模式 | Average, Minimum, Maximum, Multiply |
| Bounce Combine | 弹性组合模式 | Average, Minimum, Maximum, Multiply |
组合模式说明
Average: (a + b) / 2 // 取平均值
Minimum: min(a, b) // 取较小值
Maximum: max(a, b) // 取较大值
Multiply: a * b // 相乘
射线检测
射线检测是游戏中常用的技术,用于射击、点击选择、导航等。
public class RaycastExample : MonoBehaviour
{
public Camera mainCamera;
public LayerMask enemyLayer; // 只检测敌人层
public float rayDistance = 100f;
void Update()
{
// ========== 基础射线检测 ==========
Ray ray = new Ray(transform.position, transform.forward);
if (Physics.Raycast(ray, out RaycastHit hit, rayDistance))
{
Debug.Log($"射线击中: {hit.collider.name}");
Debug.DrawLine(ray.origin, hit.point, Color.red);
}
// ========== 从屏幕中心发射射线 ==========
Ray centerRay = mainCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
// ========== 从鼠标位置发射射线 ==========
Ray mouseRay = mainCamera.ScreenPointToRay(Input.mousePosition);
// ========== 带层遮罩的射线检测 ==========
if (Physics.Raycast(ray, out hit, rayDistance, enemyLayer))
{
// 只检测敌人层
hit.collider.GetComponent<Enemy>()?.TakeDamage(10);
}
// ========== 多射线检测 ==========
RaycastHit[] hits = Physics.RaycastAll(ray, rayDistance);
foreach (var h in hits)
{
Debug.Log($"击中: {h.collider.name}");
}
// ========== 球体投射 ==========
// 检测一个球体沿路径的碰撞
if (Physics.SphereCast(ray, radius, out hit, rayDistance))
{
// 球体半径为 radius 的投射
}
// ========== 盒体投射 ==========
if (Physics.BoxCast(ray.origin, halfExtents, ray.direction, out hit, Quaternion.identity, rayDistance))
{
// 盒体投射
}
}
}
区域检测
public class OverlapDetection : MonoBehaviour
{
void DetectInArea()
{
// ========== 球形区域检测 ==========
Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
foreach (var col in colliders)
{
if (col.CompareTag("Enemy"))
{
col.GetComponent<Enemy>()?.TakeDamage(explosionDamage);
}
}
// ========== 盒形区域检测 ==========
Collider[] boxColliders = Physics.OverlapBox(center, halfExtents, Quaternion.identity);
// ========== 胶囊形区域检测 ==========
Collider[] capsuleColliders = Physics.OverlapCapsule(point1, point2, radius);
// ========== 非分配版本(性能更好)==========
Collider[] results = new Collider[10];
int count = Physics.OverlapSphereNonAlloc(transform.position, radius, results);
}
// 可视化检测范围
void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, radius);
}
}
关节系统
关节用于连接两个刚体,模拟各种物理连接。
public class JointExamples : MonoBehaviour
{
void CreateJoints()
{
// ========== Hinge Joint - 铰链关节 ==========
// 模拟门、摆锤、链条
HingeJoint hinge = gameObject.AddComponent<HingeJoint>();
hinge.connectedBody = otherRigidbody;
hinge.axis = Vector3.up; // 旋转轴
hinge.useLimits = true;
hinge.limits = new JointLimits
{
min = -90,
max = 90
};
hinge.useMotor = true;
hinge.motor = new JointMotor
{
targetVelocity = 100,
force = 50
};
// ========== Fixed Joint - 固定关节 ==========
// 将两个物体固定在一起
FixedJoint fixedJoint = gameObject.AddComponent<FixedJoint>();
fixedJoint.connectedBody = otherRigidbody;
fixedJoint.breakForce = 1000f; // 断裂力
// ========== Spring Joint - 弹簧关节 ==========
// 模拟弹簧连接
SpringJoint spring = gameObject.AddComponent<SpringJoint>();
spring.connectedBody = otherRigidbody;
spring.spring = 100f; // 弹簧刚度
spring.damper = 5f; // 阻尼
spring.minDistance = 0f;
spring.maxDistance = 10f;
// ========== Configurable Joint - 可配置关节 ==========
// 最灵活的关节类型
ConfigurableJoint config = gameObject.AddComponent<ConfigurableJoint>();
config.connectedBody = otherRigidbody;
// 移动限制
config.xMotion = ConfigurableJointMotion.Locked;
config.yMotion = ConfigurableJointMotion.Limited;
config.zMotion = ConfigurableJointMotion.Free;
// 旋转限制
config.angularXMotion = ConfigurableJointMotion.Limited;
config.angularYMotion = ConfigurableJointMotion.Locked;
config.angularZMotion = ConfigurableJointMotion.Locked;
}
}
物理查询
public class PhysicsQueries : MonoBehaviour
{
void PhysicsQueries()
{
// ========== 检查球体是否与任何碰撞器重叠 ==========
bool isOverlapping = Physics.CheckSphere(transform.position, radius);
// ========== 检查胶囊体是否与任何碰撞器重叠 ==========
bool capsuleOverlap = Physics.CheckCapsule(point1, point2, radius);
// ========== 检查盒体是否与任何碰撞器重叠 ==========
bool boxOverlap = Physics.CheckBox(center, halfExtents);
// ========== 获取最近的点 ==========
Vector3 closestPoint = Physics.ClosestPoint(
transform.position,
targetCollider,
targetCollider.transform.position,
targetCollider.transform.rotation
);
// ========== 忽略碰撞 ==========
Physics.IgnoreCollision(collider1, collider2, true);
// ========== 忽略层碰撞 ==========
Physics.IgnoreLayerCollision(3, 8, true); // 忽略层3和层8的碰撞
}
}
实践示例
示例1:射击系统
public class ShootingSystem : MonoBehaviour
{
public Camera playerCamera;
public float damage = 25f;
public float range = 100f;
public LayerMask shootableLayers;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
void Shoot()
{
Ray ray = playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out RaycastHit hit, range, shootableLayers))
{
Debug.Log($"击中: {hit.collider.name}");
// 施加冲击力
if (hit.rigidbody != null)
{
hit.rigidbody.AddForce(ray.direction * 500f, ForceMode.Impulse);
}
// 造成伤害
var target = hit.collider.GetComponent<IDamageable>();
target?.TakeDamage(damage, hit.point, ray.direction);
// 生成弹孔效果
SpawnBulletHole(hit.point, hit.normal);
}
}
void SpawnBulletHole(Vector3 position, Vector3 normal)
{
GameObject hole = Instantiate(bulletHolePrefab, position + normal * 0.01f,
Quaternion.LookRotation(normal));
hole.transform.SetParent(hit.collider.transform);
}
}
示例2:爆炸效果
public class Explosion : MonoBehaviour
{
public float explosionForce = 500f;
public float explosionRadius = 10f;
public float upwardModifier = 1f;
public LayerMask affectedLayers;
public void Explode()
{
// 获取爆炸范围内的所有碰撞器
Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius, affectedLayers);
foreach (Collider col in colliders)
{
Rigidbody rb = col.GetComponent<Rigidbody>();
if (rb != null)
{
// 施加爆炸力
rb.AddExplosionForce(
explosionForce,
transform.position,
explosionRadius,
upwardModifier,
ForceMode.Impulse
);
}
// 造成伤害
var damageable = col.GetComponent<IDamageable>();
if (damageable != null)
{
float distance = Vector3.Distance(transform.position, col.transform.position);
float damage = Mathf.Lerp(maxDamage, 0, distance / explosionRadius);
damageable.TakeDamage(damage);
}
}
// 视觉效果
Instantiate(explosionEffect, transform.position, Quaternion.identity);
// 销毁爆炸源
Destroy(gameObject);
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, explosionRadius);
}
}
示例3:拾取系统
public class PickupSystem : MonoBehaviour
{
public Transform holdPoint;
public float pickupRange = 3f;
public LayerMask pickupLayer;
private Rigidbody heldObject;
private FixedJoint holdJoint;
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
if (heldObject == null)
{
TryPickup();
}
else
{
DropObject();
}
}
}
void TryPickup()
{
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out RaycastHit hit, pickupRange, pickupLayer))
{
Rigidbody rb = hit.collider.GetComponent<Rigidbody>();
if (rb != null)
{
PickupObject(rb);
}
}
}
void PickupObject(Rigidbody rb)
{
heldObject = rb;
// 移动到持有位置
heldObject.transform.position = holdPoint.position;
// 创建固定关节
holdJoint = gameObject.AddComponent<FixedJoint>();
holdJoint.connectedBody = heldObject;
holdJoint.breakForce = 1000f;
// 禁用重力(可选)
// heldObject.useGravity = false;
}
void DropObject()
{
if (holdJoint != null)
{
Destroy(holdJoint);
}
// heldObject.useGravity = true;
heldObject = null;
}
}
最佳实践
- 在 FixedUpdate 中处理物理:所有 Rigidbody 操作应在 FixedUpdate 中进行
- 使用插值:对玩家控制的物体启用 Rigidbody 插值,使运动更平滑
- 避免直接修改 Transform:对带有 Rigidbody 的对象,使用物理方法移动
- 合理使用触发器:触发器适合检测区域,碰撞器适合物理交互
- 优化碰撞器:尽量使用简单碰撞器(Box、Sphere),避免复杂 Mesh Collider
- 层遮罩:使用 LayerMask 优化射线检测和区域检测的性能
下一步
掌握物理系统后,你可以:
- 学习 动画系统 让角色动作更生动(即将推出)
- 了解 UI 系统 创建游戏界面(即将推出)
- 探索 粒子系统 制作特效(即将推出)