2D网络游戏开发(网络篇)(十九)
作者:akinggw
前言
hi,伙计们,是不是感觉前面的内容一点让你百般无聊,不要着急,今天我们就将给出一个别人编的例子。这个例子很简单,主要就是使用了我们上一张所讲解的内容。本来都不愿意拿出来分享的。但我这个人心比较好,分享就分享吧。呵呵!
如果你觉得这个例子比较好,请记得千万要给我来信,我的邮箱是 akinggw@126.com
这个例子最开始是出现在Irrlicht这个3D游戏引擎的官方主页上。文章标题叫“ A Primer For RakNet Using Irrlicht” ,中文意思就是如何在Irrlicht引擎中使用Raknet。文章比较简单,内容主要就是Raknet中的比特流和数据结构。你如果将我前面写的文章全部看懂了,理解这篇文章就相当没问题。
但在我这篇文章中,我只会讲解关于RankNet的部分,如果你对Irrlicht游戏引擎有兴趣,请参考原文:http://www.daveandrews.org/articles/irrlicht_raknet/ 。
OK,废话少说,让我们开始吧!
先看一张游戏执行的界图,你可以在http://www.daveandrews.org/articles/irrlicht_raknet/Chalkboard.zip 下载文件源代码和可执行文件。
客户端
如果你执行了游戏,你就会发现。这根本就不是游戏,它只是显示如何在每个客户端同步画图。
因此,我们先讲解我们要用到的一些参数,如果用一个数据结构来表示可以表示如下:
数据包
{
数据包类型;
鼠标开始位置;
鼠标结束位置;
};
这就是我们要传输的数据。如果想象一下,假如我要传输我自己的游戏中的角色信息。那结构就会是下面这个样子:
数据包
{
数据包类型; //角色信息
角色类型; //比如人,精灵,妖怪等等
操作方式; //比如是重新建立,更改,删除
角色所处地图; //就是它在那张地图上
角色所处地图坐标; //就是它在地图什么位置
角色生命值;
.
.
.
};
好象又扯得太远了, 我们还是来说这里吧。
#include “irrlicht.h“ #include ”PacketEnumerations.h” #include “RakNetworkFactory.h” #include “NetworkTypes.h” #include “RakClientInterface.h” #include “BitStream.h”
#include “windows.h” // for Sleep() #include “stdio.h” #include “conio.h” #include “string.h” #include “stdlib.h”
头文件是不能不要的。作者说bitstream好象还存在一些问题,所以作者建议你在项目中包括bitstream.h和bitstream.cpp这两个文件。
另外,作者也讲解了为什么要包含windows.h文件。包含windows.h文件的作用就是使用它的一个函
数Sleep()。
通过使用Sleep()函数的目的就是为了让主线程在执行的过程中,能够给Raknet更多的时间去执行。在整个游戏客户端中,我们都将在主循环中使用Sleep()函数。Sleep(1)并不能是程序暂停,但可以让处理器放弃执行另一程序。
“客户端连接“类
作者使用了一个客户端连接类来管理和 服务器的连接,数据包的处理和发送。我们首先来看一下这个类的成员函数和功能:
1. 构造函数 客户端连接类的构造函数有两个参数:用于连接的服务器IP地址(用字符串表示)和用于连接的服务器的端口号(也是用字符串表示)。这个函数的功能就是初始化Raknet网络连接并连接到服务器。
2. 拆构函数 拆构函数的功能就是关闭构造函数建立的连接,并且释放Raknet建立的网络结构。
3. 添加线坐标 这个函数的作用就是将我们为了画线时产生的坐标x,y添加到画线列表中。
4. 发送线到服务器 这个函数的作用就是发送我们画线用的坐标(或者点)到服务器。
5. 画线 这个函数与网络传输无关,主要就是将画线列表中的点连成一条线。
6. 侦听数据包 这个函数的作用就是检测从服务器传输过来的数据包,然后自动用下一个函数来处理它。
7. 处理数据包 它的作用就是从数据包中读出数据,然后显示到屏幕上,
具体类描述如下:
class ClientConnection
{
private:
list<line2d<s32>> lineList;
RakClientInterface *client;
public:
ClientConnection(char * serverIP, char * portString);
~ClientConnection();
void AddLineLocal(s32 x1, s32 y1, s32 x2, s32 y2);
void SendLineToServer(s32 x1, s32 y1, s32 x2, s32 y2);
void DrawLines(IVideoDriver * irrVideo);
void ListenForPackets();
void HandlePacket(Packet * p);
};
还有一个类,这个类来自于Irrlicht。这个类主要就是处理游戏中的一些事件,我在这里不讲解,请大家自己看。
另外,在文件开始处,要加入下面的代码:
const unsigned char PACKET_ID_LINE = 100;
这个用于设置我们的数据包类型,关于这个方面,我已经在前面进行了详细的讲解。
接下来,我们具体地看一下函数的实现。
首先,是构造函数:
ClientConnection(char * serverIP, char * portString)
: client(NULL)
{
client = RakNetworkFactory::GetRakClientInterface();
client->Connect(serverIP, atoi(portString), 0, 0, 0);
}
比较简单,我在这里就不进行讲解。
~ClientConnection()
{
client->Disconnect(300);
RakNetworkFactory: estroyRakClientInterface(client);
}
拆构函数,也比较简单。
void SendLineToServer(s32 x1, s32 y1, s32 x2, s32 y2)
{
RakNet::BitStream dataStream;
dataStream.Write(PACKET_ID_LINE);
dataStream.Write(x1);
dataStream.Write(y1);
dataStream.Write(x2);
dataStream.Write(y2);
client->Send(&dataStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0);
}
发送数据到服务器端,这个完全按照我们我们前面所讲的内容进行的。先往数据流中写数据包类型,然后才是数据。最后发送,关于Send()函数,我们前面已经讲解了。
我们再来看一下侦听函数:
void ListenForPackets()
{
Packet * p = client->Receive();
if(p != NULL) {
HandlePacket(p);
client->DeallocatePacket(p);
}
}
我们接收的时候必须先将数据包中的数据重新写入数据流,然后读取这个数据流的类型。
如果这个数据包满足我们的条件,我们就将数据一一读出,然后画线,最后退出。
这就是客户端的处理,下面我们来看一下服务器的处理过程。
服务器可以用一个控制台程序来写,下面我们来看一下这个程序应该怎样来写。
#include < acketEnumerations.h>
#include <RakNetworkFactory.h>
#include <NetworkTypes.h>
#include <RakServerInterface.h>
#include <windows.h> // for Sleep()
开始和客户端一样,首先包含Raknet的头文件。
const unsigned char PACKET_ID_LINE = 100;
定义数据包的类型。
服务器端不是使用的类,直接使用了三个函数:
1. SendLineToClients() 这个函数的目的就是将接收的信息广播给所有在线的客户端。
2. HandlePacket() 这个函数的目的就是处理数据包中的数据。
3. Main() 这个函数的目的就是建立服务器,然后在游戏循环中接收数据,处理数据。
下面,我们来具体看一下这些函数的处理过程。
void SendLineToClients(RakServerInterface * server, PlayerID clientToExclude, int x1, int y1, int x2, int y2)
{
RakNet::BitStream dataStream;
dataStream.Write(PACKET_ID_LINE);
dataStream.Write(x1);
dataStream.Write(y1);
dataStream.Write(x2);
dataStream.Write(y2);
server->Send(&dataStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, clientToExclude, true);
}
这个函数的目的就是将接受到的数据写入数据流,然后广播给在线的所有客户端。
void HandlePacket(RakServerInterface * server, Packet * p)
{
unsigned char packetID;
RakNet::BitStream dataStream((const char*)p->data, p->length, false);
dataStream.Read(packetID);
switch(packetID) {
case PACKET_ID_LINE:
int x1, y1, x2, y2;
dataStream.Read(x1);
dataStream.Read(y1);
dataStream.Read(x2);
dataStream.Read(y2);
SendLineToClients(server, p->playerId, x1, y1, x2, y2);
break;
default:
printf("Unhandled packet (not a problem): %in", int(packetID));
}
}
这个函数的作用就是从网络上得到数据,然后判断这个数据是不是我们所需要的,如果是,就将它从数据流中读出,然后用SenLineToClients函数进行处理。
最后我们来看一下main函数。
int main()
{
RakServerInterface * server = RakNetworkFactory::GetRakServerInterface();
Packet * packet = NULL;
int port = 10000;
if(server->Start(32, 0, 0, port)) {
printf("Server started successfully.n");
printf("Server is now listening on port %i.nn", port);
printf("Press a key to close server.n");
}
else {
printf("There was an error starting the server.");
system("pause");
return 0;
}
while(kbhit() == false) {
Sleep(1);
packet = server->Receive();
if(packet != NULL) {
HandlePacket(server, packet);
server->DeallocatePacket(packet);
}
}
server->Disconnect(300);
RakNetworkFactory: estroyRakServerInterface(server);
printf("Server closed successfully.n");
system("pause");
}
这个函数的作用很简单,我简单地说一下,先建立服务器, 然后进入游戏循环,接收数据,如果数据不为空,就进行处理。如果你按动任何按键就退出,关闭服务器。
这就是一个游戏的例程,很简单。 |