跳转至

DETERMINISTIC MATH

FixInt Math

为帧同步、战斗回放与跨平台一致性打造的定点数学基础库

以整数表达逻辑数值,提供标量、向量、数学函数与伪随机能力,让同一份战斗逻辑在不同平台获得可验证、可复现的计算结果。

快速了解 API 文档 返回首页

框架介绍

FixIntMath 是一套面向 Unity 状态同步、帧同步和战斗回放的定点数学库。它使用整数保存逻辑数值,降低不同 CPU、编译器和运行平台之间的浮点计算差异,为确定性战斗逻辑提供统一的标量、数学函数、向量和伪随机数能力。

项目 说明
命名空间 ZM.FixMath
核心类型 FixInt
缩放倍率 1024(左移 10 位)
内部存储 long
Unity 参考版本 2023.2.20f1
开源协议 MIT

适用场景

角色属性、移动速度、逻辑帧时间、伤害计算、技能范围、弹道、AI 决策、战斗回放,以及客户端与服务端共用的战斗逻辑。

职责边界

定点数学只能保证其覆盖的数学运算具有一致的计算路径。要实现完整帧同步,仍需统一逻辑帧、输入顺序、随机序列、配置版本和容器遍历顺序,并避免在逻辑层依赖 Unity 浮点物理结果。

1. 工作原理

FixInt 将实际数值放大 1024 倍后保存为整数:

内部值 = 实际值 × 1024
实际值 = 内部值 ÷ 1024

1.5 × 1024 = 1536

因此,1.5 在逻辑层内部保存为 1536。加减运算可以直接在整数上进行;乘除运算由类型内部处理缩放倍率。

精度

最小量化单位约为 1 / 1024,即 0.0009765625。输入值会被映射到最接近的可表示定点值,连续运算也会产生量化误差。

2. 获取与接入

2.1 获取源码

Unity-ZM-PhysicFixInt GitHub 仓库

git clone https://github.com/ZMteacher/Unity-ZM-PhysicFixInt.git

集成到现有项目时,将定点数学目录及其 .asmdef.meta 文件完整复制到工程:

Assets/ZMPackages/FixIntMath

不同仓库版本的目录可能写作 Assets/ZMPackage/FixIntMath,请以实际工程为准。

2.2 引用命名空间

using ZM.FixMath;

程序集引用

如果调用方使用了自定义 .asmdef,请在该程序集的 Assembly Definition References 中添加 FixIntMath

3. 快速上手

3.1 声明与运算

FixInt health = 100;
FixInt damage = 12.5f;
FixInt remain = health - damage;

FixInt speed = 3.5f;
FixInt fixedDeltaTime = 0.02f;
FixInt distance = speed * fixedDeltaTime;

3.2 转换到表现层

// 逻辑层
FixInt logicHealth = 87.5f;

// 表现层:UI、动画、Transform 等需要时再转换
float displayHealth = logicHealth.RawFloat;
int roundedHealth = logicHealth.RawInt;
double preciseDisplay = logicHealth.RawDouble;

不要混淆 Value 与 RawValue

ValueIntValue 是放大后的内部数据,不是用户看到的实际值。表现层通常应读取 RawFloatRawIntRawDouble

4. FixInt API

4.1 常量与属性

成员 类型 说明
FixInt.Zero FixInt 定点零值
FixInt.One FixInt 定点单位值 1
FixInt.MUTIPLE int 缩放倍率,当前为 1024
Value long 放大后的内部值
IntValue int 将放大后的内部值读取为 int
RawFloat float 还原后的浮点值,供表现层使用
RawInt int 还原并取整后的整数值
RawDouble double 还原后的双精度值

4.2 构造函数

FixInt value = new FixInt(10); // 实际值 10
FixInt a = new FixInt(1.25f);
FixInt b = new FixInt(2.75d);
FixInt value = new FixInt(1536L); // 实际值 1.5

FixInt(long) 的特殊语义

FixInt(long) 认为传入参数已经乘过缩放倍率,不会再次左移。创建普通整数值时应传入 int,例如 new FixInt(10),不要写成 new FixInt(10L)

4.3 类型转换

数值类型可以隐式转换为 FixInt

FixInt a = 10;
FixInt b = 1.5f;
FixInt c = 2.25d;

FixInt 转出时需要显式转换,或使用 Raw... 属性:

float a = (float)fixValue;
int b = (int)fixValue;
double c = (double)fixValue;
long internalValue = (long)fixValue;

4.4 运算符

分类 支持的运算符
算术 +-*/%、一元 -
比较 ==!=><>=<=
位移 <<>>
FixInt a = 10;
FixInt b = 4;

FixInt sum = a + b;
FixInt difference = a - b;
FixInt product = a * b;
FixInt quotient = a / b;
FixInt remainder = a % b;
bool greater = a > b;

4.5 对象接口

API 说明
Equals(FixInt) 判断两个定点值是否完全相等
CompareTo(FixInt) 比较两个定点值
GetHashCode() 获取基于内部值的哈希码
ToString() 返回还原后的数值字符串

5. FixIntMath API

5.1 基础函数

API 说明
Abs(value) 返回绝对值
Min(a, b) 返回较小值
Max(a, b) 返回较大值
Sign(value) 返回符号值
Clamp(value, min, max) 将值限制在闭区间内
Clamp01(value) 将值限制在 [0, 1]
Round(value) 四舍五入
Floor(value) 向下取整
Ceiling(value) 向上取整
FixInt damage = FixIntMath.Max(rawDamage, 0);
FixInt normalized = FixIntMath.Clamp01(progress);
FixInt gridX = FixIntMath.Floor(position.x);

5.2 幂与开方

API 说明
Pow(value, count) 整数次幂
Sqrt(value) 平方根
Sqrt(value, iterations) 指定迭代次数的平方根
FixInt square = FixIntMath.Pow(3, 2);
FixInt length = FixIntMath.Sqrt(25);

5.3 三角函数

API 输入/输出 说明
Sin(value) 弧度 查表正弦
Cos(value) 弧度 查表余弦
Acos(value) 弧度 查表反余弦
Atan2(y, x) 弧度 四象限反正切
FixInt radians = 0.5f;
FixInt sin = FixIntMath.Sin(radians);
FixInt cos = FixIntMath.Cos(radians);
FixInt direction = FixIntMath.Atan2(deltaY.RawFloat, deltaX.RawFloat);

查找表一致性

三角函数依赖仓库中的 Sin/Cos、Acos、Atan2 查找表。参与同步的所有端必须使用完全相同版本的查找表文件。

6. FixIntVector2

6.1 创建与常量

FixIntVector2 position = new FixIntVector2(10, 5);
FixIntVector2 direction = FixIntVector2.right;
FixIntVector2 origin = FixIntVector2.zero;

常用静态向量:zerooneupdownleftright

6.2 属性

属性 说明
x / y 定点分量
magnitude 向量长度
sqrMagnitude 长度平方,比较距离时优先使用
normalized 单位向量

6.3 常用 API

分类 API
插值移动 LerpLerpUnclampedMoveTowardsSmoothDamp
向量计算 DotDistanceAngleSignedAngle
变换 ScaleNormalizeReflectPerpendicular
约束比较 ClampMagnitudeMinMaxSqrMagnitude
Unity 转换 ToVector2()
FixIntVector2 current = new FixIntVector2(0, 0);
FixIntVector2 target = new FixIntVector2(10, 5);
FixInt speedPerFrame = 0.1f;

current = FixIntVector2.MoveTowards(current, target, speedPerFrame);
FixInt sqrDistance = (target - current).sqrMagnitude;

// 表现层
transform.position = current.ToVector2();

7. FixIntVector3

常用静态向量:zerooneforwardbackupdownleftright

分类 API
插值移动 LerpLerpUnclampedMoveTowards
向量计算 DotCrossDistanceAngleSignedAngle
投影反射 ProjectProjectOnPlaneReflect
约束比较 ClampMagnitudeMagnitudeSqrMagnitudeMinMax
Unity 转换 ToVector3()
FixIntVector3 forward = new FixIntVector3(0, 0, 1);
FixIntVector3 right = new FixIntVector3(1, 0, 0);

FixInt dot = FixIntVector3.Dot(forward, right);
FixIntVector3 normal = FixIntVector3.Cross(forward, right);
FixIntVector3 projected = FixIntVector3.ProjectOnPlane(forward, normal);

8. FixIntRandomSeed

固定种子随机数用于生成可复现序列。只有在种子与调用顺序都相同时,结果才一致。

FixIntRandomSeed randomA = new FixIntRandomSeed(8734);
FixIntRandomSeed randomB = new FixIntRandomSeed(8734);

for (int i = 0; i < 10; i++)
{
    int a = randomA.Range(0, 10000);
    int b = randomB.Range(0, 10000);
    Debug.Assert(a == b);
}
API 说明
SeedId 当前随机种子,只读
Range(int min, int max) 返回 [min, max) 范围内的整数
Range(FixInt min, FixInt max) 使用定点范围生成结果

随机调用顺序

任一同步端多调用或少调用一次随机函数,后续整个随机序列都会错位。建议按战斗实例持有随机对象,不要在逻辑中临时创建无种子随机数。

9. 推荐实践

  1. 逻辑层全程使用 FixInt 和定点向量,表现层最后一步再转换。
  2. 使用固定逻辑帧,不要把 Time.deltaTime 直接作为权威逻辑时间。
  3. 距离比较优先使用 sqrMagnitude,减少不必要的开方运算。
  4. 所有端锁定相同代码、查找表和配置版本。
  5. 为关键战斗状态增加帧哈希或校验和,尽早发现不同步。
  6. 限制业务数值范围,避免乘法、除法和位移造成 long 溢出。

10. 常见问题

为什么显示值和输入的小数略有差异?

定点数只能表示 1 / 1024 的整数倍,输入会发生量化。这是定点表示的正常特性。

为什么不能在逻辑层频繁转回 float?

转回浮点后继续参与逻辑运算,会重新引入跨平台浮点差异,削弱使用定点数的意义。

FixInt(long) 为什么得到的值不符合预期?

long 构造函数接收的是放大后的内部值。普通整数请使用 int 构造或隐式转换。

用了定点数就一定能实现帧同步吗?

不能。定点数只解决数学路径的一部分问题。执行顺序、随机序列、物理系统、时间步长、配置和网络输入都必须保持一致。

相关链接