游戏开发论坛

 找回密码
 立即注册
搜索
查看: 25770|回复: 11

2D网络游戏开发(网络篇)(十九)

[复制链接]

108

主题

180

帖子

250

积分

中级会员

Rank: 3Rank: 3

积分
250
QQ
发表于 2006-7-4 11:04:00 | 显示全部楼层 |阅读模式
2D网络游戏开发(网络篇)(十九)

作者:antking

前言

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 &ltacketEnumerations.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);
        }
    }

这个成员函数的作用就是接收数据包,如果数据包不为空,就处理这个数据包,然后释放它。

   void HandlePacket(Packet * p)
    {
        RakNet::BitStream dataStream((const char*)p->data, p->length, false);
        unsigned char packetID;
   
        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);
        
            AddLineLocal(x1, y1, x2, y2);
        break;
        }
    }

我们再来看一下处理数据包函数,因为我们的数据是经过数据流处理后传输的。因此,我们接收的时候必须先将数据包中的数据重新写入数据流,然后读取这个数据流的类型。
如果这个数据包满足我们的条件,我们就将数据一一读出,然后画线,最后退出。

这就是客户端的处理,下面我们来看一下服务器的处理过程。

服务器可以用一个控制台程序来写,下面我们来看一下这个程序应该怎样来写。

#include <PacketEnumerations.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): %i\n", 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.\n\n", 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::DestroyRakServerInterface(server);

    printf("Server closed successfully.\n");

    system("pause");
}

这个函数的作用很简单,我简单地说一下,先建立服务器, 然后进入游戏循环,接收数据,如果数据不为空,就进行处理。如果你按动任何按键就退出,关闭服务器。

这就是一个游戏的例程,很简单。

关于更多内容请访问金桥科普网站( http://popul.jqcq.com  )游戏开发栏目,如果你需要游戏开发方面的书籍请参考金桥书城游戏频道(http://book.jqcq.com/category/1_70_740.html )。 如果你在阅读本篇文章时有什么好的建议请来信给我,我的E_mail: akinggw@126.com. 如果你在使用SDL时有什么问题,请到金桥科普网站(http://popul.jqcq.com  )游戏开发栏目,我将详细地为你解答。

[em20] [em20] [em20]

20

主题

94

帖子

103

积分

注册会员

Rank: 2

积分
103
发表于 2006-7-5 14:24:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

怎么突然冒出来一个19  前面的呢?

108

主题

180

帖子

250

积分

中级会员

Rank: 3Rank: 3

积分
250
QQ
 楼主| 发表于 2006-7-5 20:27:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

http://bbs.gameres.com/showthread.asp?threadid=58218

这里有关于Raknet的更多教程,希望大家喜欢。

108

主题

180

帖子

250

积分

中级会员

Rank: 3Rank: 3

积分
250
QQ
 楼主| 发表于 2006-7-5 20:30:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

这里是关于如何使用mysql的三篇:

http://bbs.gameres.com/showthread.asp?threadid=58276

19

主题

38

帖子

38

积分

注册会员

Rank: 2

积分
38
发表于 2006-7-5 21:21:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

这几天正找呢!谢谢!

7

主题

21

帖子

52

积分

注册会员

Rank: 2

积分
52
发表于 2006-7-6 14:27:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

是啊,(一)在哪里?

1

主题

14

帖子

14

积分

新手上路

Rank: 1

积分
14
发表于 2006-7-8 23:45:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

请问怎么我编译(servermain.cpp)会出现:
error C2664: “RakNet::BitStream::BitStream(unsigned char *,unsigned int,bool)”: 不能将参数 1 从“const char *”转换为“unsigned char *”
        与指向的类型无关;转换要求 reinterpret_cast、C 样式转换或函数样式转换

108

主题

180

帖子

250

积分

中级会员

Rank: 3Rank: 3

积分
250
QQ
 楼主| 发表于 2006-7-9 11:21:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

也许你可以试一下强制转换,我还遇到这样的情况。

1

主题

14

帖子

14

积分

新手上路

Rank: 1

积分
14
发表于 2006-7-9 12:10:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

这样就会出现错误:
RakNet::BitStream dataStream((const char*)p->data, p->length, false);
去掉(const char*)就可以编译得过去:
RakNet::BitStream dataStream(p->data, p->length, false);
但是和客户端连接不了...

108

主题

180

帖子

250

积分

中级会员

Rank: 3Rank: 3

积分
250
QQ
 楼主| 发表于 2006-7-9 14:45:00 | 显示全部楼层

Re:2D网络游戏开发(网络篇)(十九)

你使用的是什么IP地址,在自己那台机子上调试是用127.0.0.1,这样才行。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-24 21:48

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表