2D网络游戏开发(网络篇)(八)1

来源:互联网 发布:打车软件司机端 编辑:程序博客网 时间:2024/04/30 03:02
2D网络游戏开发(网络篇)(八)
 
作者:akinggw
 
前言
 
已经写到raknet编程的第八篇了,在前面的内容当中,我们讲解了raknet如何传输普通的信息,可光是这些是不够的。因为一个游戏不可能就只传输这些信息,它们可能会传输数据结构。
而我们今天的内容就和数据结构有关。
 
建立一个数据包
 
建立什么样的数据包,或是在数据包中要包含什么样的信息完全由你想发送什么样的数据决定。而我们在游戏当中到底要发送什么样的信息呢?比如,我们要发送一个游戏玩家Mine在游戏世界中某一时间的坐标,那么我们就需要下面的数据:
l        玩家Mine在游戏世界中的坐标,包含3个浮点值:x,y,z。你可以用一个矢量值来表示。
l        用一些方法来确定所有玩家都知道玩家Mine的存在。关于这个问题,可以直接采用Network ID Generator类来完成。我们假设类MineNetworkObject继承而来,那么其它所有玩家就不得不得到并存储MineObjectID.
l        谁是玩家Mine,关于这个问题请参考players,PlayerID。如果你是在服务器上玩游戏,我们可以将你的playerID值设置成一些傀儡值,比如255。而如果在客户端,你可以用GetPlayerID得到它。
l        当一个玩家Mine在某个地方,但可能10秒以后,它就到达另一个地方,因此最重要的是我们得到那相同的时间,这样做的好处就是避免玩家Mine在不同的计算机上拥有不同的时间。幸运的是,Raknet可以使用Timestamping来处理这个问题。
 
使用结构或比特流
 
最终,你发送数据将发送用户的角色的流。有两种方法可以编码你要发送的数据。一种方法是建立一个结构,另一个方法是内建一个比特流类。
 
建立一个结构的优点就是能够非常容易地改变结构并且看到你实际发送了什么数据。这样,发送者和接收者就能够共享结构中相同的源文件,这样做就避免了不必要的错误。建立结构的缺点就是你将不得不经常改变和重新编译许多文件。也将失去在比特流中传输时压缩的机会。
 
使用比特流的优点就是你不用改变任何其它的外部文件。简单地建立一个比特流,把你想发送的数据写入比特流,然后发送。你也可以通过压缩来读写数据,然后通过bool值来判断数据是否被压缩。比特流的缺点是你现在一旦范了一个错误,那就不容易那么改变。
 
用结构建立一个包
 
打开NetworkStructures.h
在文件中间将有一个大的部分,像下面这样:
// -----------------------------------------
//你的结构在下面
//------------------------------------------
 
// ------------------------------------
//你的结构在这里
// ---------------------------------
 
// -----------------------------------------------
// 你的结构在上面
// --------------------------------------
 
有两个通用的形式用于结构中,一个是带有timestamping,一个是没有。
 
不带Timestamping的形式
 
#pragma pack(1)
struct structName
{
 unsigned char typeId;   //把你的结构类型放在这里
 //你的数据放在这里
}
 
Timestamping的形式
 
#pragma pack(1)
struct structName
{
 unsigned char useTimeStamp;   // 分配它到ID_TIMESTAMP
 unsigned long timeStamp;      // 把通过timeGetTime()返回的系统时间放在这里
 unsigned char typeId;         // 结构类型放在这里
 //你的数据放在这里
}
对于前面我们的角色,我们在这里想使用timestamping,因此,结构填充的结果如下:
pragma pack(1)
struct structName
{
 unsigned char useTimeStamp; //分配这个到ID_TIMESTAMP
 unsigned long timestamp;   //把由getTime()得到的系统时间放在这里
 unsigned char typeId;      //这将分配一个类型idPacketEnumerations.h中,这里,我们可以说是ID_SET_TIMED_MINE
 float x,y,z;              //角色Mine的坐标
 ObjectID objected;       //角色Mine的目标ID,用于在不同计算机上参考Mine的通用方法
 PlayerID playerId;       //角色Mine拥有的玩家的PlayerId
};
正如上面我们写的结构,我们添加typeId的目的就是当我们得到一个数据流到达时,我们将知道我们看见了什么。因此最后一步就是在PacketEnumerations.h中添加ID_SET_TIMED_MINE.
注意你不能在结构中直接或间接地包含指针。
 
你将注意到我在结构中调用了ObjectID objectedPlayerID playerId。为什么不使用一些更具有描述性的名字,比如mineIdmineOwerId? 我以我的实际经练告诉你,在实际运用中使用描述性的名字是不明智的。因为虽然你知道这些数据包中的参数意味着什么,但它们自己却并不知道。使用通用名字的优点是你可以通过粘贴,复制来处理你的数据包,而不用重新给这些数据包装填数据。当你有许多的数据包的时候,你将在一个很大的游戏中,这种方法保存了许多分歧。
 
巢结构 
 
关于巢结构并没有多少问题,值得注意的是第一个字节总是决定了数据包的类型。
 
#pragma pack(1)
struct A
{
 unsigned char typeId; // ID_A
};
#pragma pack(1)
struct B
{
 unsigned char typeId; //ID_A
};