定点数
@esengine/ecs-framework-math 提供确定性定点数计算,专为帧同步 (Lockstep) 设计。定点数在所有平台上保证产生完全相同的计算结果。
为什么需要定点数?
Section titled “为什么需要定点数?”浮点数在不同平台上可能产生不同的舍入结果:
// 浮点数:不同平台可能得到不同结果const a = 0.1 + 0.2; // 0.30000000000000004 (某些平台) // 0.3 (其他平台)
// 定点数:所有平台结果一致const x = Fixed32.from(0.1);const y = Fixed32.from(0.2);const z = x.add(y); // raw = 19661 (所有平台)| 特性 | 浮点数 | 定点数 |
|---|---|---|
| 跨平台一致性 | ❌ 可能不同 | ✅ 完全一致 |
| 网络同步模式 | 状态同步 | 帧同步 (Lockstep) |
| 适用游戏类型 | FPS、RPG | RTS、MOBA、格斗 |
npm install @esengine/ecs-framework-mathFixed32 定点数
Section titled “Fixed32 定点数”Q16.16 格式:16 位整数 + 16 位小数,范围 ±32767.99998。
import { Fixed32 } from '@esengine/ecs-framework-math';
// 从浮点数创建const speed = Fixed32.from(5.5);
// 从整数创建(无精度损失)const count = Fixed32.fromInt(10);
// 从原始值创建(网络接收后使用)const received = Fixed32.fromRaw(360448); // 等于 5.5
// 预定义常量Fixed32.ZERO // 0Fixed32.ONE // 1Fixed32.HALF // 0.5Fixed32.PI // πFixed32.TWO_PI // 2πFixed32.HALF_PI // π/2const a = Fixed32.from(10);const b = Fixed32.from(3);
const sum = a.add(b); // 13const diff = a.sub(b); // 7const prod = a.mul(b); // 30const quot = a.div(b); // 3.333...const mod = a.mod(b); // 1const neg = a.neg(); // -10const abs = neg.abs(); // 10const x = Fixed32.from(5);const y = Fixed32.from(3);
x.eq(y) // false - 等于x.ne(y) // true - 不等于x.lt(y) // false - 小于x.le(y) // false - 小于等于x.gt(y) // true - 大于x.ge(y) // true - 大于等于
x.isZero() // falsex.isPositive() // truex.isNegative() // false// 平方根(牛顿迭代法,确定性)const sqrt = Fixed32.sqrt(Fixed32.from(16)); // 4
// 取整Fixed32.floor(Fixed32.from(3.7)) // 3Fixed32.ceil(Fixed32.from(3.2)) // 4Fixed32.round(Fixed32.from(3.5)) // 4
// 范围限制Fixed32.clamp(value, min, max)
// 线性插值Fixed32.lerp(from, to, t)
// 最大/最小值Fixed32.min(a, b)Fixed32.max(a, b)const value = Fixed32.from(3.14159);
// 转为浮点数(用于渲染)const float = value.toNumber(); // 3.14159
// 获取原始值(用于网络传输)const raw = value.toRaw(); // 205887
// 转为整数(向下取整)const int = value.toInt(); // 3FixedVector2 定点数向量
Section titled “FixedVector2 定点数向量”不可变的 2D 向量类,所有运算返回新实例。
import { FixedVector2, Fixed32 } from '@esengine/ecs-framework-math';
// 从浮点数创建const pos = FixedVector2.from(100, 200);
// 从原始值创建(网络接收后使用)const received = FixedVector2.fromRaw(6553600, 13107200);
// 从 Fixed32 创建const vec = new FixedVector2(Fixed32.from(10), Fixed32.from(20));
// 预定义常量FixedVector2.ZERO // (0, 0)FixedVector2.ONE // (1, 1)FixedVector2.RIGHT // (1, 0)FixedVector2.LEFT // (-1, 0)FixedVector2.UP // (0, 1)FixedVector2.DOWN // (0, -1)const a = FixedVector2.from(3, 4);const b = FixedVector2.from(1, 2);
// 基本运算const sum = a.add(b); // (4, 6)const diff = a.sub(b); // (2, 2)const scaled = a.mul(Fixed32.from(2)); // (6, 8)const divided = a.div(Fixed32.from(2)); // (1.5, 2)
// 向量积const dot = a.dot(b); // 3*1 + 4*2 = 11const cross = a.cross(b); // 3*2 - 4*1 = 2
// 长度const lenSq = a.lengthSquared(); // 25const len = a.length(); // 5
// 归一化const norm = a.normalize(); // (0.6, 0.8)
// 距离const dist = a.distanceTo(b); // sqrt((3-1)² + (4-2)²)import { FixedMath } from '@esengine/ecs-framework-math';
const vec = FixedVector2.from(1, 0);const angle = Fixed32.from(Math.PI / 2); // 90度
// 旋转向量const rotated = vec.rotate(angle); // (0, 1)
// 围绕点旋转const center = FixedVector2.from(5, 5);const around = vec.rotateAround(center, angle);
// 获取向量角度const vecAngle = vec.angle();
// 两向量夹角const between = vec.angleTo(other);
// 从角度创建单位向量const dir = FixedVector2.fromAngle(angle);
// 从极坐标创建const polar = FixedVector2.fromPolar(length, angle);const pos = FixedVector2.from(100.5, 200.5);
// 转为浮点对象(用于渲染)const obj = pos.toObject(); // { x: 100.5, y: 200.5 }
// 转为数组const arr = pos.toArray(); // [100.5, 200.5]
// 获取原始值(用于网络传输)const raw = pos.toRawObject(); // { x: 6586368, y: 13140992 }FixedMath 三角函数
Section titled “FixedMath 三角函数”使用查找表实现确定性三角函数。
import { FixedMath, Fixed32 } from '@esengine/ecs-framework-math';
const angle = Fixed32.from(Math.PI / 6); // 30度
// 三角函数const sin = FixedMath.sin(angle); // 0.5const cos = FixedMath.cos(angle); // 0.866const tan = FixedMath.tan(angle); // 0.577
// 反三角函数const atan = FixedMath.atan2(y, x);const asin = FixedMath.asin(value);const acos = FixedMath.acos(value);
// 角度规范化到 [-π, π]const normalized = FixedMath.normalizeAngle(angle);
// 角度差(最短路径)const delta = FixedMath.angleDelta(from, to);
// 角度插值(处理 360° 环绕)const lerped = FixedMath.lerpAngle(from, to, t);
// 弧度/角度转换const deg = FixedMath.radToDeg(rad);const rad = FixedMath.degToRad(deg);1. 全程使用定点数计算
Section titled “1. 全程使用定点数计算”// ✅ 正确:所有游戏逻辑使用定点数function calculateDamage(baseDamage: Fixed32, multiplier: Fixed32): Fixed32 { return baseDamage.mul(multiplier);}
// ❌ 错误:混用浮点数function calculateDamage(baseDamage: number, multiplier: number): number { return baseDamage * multiplier; // 可能不一致}2. 只在渲染时转换为浮点数
Section titled “2. 只在渲染时转换为浮点数”// 游戏逻辑层const position: FixedVector2 = calculatePosition(input);
// 渲染层const { x, y } = position.toObject();sprite.position.set(x, y);3. 使用原始值进行网络传输
Section titled “3. 使用原始值进行网络传输”// ✅ 正确:传输整数原始值const raw = position.toRawObject();send(JSON.stringify(raw));
// ❌ 错误:传输浮点数const float = position.toObject();send(JSON.stringify(float)); // 可能丢失精度4. 使用 FixedMath 进行三角运算
Section titled “4. 使用 FixedMath 进行三角运算”// ✅ 正确:使用查找表const direction = FixedVector2.fromAngle(FixedMath.atan2(dy, dx));
// ❌ 错误:使用 Math 库const angle = Math.atan2(dy.toNumber(), dx.toNumber()); // 不确定API 导出
Section titled “API 导出”import { Fixed32, FixedVector2, FixedMath, type IFixed32, type IFixedVector2} from '@esengine/ecs-framework-math';