浅谈网络游戏的设计——服务器端编程

浅谈网络游戏的设计——服务器端编程

本游戏服务器端操作系统采用UNIX,因为UNIX是标准的服务器操作系统,可保证网络游戏的稳定性。因此,以下所有的编程都将针对UNIX进行。
  服务器端的整体构架如下:通讯模块,消息传递模块,游戏规则模块,线程管理模块,游戏世界管理模块。

 

 

通讯模块:

 

 

通讯模块主要实现与客户端的通讯功能,实际上,通讯模块就是对套结字Socket的封装。Socket是UNIX下的网络通讯基础,对于一个Socket我们可以对其进行读写操作,读入的数据来自客户端,写入的数据可供客户端读取。

 

Socket主要有阻塞套接字和无阻塞套接字两种,对于无阻塞套结字,每次读写后,不管读写的字节数是否达到要求,都立即返回;而对于阻塞套结字,若读写字节数不够,函数将被阻塞,直到所有待处理的数据都处理完毕才返回。可以看出,若采用无阻塞套结字,则将使网络传输变得很不稳定,在网络环境不好时很难控制传输。因此,对于我们的系统,将采用阻塞模式。

 

我们下一个面临的问题是怎么知道何时读入,如果在不合适的时候从阻塞套接字读入数据,那线程很可能将被阻塞,这里采用了select——多路复用技术,原理是对我们的socket进行监视,如果socket上有读事件发生,将调用消息模块发送消息给socket的携带对象,对其进行读写。

 

以下是对Socket的简单封装:

 

class SSocket

 

{

 

  fd_set *SockSet;              //fd_set,也就是我们的select监听集合

 

  char IsListenSocket;      //是否是监听套接字

 

  int ServerPort;          //监听套接字的监听端口号

 

  struct sockaddr_in addr;  //地址信息

 

 

 

public:

 

  SSocket();

 

  ~SSocket();

 

 

 

  int Socket;             //socket

 

  int CreateListenSocket(fd_set *sset, int Port, char* addr); //初始//化一个监听socket

 

  int AcceptSocket(int listen_fd, fd_set *sset);  //初始化一个非监听socket

 

 int CloseSocket();                      //关闭 socket

 

 

 

  int SendBuf(void *buf, int size);     //发送数据

 

  int RecvBuf(void *buf, int size);  //接收数据

 

 

 

  int SetSocketFd();                      //将socket加入到监听集合中

 

  int ClrSocketFd();                      //将socket从监听集合中清除

 

 

 

};

 

通过对socket的封装,我们完成了通讯模块的基本任务。下一步要做的是传输网络上的消息,此时需要对SSocket继续封装,首先定义一个消息结构体,然后就是读写消息,消息结构根据各个游戏不同,在此就不再深入探讨了。

 

 

 

 

 

线程管理模块

 

 

 

由于我们用了阻塞模式,这就意味着必须要为各个socket创建单独的线程,否则很可能会引起服务器端停止工作。因此我们需要封装线程,封装内容主要包括:线程函数地址,线程开始时间,线程上次阻塞时间,线程最大阻塞时间,线程start方法,线程stop方法。线程stop方法可以通过发送信号给线程来达到杀死线程的目的。

 

线程类封装完成后,我们就可以开始编写管理模块代码了。管理模块其实也是一个线程,其第一功能就是监视各个线程是否阻塞超时,通过察看线程上次阻塞时间和最大阻塞时间来完成。一旦发现当前时间超过线程最大阻塞时间加上线程上次阻塞时间,既可断定线程阻塞超时,此时就需要kill该线程。另外,其他一些根线程有关的管理方法都有此模块负责。

 

 

 

 

 

消息传递模块:

 

 

 

对象、模块之间怎样传递消息,这也是服务器端设计的重点。举一个简化的例子说明如下:若某一玩家对另一玩家发了一条信息,先通过通讯模块接收数据,然后用消息传递模块通知另一玩家,再由另一玩家的线程调用通讯模块把消息发回客户端,这就是消息传递模块的作用。

 

要怎样封装消息模块呢?第一步就是做一个MessageBox类,它是一个堆栈,用来装消息,主要由pop和push方法,当然这里不能忘了必须实现一个存储消息数据结构。第二步封装就是HandleMessage类,这就是我们的消息模块的主要实现。其中有一个WaitMessage方法,调用此方法后,线程将被阻塞,直到有消息到达。在此可通过无名信号量来实现,也就是UNIX下的sem,它可以增加或减少信号量来实现互斥。然而,有人一定会问,为什么我们要有WaitMesssage呢,这不是将造成线程阻塞吗?其实,服务器端是一个被动驱动的模型,就像没有踩油门汽车就不会走一样,如果没有消息来驱动,服务器端就不会运行下去。

 

实现以上封装后,在两个对象之间发消息就变得很简单,我们直接用SendMessage方法就可以,SendMessage的实现也很简单,就是调用MessageBox里Push方法向里边放消息,之后把sem加一,这样接受这就可以收到消息了。

 

 

 

 

 

其他:

 

 

剩下的两个模块,都是与游戏相关的,事实上他们是两个更为复杂的模块,根据要编写的游戏的不同,这两个模块实现也不同。但是,他们究竟是做什么的,下面通过一个例子来说明:

 

假设我们现在开发的是一个RPG游戏,我们的玩家在屏幕上让游戏人物向前走了一步,此时发送移动请求给服务器端,服务器端的通讯模块收到后,便通知游戏世界管理模块,游戏世界管理模块调用游戏规则模块,判断玩家请求是否符合规则(是否合法),若可以移动,再由游戏管理模块将其坐标改变,最后再通知其相关玩家。也就是说,规则模块实际上是专门处理游戏业务逻辑的,管理模块实际上是专门处理游戏对象的。

 

 

 

 

 

小结

 

   网络游戏的开发入门是挺难的,实际上,本文只能起到抛砖引玉的作用,仅告诉大家一个入门的方法而已,剩下的,还有那烦人的一行一行实现代码。

 

 

 


-------------------------------------------------------------

 

 

 

首先,再次强调一下,网络游戏开发极为困难,技术含量相当的高,其编程知识涉及网络编程,操作系统进程、线程编程,图形图像编程(DirectX / OpenGL),WIN32 API编程(Windows下开发),以及各种算法和数据结构,同时对设计人员策划能力要求也颇高,如不能构思出一个吸引玩家的游戏世界,也必将导致开发失败。

 

目前,国内的网络游戏市场被韩国游戏霸占,情形让人心寒。在国内网络游戏编程资源奇缺的环境下,我希望把自己的一些经验和想法说出来,供大家参考,起一个抛砖引玉的作用。

 

对于我在浅(1)中提出的架构,如果大家有更好的修改建议,欢迎大家共同探讨修改,把我国的网络游戏开发水品提高到世界级标准。最起码,也要在国内市场是立足!

 

好了,费话就不多说了,正文开始。

 

在浅(1)中,关于游戏世界管理模块和通讯模块我没有详细说明,本篇中将补充介绍。

 

 

 

游戏世界管理模块:

 

 

 

  

 

本模块专门管理游戏世界里的数据模型,也就意味着,所有游戏里的对象基本上都由他来管理所以,此模块极为复杂,甚至在大型系统里,也可以把它再划分成很多子模块来协同工作。

 

  这个模块该怎么封装呢?首先,自然是需要一个消息处理类,因为游戏世界管理模块同样是需要消息驱动的,此模块每收到一个消息后,就察看消息类型,看是转发类型还是管理类型的消息,如果是转发类型,就将消息转发给消息目的地模块,如果是管理类型的消息,就察看管理的目标以及管理的方法,然后执行管理方法。因此,我们还需要的就是一个辨别消息的方法,以及一些数据及操作数据的方法。

 

 

 

 

游戏规则模块

 

   

 

   本模块按照游戏策划者制定的规则来进行业务逻辑处理。同样,首先需要封装的也是消息处理类。然后就是辨别消息,按照消息提示进行规则处理,随后就是将处理结果封装成消息,发给管理模块,基本上与游戏世界管理模块模式相同。

 

  以上谈了两个模块的封装思想,但是,实际上,这两个模块是不可能像上面写得那样运用的,很多朋友也谈到这个构架并不适合作大型网络游戏,那么,真正的大型网络究竟是怎样的架构呢?

 

   就像我们的OSI模型和TCP/IP模型一样,只有后者能真正运用在工业标准中,前者固然是好的,但是他封装得太细了,太过于复杂了,不适合现在的情况使用。在真正的网络游戏中,以上的两个模块是合在一起的!我把它们统称为游戏世界模块。请大家注意看下面这个模型(发了几次图片都失败了,所以用文本弄了一个,请谅解)

 

 

 

户 端          通讯模块           游戏世界模块

 

    |                     |                    |

 

    |  发送internet消息      |                     |

 

    | --------------------- |                     |

 

    |                     |──┐              |

 

    |                     |     | 解包           |

 

    |                     |<─┘                |

 

    |                     |    发送消息         |

 

    |                     |  --------------------- > |

 

    |                     |    (通过消息模块) |

 

    |                     |                    |──┐ 

 

    |                     |                    |     | 规则判定

 

    |                     |                     |<─┘  

 

    |                     |                    |

 

    |                     |                    | ──┐

 

    |                     |                    |     | 修改游戏世界

 

    |                     |     发送消息       | <─┘ 

 

    |                     | <----------------------  |

 

    | 发送internet消息    |   (通过消息模块)  |

 

    | <------------------------- |                     |

 

    |                    |                    |

 

 

 

 

补充日期: 2004-02-01 18:28:34

续,没发好

首先,再次强调一下,网络游戏开发极为困难,技术含量相当的高,其编程知识涉及网络编程,操作系统进程、线程编程,图形图像编程(DirectX / OpenGL),WIN32 API编程(Windows下开发),以及各种算法和数据结构,同时对设计人员策划能力要求也颇高,如不能构思出一个吸引玩家的游戏世界,也必将导致开发失败
目前,国内的网络游戏市场被韩国游戏霸占,情形让人心寒。在国内网络游戏编程资源奇缺的环境下,我希望把自己的一些经验和想法说出来,供大家参考,起一个抛砖引玉的作用。
对于我在浅(1)中提出的架构,如果大家有更好的修改建议,欢迎大家共同探讨修改,把我国的网络游戏开发水品提高到世界级标准。最起码,也要在国内市场是立足!
好了,费话就不多说了,正文开始。
在浅(1)中,关于游戏世界管理模块和通讯模块我没有详细说明,本篇中将补充介绍。


游戏世界管理模块:

    本模块专门管理游戏世界里的数据模型,也就意味着,所有游戏里的对象基本上都由他来管理所以,此模块极为复杂,甚至在大型系统里,也可以把它再划分成很多子模块来协同工作。
这个模块该怎么封装呢?首先,自然是需要一个消息处理类,因为游戏世界管理模块同样是需要消息驱动的,此模块每收到一个消息后,就察看消息类型,看是转发类型还是管理类型的消息,如果是转发类型,就将消息转发给消息目的地模块,如果是管理类型的消息,就察看管理的目标以及管理的方法,然后执行管理方法。因此,我们还需要的就是一个辨别消息的方法,以及一些数据及操作数据的方法。


游戏规则模块
   
    本模块按照游戏策划者制定的规则来进行业务逻辑处理。同样,首先需要封装的也是消息处理类。然后就是辨别消息,按照消息提示进行规则处理,随后就是将处理结果封装成消息,发给管理模块,基本上与游戏世界管理模块模式相同。


    以上谈了两个模块的封装思想,但是,实际上,这两个模块是不可能像上面写得那样运用的,很多朋友也谈到这个构架并不适合作大型网络游戏,那么,真正的大型网络究竟是怎样的架构呢?
   就像我们的OSI模型和TCP/IP模型一样,只有后者能真正运用在工业标准中,前者固然是好的,但是他封装得太细了,太过于复杂了,不适合现在的情况使用。在真正的网络游戏中,以上的两个模块是合在一起的!我把它们统称为游戏世界模块。请大家注意看下面这个模型(发了几次图片都失败了,所以用文本弄了一个,请谅解)

客 户 端          通讯模块           游戏世界模块
    |                     |                    |
    |  发送internet消息      |                     |
    | ---------------------a |                     |
    |                     |──┐              |
    |                     |     | 解包           |
    |                     |<─┘                |
    |                     |    发送消息         |
    |                     |  --------------------- > |
    |                     |    (通过消息模块) |
    |                     |                    |──┐ 
    |                     |                    |     | 规则判定
    |                     |                     |<─┘  
    |                     |                    |
    |                     |                    | ──┐
    |                     |                    |     | 修改游戏世界
    |                     |     发送消息       | <─┘ 
    |                     | <----------------------  |
    | 发送internet消息    |   (通过消息模块)  |
    | <------------------------- |                     |
    |                    |                    |


从上图可以看出,实际上,游戏规则模块和游戏管理模块被合并在一起了,也就是说,这两个模块之间不需要消息传递,他们只是简单的函数调用关系。

规则判定要做的主要工作就是辨别消息,把我们的消息翻译成对对象的处理方式。
我们的游戏世界是有很多对象构成的,一个对象同时也可以携带多个对象,对象也可以不断增加、扩充。每当我们添加或扩充一个新对象,我们可以把它include进来,再在规则模块里加入对他的方法调用。
这里要说明的一点就是,其实,所有的对象都是无差别的。大家都是数据模型,不管你是一个人,还是一棵树,或者一种道具,甚至魔法,他们都只是一些属性而已。这些属性,统统都存在我们的配置文件中,甚至可以存在数据库中。到时候创建对象的时候把属性带入就可以了。当然,关于游戏世界对象的想法,有很多好的提议,我甚至想过可以不要对象的方法,只要它的属性,反正我们只是改变对象的属性而已,而把怎么改变这些属性按一定的格式写在文件或数据库中,比如inc XX 0.3表示XX属性+30%之类的,这样就可以很方便的改变规则和对象。
下面,我针对大家的问题提出一些看法:
首先,可能大家会有疑问,这样的系统架构,似乎很慢啊,怎么维持那么多玩家在线呢?如果有那么多对象要处理的话,怎么可能保持速度?
1.  服务器不是大家用的PC机,只要你能用一下服务器和PC机,你就知道他们的区别了,用unix开发也是为了确保游戏能在小型机之类的服务器上运行。
2.  很多人认为网络游戏应该支持数十万人的玩家同时在线,比如说传奇,联众,他们确实是30-40万人在线哪!实际上,大家看仔细了,每台服务器到底是多少人,有的服务器才6XX人就说满了,这就是网络游戏的真实情况。
3.  UDP和TCP的争议问题,有的朋友可能认为应该用udp,就因为它快,其实这个问题没什么好争的,我们的ftp为什么不用udp呢?如果用udp,我们就得自己封装一套tcp的确认机制出来,
4.  对于同步问题,一般来说,客户端确实就是有什么发什么,但是要控制发送的间隔时间,这也是为了防止变速齿轮和解决同步问题。比如客户端一旦发出移动命令后,客户端自己首先判断是否在间隔时间内,再判断是否能移动,能移动了才发消息给服务器端,同时开始移动,如果服务器端发回消息移动成功,那就成功,否则,屏幕上的人物就会被拉回来。


连接池技术
   很多朋友都比较关心的一个问题就是:为每个连接分配一个线程,是否太浪费了!实际上,apache大家都知道吧,他为每个连接分配的可是一个进程呢!线程比进程开销要小得多,如果用户数不是很多的话,那是没有问题的。但用户数不是很多要怎么理解呢:linux6.2下测试,每个进程最多能创建1000个线程左右(实际应该是1024),win2000 advance server sp3下测试,每个进程最多能创建2000个线程左右(实际应该是2048),这下大家明白了吧。
    不过,再怎么说连接线程只是用在网络通讯上,要支持更多的用户的话,太不划算,因此在这里就介绍一下连接池技术。
   所谓连接池,实际上是我们事先创建一些线程,每次通讯模块要发消息时,就找一个空闲的线程,然后把要发的消息给它,让它去发送。这样处理的话,创建很少的线程就够用了。这有点类似于apache的进程预处理,又更像jdbc的数据库连接池。这就要求封装一个连接池控制类。
    总之,如果要用连接池技术的话,就会使通讯模块复杂化,但是它能大量节约系统资源,建议大家设计的时候可以让用户在配置时选择是否使用连接池技术,这样根据不同的系统,就可以有不同的配置方案。

   
小节
    这次探讨就到这里,不知这次又要引出多少反对意见,不过,我真的很希望看到大家的意见,因为任何软件开发本身就是仁者见仁,智者见智的问题,在这里,没有绝对的标准,更没有绝对的真理,我们要的是真正能实现的方法,希望大家在给出意见的同时,最好能给出好的建议。

补充日期: 2004-02-01 18:31:10

本系列文章始终以浅谈二字开头,所以内容简单,不够深入,希望大家谅解。
但是,正如人类的学习过程一样,是一个由浅入深的过程。市面上很多昂贵的图书都喜欢用深入XXXX,或者XXXX内幕揭秘之类的题目,其实大家看了以后,感觉他们又何尝不是浅谈呢。为什么会有这样的感觉,因为人类的思维是发散的,特别是科技含量较高的领域。我们的知识,都是在前人的基础上继承过来的。就像我们的面向对象编程一样,如果我们照搬,那就不是继承了,直接实例化就行了,只有在交作业的时候,我们才会把书上的东西照抄。要继承,就得自己编写一些新的方法,甚至有时候需要覆盖一些方法,这才是开发研究的真正涵义。
所以,我的文章只是一个基类,给大家抛砖引玉,让大家自由继承,甚至很多东西也只是一个虚函数,好的架构,好的实现,靠大家自己。希望各位开发出经典游戏以后,别忘了免费给我几个月的点卡玩玩。
  
    这次先不谈技术问题,先谈谈怎么策划网络游戏(离服务器端编程越来越远了,呵呵)。


网络的游戏构思

游戏写得好,绝对不是因为技术用得好,举个简单的例子,假设我们现在策划了一款非常不错的网络游戏,就算因为技术问题能支持的同时在线用户很少,也同样能够赢得市场,只要加上几台服务器就行了;但反过来,策划不好的游戏没人玩,那就一定没市场,此时,我们具备高超编程水平的技术人员就白辛苦了。当然,我这样说的目的并不是要大家忽视技术,只是想让大家重视游戏策划而已,因为我发现有时候我们的很多编程高手太过于注重技术细节,反而忽视了市场这个我们赖以生存的重要因素,忽视构思、策划,就等于忽视市场,后果可想而知。
构思哪里来?创意怎么找?最简单的办法,多找几个玩家聊聊,最好就是身边的好朋友。只要你要善于捕捉他们的思想闪光点,就一定会有重要发现。
    什么样的创意才是好创意?我问了很多人它们的创意是什么,大部分都是一来就说:应该做一个完全模拟现实世界的真实的游戏世界!何其伟大创意构思啊,可惜,用不了。股市里有一名言:“和别人一样的话是很难赚到钱的”,意思是说大家都这样操作,你也这么操作的话就别想赚钱了。因为市场是残酷的,历来只有少数人会胜出,既然这个想法大家都能想到,那游戏公司的策划高手又怎么会不知道呢,程序高手又怎么会写不出来呢?这个构思的失败的原因很简单,就因为它没有吸引玩家玩下去的地方。 一个网络游戏是否能够成功,就看它是否能引入一种游戏规则,让玩家一上手就觉得自己不得不不停的做一件在他认为是很有意义的事。上面这句话是有点拗口,不过我想不到更好的表达方式了,大家多读几遍一定能读懂的。这个规则有很多办法实现。最常用的办法就是引入一个竞争系统,因为只有竞争,才能激起人的奋斗欲望,才能让人把money和time大量的花在上面。这个系统的竞争规则,应该是相对一致的,就是说不同的玩家之间竞争的东西应该是一样的,比如,目前的网络游戏大多引入的竞争基本上是等级竞争,装备竞争,为了更高的级别,为了更好的装备,玩家可以不停的作一些枯燥的事。而我们上面那个虚拟现实世界想法失败的根本原因,就是因为它的竞争系统不明确。玩家的人数是有限的,但是相对于这样的游戏人数来说,那么大的一个系统,竞争的目标却是无限的,玩家可以选择的目标太多了,以至于玩家之间没有了比较的方法,我不知道我玩了一天和他玩了一年到底是谁厉害,因为我们的属性根本不一样,假设游戏里有教师和学生两种职业,到底是教得好,还是学得好,在教师与学生之间根本比不出来。我们只能在教师与教师,学生与学生之间比。如果是要模拟现实世界的话,必然出现成千上万的竞争机制,为了能分出个高下,大家最终将会自然而然的集中到少数几个竞争机制上来,那就意味着,其他的竞争机制白做了,还不如把游戏简化,专门做这几种竞争机制,那样会更好。那么,竞争机制越少越好吗?那也不一定,竞争机制太少的话,游戏有时候也会缺乏乐趣,这个,就是体现策划功底的地方了。
    下面我们看几个例子。
    首先是经典游戏DIABLO 2,要研究一个游戏的竞争系统,第一步要做的就是看玩家玩这个游戏大部分的时间是花在什么上面,答案是:找装备;其次再看看在这个游戏里什么样的角色是最值得崇拜,答案就是装备好的玩家,等级高的玩家在这个游戏里并不值得崇拜,钱多的玩家也一样,因此,我们可以断定,此游戏就只有一个竞争系统:物品竞争。由于这个竞争系统做得过于完美,所以,他只要一个竞争系统,就足够了!
    传奇里,大家花时间最多的事就是练级,或者找装备,由此可见,此游戏的竞争系统就是这两样。
    UO里,大家花时间最多的事情是什么,不一定,有的是练级,有的是PK,有的是打矿石,有的是其他,所以可见,UO引入了一个多种竞争机制的系统,但是,仔细体会又会发现,这个“多种”其实并不是很多,而是在一些传统系统上进一步扩展,多了几个职业。
   有人可能会问,那联众的竞争机制又是什么呢?乍一看似乎很难下手,不过,我们仍然运用上面的方法来分析。大家把时间花在什么上面?打牌下棋,可见,他直接引入了早已成熟了几百年的竞争机制,他的成功就在于直接把现实中吸引大家的规则引入到了游戏中。所以自然大受欢迎。
   由此可见,做一个成功的网络游戏,可以先把游戏背景定下来,也可以以后再定,但最重要的是要想好应该采用什么样的竞争系统,不管引入一种还是几种,一旦确定后,就要考虑怎样让游戏世界更好的体现出你的竞争系统。如果一开始不确定竞争系统,而是一开始就深入构思整个游戏世界,那就会面临很大的市场风险了。
   下面举个策划的例子:某天,我灵机一动,决定采用money竞争机制,于是我开始确定游戏背景,古代?现代?未来?虚幻世界?我个人觉得现代似乎更容易体现这个竞争机制(这可不一定),下一步就是怎么体现这个竞争机制:为什么要有钱,有钱可以干什么,我得到钱的方法是什么。于是,我就可以引入一些现实世界的赚钱手段,并规定开始我只能赚很少的钱,等我钱多了,就可以赚大钱,围绕着这些想法,我们就可以构建出一个又一个的游戏世界。当然,我现在只是举例子罢了,其实对于这个游戏世界,我并没有什么很好的想法,如果有朋友有,可以来信共同探讨。
   在想要策划一个网络游戏之前,一定要征求玩家的意见,虽然很多人会说他想虚拟现实世界,对于这样的回答,你可以开导他,告诉他你只想做一个简单的游戏世界,然后他就会慢慢缩小这个游戏世界,最后你一定能发掘出他思想里的竞争机制来,此时,你就得到了这个玩家思想的精华部分了(是不是太坏了,呵呵)。当你得到了很多精华的思想后,就可以做选择了,找出那些有市场的创意来(这也是很难的一件事,看策划者的眼光了),然后就用策划例子里的方法,逐步制定出整个游戏世界,剩下的就是写出详细策划。
    游戏策划完成后该怎么办?当然是编码了,虽然本篇中始终没有谈到技术问题,但是开发人员的技术水平是不可忽视的,如果水平不够,游戏都写不出来,那我们的策划师又白辛苦了。
   由于没有政府扶持,中国现在的游戏开发现状就是,很多开发小组里的人员大多既是策划师,又是程序员,甚至还是美工,更有甚人还要负责市场(民间开发小组里这种情况比比皆是)!看看韩国,网络游戏编程技术由政府出马研究,各大公司直接拿过来用!也就是说,在某种程度上,韩国只要把策划搞好就行了,这才是真正的政府扶持!


小结

    看完本文后,各位有什么想法,也许有人会说,空谈理论!确实,本文太理论化了,而且又没有谈技术,所以很不合程序员的胃口。
    但是,不管怎么说,我希望程序员能把眼光看远点,把目光从技术向市场转移,把本文看成一个基类去继承,去添加大家自己的方法,去覆盖不够理想的函数,最后再把大家的成果让别人去继承,这样下去的话,我们国家的网络游戏水平肯定能够达到一个理想的高度!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值