DETERMINISTIC COLLISION
Int Collider
可脱离渲染运行的 2D / 3D 定点物理碰撞库
以 FixInt 为计算基础,为状态同步、帧同步、战斗回放和服务端权威验证提供稳定一致的逻辑碰撞判定。
框架介绍
Unity ZM Physic Int Collider 是一套面向状态同步、帧同步、战斗回放与服务端战斗验证的定点碰撞检测库。碰撞体、坐标、尺寸及碰撞算法建立在 FixInt 定点数学之上,逻辑碰撞体不继承 MonoBehaviour,可以脱离渲染对象独立运行。
| 模块 | 命名空间 | 说明 |
|---|---|---|
| 定点数学 | ZM.FixMath |
标量、数学函数、2D/3D 定点向量 |
| 2D 碰撞 | ZM.Physics2D |
Box、Sphere、碰撞事件、扫掠体 CCD |
| 3D 碰撞 | ZM.Physics3D |
Box、Sphere、Cylinder 组合检测 |
核心设计
逻辑层持有定点碰撞数据并完成权威判定;Unity GameObject、Transform 和 Renderer 只承担可选的输入适配与画面表现。相同逻辑可复用于客户端和无渲染服务端。
职责边界
本库提供确定性基础碰撞检测、碰撞生命周期和部分位置修正能力,并不是完整刚体动力学引擎,不包含 PhysX 的重力、摩擦、质量、关节和复杂旋转碰撞体模拟。
1. 功能概览
| 能力 | 说明 |
|---|---|
PhysicsWorld2D |
管理 2D 逻辑碰撞体并执行 Box / Sphere 检测 |
PhysicsWorld3D |
管理 3D 逻辑碰撞体并执行 Box / Sphere / Cylinder 检测 |
| 碰撞生命周期 | 提供 Enter、Stay、Exit 回调 |
| 位置修正 | 计算部分碰撞组合的穿插修正向量 |
| Unity Collider 适配 | 从 Unity Collider 快速创建定点逻辑碰撞体 |
| 2D Swept Volume | 为高速物体提供扫掠体连续检测示例 |
| 逻辑渲染分离 | 逻辑碰撞体可不绑定任何 GameObject |
1.1 碰撞支持矩阵
| A B | Box | Sphere |
|---|---|---|
| Box | 支持 | 支持 |
| Sphere | 支持 | 支持 |
| A B | Box | Sphere | Cylinder |
|---|---|---|---|
| Box | 支持 | 支持 | 支持 |
| Sphere | 支持 | 支持 | 支持 |
| Cylinder | 支持 | 支持 | 支持 |
形状说明
Box 检测以轴对齐包围盒思路实现。当前版本不提供任意旋转 OBB 或复杂网格碰撞,请根据项目需求扩展。
2. 获取与接入
2.1 获取源码
使用 Unity Hub 打开完整工程,或将下列目录完整复制到已有项目:
依赖关系
FixIntPhysics 依赖 FixIntMath。复制时应保留两个目录的 .asmdef 和 .meta 文件;使用自定义程序集的业务代码还需要添加相应 Assembly Definition Reference。
2.2 引用命名空间
3. 运行流程
一次标准逻辑帧通常包含以下步骤:
- 读取当前帧已确认输入。
- 计算角色等逻辑对象的新定点位置。
- 使用
SyncLogicPos、SyncLogicSize、SyncLogicRadius等接口同步碰撞数据。 - 调用
PhysicsWorld2D.Instance.OnLogicFrameUpdate()或 3D 对应接口。 - 处理 Enter / Stay / Exit 回调和
AdjustPos。 - 将最终逻辑状态同步到表现对象。
不要使用 Update 作为权威逻辑帧
仓库 Demo 在 Update() 中调用碰撞世界是为了直观演示。正式帧同步项目应由固定频率的逻辑帧调度器调用,不能依赖各客户端不同的渲染帧率。
4. 2D 快速上手
4.1 纯逻辑方式创建
using ZM.FixMath;
using ZM.Physics2D;
FixIntBoxCollider2D player = new FixIntBoxCollider2D(
new FixIntVector2(0, 0), // 逻辑位置
FixIntVector2.zero, // 中心偏移
new FixIntVector2(2, 2) // 完整尺寸
);
FixIntSphereCollider2D enemy = new FixIntSphereCollider2D(
new FixIntVector2(3, 0),
FixIntVector2.zero,
1 // 半径
);
PhysicsWorld2D.Instance.AddCollider2D(player);
PhysicsWorld2D.Instance.AddCollider2D(enemy);
4.2 监听碰撞事件
player.OnCollisionEnter2DAction += OnCollisionEnter;
player.OnCollisionStay2DAction += OnCollisionStay;
player.OnCollisionExit2DAction += OnCollisionExit;
void OnCollisionEnter(FixIntCollider2D other)
{
Debug.Log($"进入碰撞:{other.ColliderType}");
}
void OnCollisionStay(FixIntCollider2D other)
{
// 持续伤害、触发区计时等
}
void OnCollisionExit(FixIntCollider2D other)
{
Debug.Log($"离开碰撞:{other.ColliderType}");
}
4.3 驱动逻辑帧
void OnLogicFrameUpdate(FixIntVector2 nextPosition)
{
player.SyncLogicPos(nextPosition);
PhysicsWorld2D.Instance.OnLogicFrameUpdate();
}
4.4 释放碰撞体
OnRelease() 会停用碰撞体、清理碰撞状态,并将其从对应物理世界移除。
5. 从 Unity Collider 创建
5.1 2D Box
BoxCollider2D unityBox = GetComponent<BoxCollider2D>();
FixIntBoxCollider2D logicBox =
PhysicsWorld2D.Instance.GenerateFixIntBoxFormUnityCollider(unityBox);
5.2 3D Box / Sphere / Cylinder
BoxCollider unityBox = boxObject.GetComponent<BoxCollider>();
SphereCollider unitySphere = sphereObject.GetComponent<SphereCollider>();
CapsuleCollider unityCapsule = cylinderObject.GetComponent<CapsuleCollider>();
FixIntBoxCollider3D box =
PhysicsWorld3D.Instance.GenerateFixIntBoxFormUnityCollider(unityBox);
FixIntSphereCollider3D sphere =
PhysicsWorld3D.Instance.GenerateFixIntSphereFormUnityCollider(unitySphere);
FixintCylinderCollider3D cylinder =
PhysicsWorld3D.Instance.GenerateFixIntCylinderFormUnityCollider(unityCapsule);
生成接口的 managerCollider 参数默认为 true,表示创建后自动加入碰撞世界。传入 false 时需要自行管理。
RenderObj
适配接口会调用 SetRenderObj 保存对应 Unity 对象。纯逻辑方式创建的碰撞体可以不设置 RenderObj,业务代码不得假设它一定存在。
6. 碰撞体公共 API
6.1 公共属性
| 属性 | 说明 |
|---|---|
Active |
是否参与碰撞检测 |
RenderObj |
可选的 Unity 表现对象 |
LogicPos |
定点逻辑位置,包含中心偏移后的结果 |
Center |
碰撞体中心偏移 |
ColliderType |
碰撞体形状枚举 |
X / Y / Z |
逻辑位置分量;2D 无 Z |
CanAdjust |
当前是否存在待消费的位置修正 |
AdjustPos |
位置修正向量 |
6.2 公共方法
| API | 说明 |
|---|---|
SetRenderObj(GameObject) |
绑定可选表现对象 |
SetUseAdjustPos(bool) |
开启或关闭位置修正 |
SyncLogicPos(...) |
更新逻辑位置 |
SyncLogicSize(...) |
更新 Box 尺寸 |
SyncLogicRadius(...) |
更新 Sphere / Cylinder 半径 |
SyncLogicHeight(...) |
更新 3D Cylinder 高度 |
DetectCollider(target) |
与目标碰撞体执行检测并更新事件状态 |
OnRelease() |
释放并从物理世界移除 |
AdjustPos 是消费式读取
读取 AdjustPos 时会将 CanAdjust 重置为 false。请在碰撞世界完成检测后统一读取一次,不要在多个系统中重复读取。
7. 形状 API
7.1 FixIntBoxCollider2D
| 成员 | 说明 |
|---|---|
Size |
完整宽高 |
BoxWidth |
半宽 |
BoxHeight |
半高 |
构造函数:
7.2 FixIntSphereCollider2D
| 成员 | 说明 |
|---|---|
Radius |
圆形碰撞体半径 |
7.3 FixIntBoxCollider3D
| 成员 | 说明 |
|---|---|
Size |
完整三维尺寸 |
HalfSize |
三轴半尺寸 |
BoxWidth |
X 轴半宽 |
BoxHeight |
Y 轴半高 |
7.4 FixIntSphereCollider3D
Radius 表示球体半径。
7.5 FixintCylinderCollider3D
| 成员 | 说明 |
|---|---|
Radius |
圆柱半径 |
Height |
圆柱高度 |
MaxHeightPoint |
顶部 Y 坐标 |
MinHeightPoint |
底部 Y 坐标 |
ContainTargetHeight(y) |
判断 Y 坐标是否处于圆柱高度范围 |
类名大小写
当前源码类型名为 FixintCylinderCollider3D,其中 int 的 i 为小写。调用时需要与源码保持一致。
8. PhysicsWorld2D API
| API | 说明 |
|---|---|
Instance |
2D 世界单例 |
OnLogicFrameUpdate() |
遍历已注册碰撞体并更新碰撞状态 |
DetectCollider(Box, Box, useAdjust) |
Box 与 Box 检测 |
DetectCollider(Box, Sphere, useAdjust) |
Box 与 Sphere 检测 |
DetectCollider(Sphere, Sphere) |
Sphere 与 Sphere 检测 |
AddCollider2D(collider) |
注册碰撞体 |
RemoveCollider2D(collider) |
移除碰撞体 |
GenerateFixIntBoxFormUnityCollider(...) |
从 Unity Box 创建逻辑 Box |
GenerateFixIntSphereFormUnityCollider(...) |
从 Unity Collider 创建逻辑 Sphere |
直接调用 DetectCollider 适合一次性查询;调用碰撞体的 DetectCollider(target) 或世界逻辑帧更新,则会一并维护 Enter / Stay / Exit 状态。
9. PhysicsWorld3D API
| 检测组合 | 对应重载 |
|---|---|
| Box / Box | DetectCollider(FixIntBoxCollider3D, FixIntBoxCollider3D) |
| Box / Sphere | DetectCollider(FixIntBoxCollider3D, FixIntSphereCollider3D) |
| Sphere / Sphere | DetectCollider(FixIntSphereCollider3D, FixIntSphereCollider3D) |
| Cylinder / Sphere | DetectCollider(FixintCylinderCollider3D, FixIntSphereCollider3D) |
| Cylinder / Box | DetectCollider(FixintCylinderCollider3D, FixIntBoxCollider3D, bool) |
| Cylinder / Cylinder | DetectCollider(FixintCylinderCollider3D, FixintCylinderCollider3D) |
PhysicsWorld3D 同样提供碰撞体注册、移除、逻辑帧更新和 Unity Collider 适配接口。
10. 位置修正
位置修正用于处理部分形状碰撞后的穿插:
player.SetUseAdjustPos(true);
void OnLogicFrame(FixIntVector2 intendedPosition)
{
player.SyncLogicPos(intendedPosition);
PhysicsWorld2D.Instance.OnLogicFrameUpdate();
if (player.CanAdjust)
{
intendedPosition += player.AdjustPos;
player.SyncLogicPos(intendedPosition);
}
}
修正不是完整动力学响应
AdjustPos 只提供穿插位置修正,不会自动计算质量、冲量、摩擦或反弹。多个碰撞同时发生时,业务层还需确定稳定的处理顺序和迭代策略。
11. 2D 连续碰撞检测
离散碰撞只检查每个逻辑帧的最终位置,高速子弹可能在相邻两帧之间越过薄墙。CCDSweptVolume2D 根据上一帧与当前帧位置生成扫掠包围盒,用于补充检测运动路径覆盖区域。
CCDSweptVolume2D swept = new CCDSweptVolume2D(
bulletCollider,
currentFramePosition,
lastFramePosition
);
bool hit = swept.DetectCollider(wallCollider);
当前实现
仓库中的 CCDSweptVolume2D 同时包含教学可视化代码,会创建临时 GameObject 和 Image。服务端或正式逻辑版本应移除表现层代码,仅保留定点扫掠体计算与检测。
12. 性能与确定性
12.1 当前复杂度
OnLogicFrameUpdate() 当前使用两层循环遍历碰撞体,检测复杂度约为 O(n²)。少量战斗单位便于使用;碰撞体较多时建议增加 Broad Phase:
- 均匀网格 / Spatial Hash
- 四叉树(2D)
- 八叉树(3D)
- Sweep and Prune
- 碰撞层与掩码过滤
12.2 确定性清单
- 所有端使用相同库版本、配置和查找表。
- 使用固定逻辑帧和一致输入顺序。
- 碰撞体注册、移除与遍历顺序保持一致。
- 不以
Transform或 Unity Physics 结果作为权威逻辑状态。 - 随机数使用相同种子和调用顺序。
- 多碰撞修正采用明确、稳定、可复现的顺序。
13. 示例场景
| 场景 | 演示内容 |
|---|---|
PhysicsWorld2DDemo.unity |
2D Box / Sphere 碰撞与事件 |
BoxPlayerMove/PlayerMove.unity |
Box 角色移动及阻挡 |
SpherePlayerMove/PlayerMove.unity |
Sphere 角色移动及阻挡 |
SweptVolume/SweptVolume.unity |
高速子弹连续碰撞检测 |
PhysicsWorld3DDemo.unity |
3D Box / Sphere / Cylinder 检测 |
PlayerMoveDemo.unity |
3D 定点移动与碰撞 |
示例根目录:
14. 常见问题
为什么两个对象看起来接触了,却没有触发碰撞?
检查逻辑位置、中心偏移、尺寸或半径是否已通过 SyncLogic... 接口更新;同时确认碰撞体仍处于 Active 状态并已注册到正确的 PhysicsWorld。
为什么回调重复触发?
OnCollisionStay...Action 本来就会在持续碰撞期间每个逻辑帧触发。只需要首次事件时应监听 OnCollisionEnter...Action。
为什么读取不到位置修正?
先调用 SetUseAdjustPos(true),并确认当前形状组合支持修正。AdjustPos 为消费式读取,读取后 CanAdjust 会重置。
可以放到纯 C# 服务端运行吗?
核心设计支持逻辑与渲染分离,但现有程序集仍引用 UnityEngine,部分适配与 Demo 也使用 Unity 类型。部署纯 .NET 服务端时,应拆分核心逻辑程序集并移除 Unity 表现与适配代码。
为什么高速子弹仍可能漏检?
普通离散检测只检查帧末位置。可使用扫掠体思路,并根据子弹形状、墙体厚度和业务精度扩展专用 CCD。