游戏中的帧同步要求的计算一致性——定点数(Fixed Point)
最近做了一款帧同步游戏,其寻路算法采用了RVO算法。但是由于是移动端的游戏。需要在不同的设备上运行,其所有运算必须符合一致性——即所有客户端运算出来的结果必须一致。但是由于浮点数的特性,具有误差,且在不同设备上误差更为明显。因此所有的算法都不能采用浮点数来运算。一般来说,这些游戏内大多采用*1000 再/1000的方式来实现帧同步的计算一致性。
但是由于RVO算法,是用来寻路的,其中大量的向量运算,其算法复杂度非常高。而向量运算是一个特例。其中比如normalize,是不能放大的——因为放大多少倍最后的结果也只能是(1,0)(0,1)(0,0).还有一些 sin函数 cos函数是没有办法先乘后除的。在这种情况下,采用老式的方式已经非常不便了(大量的临时变量和成员变量你已经记不清哪些放大过哪些是标准值了)。因此,我打算采用定点库。网上的定点数库大多重载不全,而且没有找到跨语言的。。。所以就自己实现了一个。直接上代码(代码是c#的)。
(其中的sin cos 的实现请参考上一篇文章)
using System.Collections; using System; using System.Globalization; namespace FP { public struct FPoint { public static int Fix_Fracbits = 16; public static FPoint Zero = new FPoint(0); internal Int64 m_Bits; public FPoint(int x) { m_Bits = (x<<Fix_Fracbits); } public FPoint(float x) { m_Bits =(Int64)((x) * (1 << Fix_Fracbits)); //x*(((Int64)(1)<<Fix_Fracbits)) } public FPoint(Int64 x) { m_Bits = ((x) * (1 << Fix_Fracbits)); } public Int64 GetValue() { return m_Bits; } //******************* + ************************** public static FPoint operator +( FPoint p1, FPoint p2) { FPoint tmp ; tmp.m_Bits = p1.m_Bits + p2.m_Bits; return tmp; } public static FPoint operator +(FPoint p1, int p2) { FPoint tmp ; tmp.m_Bits = p1.m_Bits + (Int64)(p2<<Fix_Fracbits); return tmp; } public static FPoint operator +(int p1, FPoint p2) { return p2+p1; } public static FPoint operator +(FPoint p1, Int64 p2) { FPoint tmp ; tmp.m_Bits = p1.m_Bits + p2<<Fix_Fracbits; return tmp; } public static FPoint operator +(Int64 p1, FPoint p2) { return p2+p1; } public static FPoint operator +(FPoint p1, float p2) { FPoint tmp ; tmp.m_Bits = p1.m_Bits + (Int64)(p2*(1<<Fix_Fracbits)); return tmp; } public static FPoint operator +(float p1, FPoint p2) { FPoint tmp = p2 + p1; return tmp; } //******************* - ************************** public static FPoint operator -(FPoint p1, FPoint p2) { FPoint tmp ; tmp.m_Bits = p1.m_Bits - p2.m_Bits; return tmp; } public static FPoint operator -(FPoint p1, int p2) { FPoint tmp ; tmp.m_Bits = p1.m_Bits - (Int64)(p2<<Fix_Fracbits); return tmp; } public static FPoint operator -(int p1, FPoint p2) { FPoint tmp ; tmp.m_Bits = (p1<<Fix_Fracbits) - p2.m_Bits; return tmp; } public static FPoint operator -(FPoint p1, Int64 p2) { FPoint tmp ; tmp.m_Bits = p1.m_Bits - (p2<<Fix_Fracbits); return tmp; } public static FPoint operator -(Int64 p1, FPoint p2) { FPoint tmp; tmp.m_Bits = (p1<<Fix_Fracbits) - p2.m_Bits; return tmp; } public static FPoint operator -(float p1, FPoint p2) { FPoint tmp; tmp.m_Bits = (Int64)(p1*(1<<Fix_Fracbits)) - p2.m_Bits; return tmp; } public static FPoint operator -(FPoint p1, float p2) { FPoint tmp; tmp.m_Bits = p1.m_Bits - (Int64)(p2*(1<<Fix_Fracbits)); return tmp; } //******************* * ************************** public static FPoint operator *(FPoint p1, FPoint p2) { FPoint tmp ; tmp.m_Bits = ((p1.m_Bits) * (p2.m_Bits)) >> (Fix_Fracbits); return tmp; } public static FPoint operator *(int p1, FPoint p2) { FPoint tmp ; tmp.m_Bits = p1 * p2.m_Bits; return tmp; } public static FPoint operator *(FPoint p1, int p2) { return p2 * p1; } public static FPoint operator *(FPoint p1, float p2) { FPoint tmp; tmp.m_Bits = (Int64)(p1.m_Bits*p2); return tmp; } public static FPoint operator *(float p1, FPoint p2) { FPoint tmp ; tmp.m_Bits = (Int64)(p1 * p2.m_Bits); return tmp; } //******************* / ************************** public static FPoint operator / (FPoint p1, FPoint p2) { FPoint tmp ; if(p2 == FPoint.Zero) { UnityEngine.Debug.LogError("/0"); tmp.m_Bits = Zero.m_Bits; } else { tmp.m_Bits = (p1.m_Bits) * (1<<Fix_Fracbits) / (p2.m_Bits); } return tmp; } public static FPoint operator / (FPoint p1, int p2) { FPoint tmp ; if(p2 == 0) { UnityEngine.Debug.LogError("/0"); tmp.m_Bits = Zero.m_Bits; } else { tmp.m_Bits = p1.m_Bits/(p2); } return tmp; } public static FPoint operator /(int p1, FPoint p2) { FPoint tmp ; if(p2 == Zero) { UnityEngine.Debug.LogError("/0"); tmp.m_Bits = Zero.m_Bits; } else { Int64 tmp2 = ((Int64)p1 << Fix_Fracbits << Fix_Fracbits); tmp.m_Bits = tmp2/(p2.m_Bits); } return tmp; } public static FPoint operator / (FPoint p1, Int64 p2) { FPoint tmp; if(p2 == 0) { UnityEngine.Debug.LogError("/0"); tmp.m_Bits = Zero.m_Bits; } else { tmp.m_Bits = p1.m_Bits/(p2); } return tmp; } public static FPoint operator /(Int64 p1, FPoint p2) { FPoint tmp; if(p2 == Zero) { UnityEngine.Debug.LogError("/0"); tmp.m_Bits = Zero.m_Bits; } else { if(p1>Int32.MaxValue ||p1 { tmp.m_Bits = 0; return tmp; } tmp.m_Bits = (p1<<Fix_Fracbits)/(p2.m_Bits); } return tmp; } public static FPoint operator /(float p1, FPoint p2) { FPoint tmp ; if(p2 == Zero) { UnityEngine.Debug.LogError("/0"); tmp.m_Bits = Zero.m_Bits; } else { Int64 tmp1 = (Int64)p1 * ((Int64)1 << Fix_Fracbits << Fix_Fracbits); tmp.m_Bits = (tmp1)/(p2.m_Bits); } return tmp; } public static FPoint operator /(FPoint p1, float p2) { FPoint tmp; if(p2>-0.000001f && p2<0.000001f) { UnityEngine.Debug.LogError("/0"); tmp.m_Bits = Zero.m_Bits; } else { tmp.m_Bits = (p1.m_Bits<<Fix_Fracbits)/((Int64)(p2*(1<<Fix_Fracbits))); } return tmp; } public static FPoint Sqrt(FPoint p1) { FPoint tmp; Int64 ltmp = p1.m_Bits * (1 << Fix_Fracbits); tmp.m_Bits = (Int64)Math.Sqrt(ltmp); return tmp; } public static bool operator >(FPoint p1, FPoint p2) { return (p1.m_Bits>p2.m_Bits)?true:false; } public static bool operator <(FPoint p1, FPoint p2) { return (p1.m_Bits } public static bool operator <=(FPoint p1, FPoint p2) { return (p1.m_Bits<=p2.m_Bits)?true:false; } public static bool operator >=(FPoint p1, FPoint p2) { return (p1.m_Bits>=p2.m_Bits)?true:false; } public static bool operator !=(FPoint p1, FPoint p2) { return (p1.m_Bits!=p2.m_Bits)?true:false; } public static bool operator ==(FPoint p1, FPoint p2) { return (p1.m_Bits==p2.m_Bits)?true:false; } public static bool Equals(FPoint p1, FPoint p2) { return (p1.m_Bits==p2.m_Bits)?true:false; } public bool Equals(FPoint right) { if(m_Bits == right.m_Bits) { return true; } return false; } public static bool operator >(FPoint p1, float p2) { return (p1.m_Bits>(p2*(1<<Fix_Fracbits)))?true:false; } public static bool operator <(FPoint p1, float p2) { return (p1.m_Bits<<Fix_Fracbits))?true:false; } public static bool operator <=(FPoint p1, float p2) { return (p1.m_Bits<=p2*(1<<Fix_Fracbits))?true:false; } public static bool operator >=(FPoint p1, float p2) { return (p1.m_Bits>=p2*(1<<Fix_Fracbits))?true:false; } public static bool operator !=(FPoint p1, float p2) { return (p1.m_Bits!=p2*(1<<Fix_Fracbits))?true:false; } public static bool operator ==(FPoint p1, float p2) { return (p1.m_Bits==p2*(1<<Fix_Fracbits))?true:false; } public static FPoint Cos (FPoint p1) { return FP.TrigonometricFunction.Cos(p1); } public static FPoint Sin (FPoint p1) { return FP.TrigonometricFunction.Sin(p1); } public static FPoint Max() { FPoint tmp ; tmp.m_Bits = Int64.MaxValue; return tmp; } public static FPoint Max(FPoint p1 ,FPoint p2) { return p1.m_Bits>p2.m_Bits?p1:p2; } public static FPoint Min(FPoint p1 ,FPoint p2) { return p1.m_Bits } public static FPoint Precision() { FPoint tmp ; tmp.m_Bits = 1; return tmp; } public static FPoint MaxValue() { FPoint tmp ; tmp.m_Bits = Int64.MaxValue; return tmp; } public static FPoint Abs(FPoint P1) { FPoint tmp ; tmp.m_Bits = Math.Abs (P1.m_Bits); return tmp; } public static FPoint operator -(FPoint p1) { FPoint tmp ; tmp.m_Bits = -p1.m_Bits; return tmp; } public float ToFloat() { return m_Bits/(float)(1<<Fix_Fracbits); } public int ToInt() { return (int)(m_Bits >> (Fix_Fracbits)); } public string ToString() { double tmp = (double)m_Bits/(double)(1<<Fix_Fracbits); return tmp.ToString(); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)