帧同步和状态同步区别
状态同步:发操作,收状态
帧同步:发操作,收操作
逻辑严格排序
经常会有需要排序的列表或者数组,比如攻击距离自己最近的敌人,这时候就需要将身边的敌人进行距离排序,一般来说只要排序距离就行了,但是如果出现两个敌人距离一样的时候,就会导致在不同的机器上选择的敌人是不同的。所以排序一定要排到id为止,才能避免出现条件相同顺序不同的问题。
浮点数与定点数
浮点数:精度为7位到8位小数,有一定的误差,不同的硬件软件平台也会有些许差异,随着游戏进行,这一点点误差会逐渐放大,导致不同客户端的计算结果不一致
定点数:把整数部分和小数部分拆分开来,都用整数的形式表示,小数点位置固定,缺点是占用空间更大
实现自定义定点数
using System;
/// <summary>
/// 自定义64位定点数
/// </summary>
[Serializable]
public struct Fixed64 : IEquatable<Fixed64>, IComparable, IComparable<Fixed64>
{
public long value;
//小数部分位数
private const int FRACTIONALBITS = 12;
private const long ONE = 1L << FRACTIONALBITS;
public static Fixed64 Zero = new Fixed64(0);
/// <summary>
/// ֱ直接对value赋值
/// </summary>
Fixed64(long value)
{
this.value = value;
}
/// <summary>
/// 传入具体数字的构造函数
/// </summary>
public Fixed64(int value)
{
this.value = value * ONE;
}
/// <summary>
/// 重载运算符
/// </summary>
public static Fixed64 operator +(Fixed64 a, Fixed64 b)
{
return new Fixed64(a.value + b.value);
}
public static Fixed64 operator -(Fixed64 a,Fixed64 b)
{
return new Fixed64(a.value - b.value);
}
public static Fixed64 operator*(Fixed64 a,Fixed64 b)
{
//直接相乘后面会多出0,所以这里要右移
return new Fixed64((a.value >> FRACTIONALBITS) * b.value);
}
public static Fixed64 operator/(Fixed64 a,Fixed64 b)
{
return new Fixed64((a.value << FRACTIONALBITS) / b.value);
}
public static bool operator ==(Fixed64 a,Fixed64 b)
{
return a.value == b.value;
}
public static bool operator !=(Fixed64 a,Fixed64 b)
{
return !(a == b);
}
public static bool operator>(Fixed64 a,Fixed64 b)
{
return a.value > b.value;
}
public static bool operator <(Fixed64 a, Fixed64 b)
{
return a.value < b.value;
}
public static bool operator >=(Fixed64 a, Fixed64 b)
{
return a.value >= b.value;
}
public static bool operator <=(Fixed64 a, Fixed64 b)
{
return a.value <= b.value;
}
/// <summary>
/// 显式强制类型转换,Fixed64转换为long类型
/// </summary>
public static explicit operator long(Fixed64 value)
{
return value.value >> FRACTIONALBITS;
}
public static explicit operator Fixed64(long value)
{
return new Fixed64(value);
}
public static explicit operator float(Fixed64 value)
{
return (float)value.value / ONE;
}
public static explicit operator Fixed64(float value)
{
return new Fixed64((long)(value * ONE));
}
public int CompareTo(object obj)
{
return value.CompareTo(obj);
}
public int CompareTo(Fixed64 other)
{
return value.CompareTo(other.value);
}
public bool Equals(Fixed64 other)
{
return value == other.value;
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is Fixed64 && ((Fixed64)obj).value == value;
}
public override string ToString()
{
return ((float)this).ToString();
}
}
实现自定义定点数向量
using System;
/// <summary>
/// 自定义定点数三维向量
/// </summary>
public struct Fixed64Vector3 : IEquatable<Fixed64Vector3>
{
public Fixed64 x;
public Fixed64 y;
public Fixed64 z;
public Fixed64Vector3(Fixed64 x, Fixed64 y, Fixed64 z)
{
this.x = x;
this.y = y;
this.z = z;
}
public Fixed64 this[int index]
{
get
{
if (index == 0)
return x;
else if (index == 1)
return y;
else
return z;
}
set
{
if (index == 0)
x = value;
else if (index == 1)
y = value;
else
y = value;
}
}
public static Fixed64Vector3 Zero
{
get { return new Fixed64Vector3(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero); }
}
public static Fixed64Vector3 operator +(Fixed64Vector3 a, Fixed64Vector3 b)
{
Fixed64 x = a.x + b.x;
Fixed64 y = a.y + b.y;
Fixed64 z = a.z + b.z;
return new Fixed64Vector3(x, y, z);
}
public static Fixed64Vector3 operator -(Fixed64Vector3 a, Fixed64Vector3 b)
{
Fixed64 x = a.x - b.x;
Fixed64 y = a.y - b.y;
Fixed64 z = a.z - b.z;
return new Fixed64Vector3(x, y, z);
}
public static Fixed64Vector3 operator *(Fixed64 d, Fixed64Vector3 a)
{
Fixed64 x = a.x * d;
Fixed64 y = a.y * d;
Fixed64 z = a.z * d;
return new Fixed64Vector3(x, y, z);
}
public static Fixed64Vector3 operator *(Fixed64Vector3 a, Fixed64 d)
{
Fixed64 x = a.x * d;
Fixed64 y = a.y * d;
Fixed64 z = a.z * d;
return new Fixed64Vector3(x, y, z);
}
public static Fixed64Vector3 operator /(Fixed64Vector3 a, Fixed64 d)
{
Fixed64 x = a.x / d;
Fixed64 y = a.y / d;
Fixed64 z = a.z / d;
return new Fixed64Vector3(x, y, z);
}
public static bool operator ==(Fixed64Vector3 lhs, Fixed64Vector3 rhs)
{
return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z;
}
public static bool operator !=(Fixed64Vector3 lhs, Fixed64Vector3 rhs)
{
return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z;
}
/// <summary>
/// 进行左移和右移是为了增加哈希值的混淆性和分布性,参考Vector3中的写法
/// </summary>
public override int GetHashCode()
{
return x.GetHashCode() ^ (y.GetHashCode() << 2) ^ (z.GetHashCode() >> 2);
}
public override bool Equals(object obj)
{
return ((Fixed64Vector3)obj) == this;
}
public override string ToString()
{
return string.Format("x:{0} y:{1} z:{2}", x, y, z);
}
public bool Equals(Fixed64Vector3 other)
{
return this == other;
}
}
帧同步流程
逻辑帧:游戏被划分为连续的游戏帧,每一帧就是一个时间片段,帧同步的目标是把每一帧中所有客户端的操作同步起来
客户端-服务端架构:每个客户端把自己的一个或多个操作发给服务端,服务端把这些操作应用到游戏状态中,并将结果发给所有客户端,保持一致性,上图中方框表示服务端
严格帧同步:每个关键帧只有当服务器集齐了所有玩家的操作指令,才可以进行转发,进入下一个关键帧,否则就要等待最慢的玩家。
上图表示严格帧同步流程,服务器和客户端约定每5帧同步一次,这里每一帧的时间间隔由服务器决定,UPDATE 0表示服务器发的第0帧数据,CTRL 0表示客户端发的第0帧操作,服务器在第5帧开始前收到了客户端A,B的数据,进行打包再转发给A,B。服务端在第10帧开始时没有收到所有客户端的数据,开始等待,直到收到所有客户端的数据。
乐观帧同步:服务器定时广播操作指令,而不必等待慢的客户端
帧同步服务端代码编写相对简单,只是转发数据