2D
网络游戏开发(网络篇)(八)
作者:
akinggw
前言
已经写到
raknet
编程的第八篇了,在前面的内容当中,我们讲解了
raknet
如何传输普通的信息,可光是这些是不够的。因为一个游戏不可能就只传输这些信息,它们可能会传输数据结构。
而我们今天的内容就和数据结构有关。
建立一个数据包
建立什么样的数据包,或是在数据包中要包含什么样的信息完全由你想发送什么样的数据决定。而我们在游戏当中到底要发送什么样的信息呢?比如,我们要发送一个游戏玩家
Mine
在游戏世界中某一时间的坐标,那么我们就需要下面的数据:
l
玩家
Mine
在游戏世界中的坐标,包含
3
个浮点值:
x,y,z
。你可以用一个矢量值来表示。
l
用一些方法来确定所有玩家都知道玩家
Mine
的存在。关于这个问题,可以直接采用
Network ID Generator
类来完成。我们假设类
Mine
从
NetworkObject
继承而来,那么其它所有玩家就不得不得到并存储
Mine
的
ObjectID.
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; //
这将分配一个类型
id
到
PacketEnumerations.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 objected
和
PlayerID playerId
。为什么不使用一些更具有描述性的名字,比如
mineId
和
mineOwerId?
我以我的实际经练告诉你,在实际运用中使用描述性的名字是不明智的。因为虽然你知道这些数据包中的参数意味着什么,但它们自己却并不知道。使用通用名字的优点是你可以通过粘贴,复制来处理你的数据包,而不用重新给这些数据包装填数据。当你有许多的数据包的时候,你将在一个很大的游戏中,这种方法保存了许多分歧。
巢结构
关于巢结构并没有多少问题,值得注意的是第一个字节总是决定了数据包的类型。
#pragma pack(1)
struct A
{
unsigned char typeId; // ID_A
};
#pragma pack(1)
struct B
{
unsigned char typeId; //ID_A
};