资源描述
网络棋牌游戏之框架制作
先从实质性的层次划分它,首先要有玩家,那么就设计玩家的一个结构,其实所做的工作就是把传统的对象实体设计成计算机面向对象的实体,然后给这些实体一些操作方法,就这么简单,不要被那些软文给讲蒙了,就是这么一回事!啥单件模式,三层模式,观察者模式最终都离不开这个出发点,继续启动~~ 首先肯定要有玩家对吧,没有玩家咱玩呀~那么就先设计一个玩家类,如下:
Class 或 Struct 都可以,如果是Class最好加上抽象(abstract)声明至于为什么要加问别人吧,在使用C++的时候都被C#的完全面向对象所吸引,然后给这个类赋一些属性,天呐,C#下面实现这些属性太棒了get 和 set 以及那么成熟高级的特性(什么不会设计这样一个类,那么就此打住先收藏本文学习这部分知识之后再来阅读)不像C++下面的那样古老级的做法,不过可不要忘记哦一切源自C++的设计灵感才会有现在的C#~. 称C++可是杀手锏~~~~
游戏玩家类
{
属性:
玩家在系统的唯一标识
玩家的帐户名
玩家的昵称
玩家的积分
玩家的注册时间
玩家的当前登录时间
玩家的当前登录IP
玩家的等级
玩家的信誉度
是男是女还是人妖
是不是喜欢老温
……..略
}
这儿讲句题外话,在设计属性时,对Set操作时一定要进行判断规则,这样才能从基层保证了数据是OK的,合法的,比如玩家的等级不可能小于0标识,最好做个判断,玩家的金币不可能超出多少钱做个判断,如果不符合就抛出异常,好像从哪本EFFCT C++啥中看到的,英文单词肯定打错啦,学到的知识!
具体有哪些属性要看自己的发挥了,要知道这一切都是由你来决定,没有人能够阻止!切记,自由很重要,人类为了追求自由流了多少鲜血呀!怎么竟讲蠢话。上面的这个类就是一个纯的玩家实体类,没有啥功能,就是一个概念级的东西描述符罢了。也就是一个玩家就有这些特性,够简单吧!好了,这是一个玩家,那么要是有上百个玩家进行管理呢,怎么组织它们,数据结构发挥了它魅力的部分了,一个个玩家链在一起就方便程序员管理它们了,再次感谢微软,感谢老盖,C#下面实现了这些数据结构类,什么不知道,LIST类总知道吧,连排序方法都搞定了。打个比方,要把用户一个个组织串起来,然后就依次对它取一个出来操作或遍历它或删除它等。也就是玩家退出大厅和登录大厅这样的情况下的操作罢了。
有了一个玩家类还不够,得给它生命,让他学会叫,学会叛逆才行。那么这一切工作就需要编写一个操作它的类,就叫它逻辑类(生命类)吧,又要告诉您,不要在乎别人怎么讲,游戏设计是很自由的,这个世界属于自己的! 由这个逻辑类负责给这个玩家实体类产生生命的源泉! 嘿它是单性繁殖的。
在这儿有必要提出另一种设计方法:一开始还是比较倾向把这些实现逻辑部分的代码放进这个实体类本身,因为按照面向对象的原则性是这样做的,一只猫,一只狗都有自己的属性而且都有自己的方法,所以它是一体的!但是从另一层次讲可以把这些叫方法独立开来,让一只猫就是一只猫,需要叫,需要吃都由另外负责类处理!这一点在WEB应用程序中一直这么做! 其实在明白这些道理之后就会对啥工厂模式啦,啥三层模式啦都明白啦,就是那个味,只是给它套上了一个专业术语,有些还是啥英文的,奶奶的咱们中国民族的文化可比强多了,可惜现在所学的知识仍然完全是由老外控制的,哎~~讲这话腰酸呀!
那么这个负责处理玩家的类需要哪些方法呢?
玩家需要登录就有一个 登录方法();玩家需要退出游戏 就需要一个 退出方法();有一次项目中偿试方法都用汉字来定义差点被团友给扔出去C#是可以用汉字来命名的吧)。如果想要得到一位玩家资料就有一个获取方法();删除玩家方法();给玩家发送通知方法();让玩家强制下线方法();玩家注册方法()等等;发挥想象力,但是一定要拥有一个原则,它是围绕玩家类来实现的。这一切都会在设计项目时就已经很清晰的画出来了,
好的,亲爱的旅客已经离开了地球,坐好了!会开启双核亚光速引擎,速度提高双倍!尽可能快的到达火星; 听人家说美国的的火星探车程序用JAVA编写用来通讯的!
经过以上部分的阅读,已经初步有了一个概念,那么接下来就是很重要的部分,来分析一下这种游戏的总体架构模型,认为很重要,不知道认为重要不重要,重不重要得看了才知道哟!Let’s GO!将这个系统划分为这几个重要组成部分:
1) 客户端大厅程序
2) 游戏服务器(集群)
3) WEB游戏站点
4) 辅助GM工具
逐一讲解,先看最简单的部分就是WEB游戏站点,既然选择了C#还干嘛要用PHP,JSP呢,可惜人家PHP是FREE的呀,羡慕ing,不过用盗版,嘿嘿!等做大了还怕出不起这几个版权费用嘛,最贵的可是SQL系统哦,好几万啊,所有的预算加起来要十几万啊才一个解决方案! WEB游戏站点用ASP.NET实现,新建一个解决方案,再建立一个网站项目,想做网站任何人都比来的强,这个网站就是给用户查询积分,在线交流,新闻发布,用户注册,修改密码,互动娱乐等等作为一个推广的平台!这中间没啥好讲的,唯一能够打交道的就是数据库,因为和游戏系统 交互纽带就是通过数据库。既然讲到数据库,那么就顺便讲一下如何设计数据表结构吧,这中间不想用更多的废话讲,很简单,把策划与需求分析好,转成数据表,规划好数据完整约束,建立好索引,命名好规范,合理设计好用户表等等;其实前面所讲到的玩家类就是一个数据表的体现。博客园的人谁不会呀!
OK,在双核亚光速的旅途中已经建立好了游戏站点,玩家可以申请帐号,登录管理中心等等操作!让地球的玩家通过WAP连接进来吧,什么这种无线技术延时……….现在很振奋吧,的棋牌游戏站点架设完毕了!一切看上去都是那么的真实美妙,只有自己知道还没有做出棋牌游戏来呢?,伟大的C#,在做游戏站点的时候已经拥有了sqlhelper类,通常为了能够给游戏中使用将它单独划入一个公用类库中,想大家也是这么做的。(比如:)这儿将这个数据帮助类已经写好了,下载即可,只有几个方法很简单也够全面,几乎可以用这几个方法操作所有的需求,学习过那个叫什么Pet什么的工程就清楚了。看不懂这段话吗? SqlHelper类你不知道吗,网上一大把呀,做A不用它吗? 这位旅客请下船,什么下船还要拿走SqlHelper类?接着继续来观看辅助GM工具的设计,其实这类工具未必要做成WINFORM形式的,完全有必要集成在游戏站点的后台中,依情况与心情决定吧!它主要负责的就是给一些用户增加积分,删除一些用户,注册一些用户,禁用一些用户,配置一些游戏参数,积分双倍,活动定义,游戏公告,游戏通知,广告通知等等操作!这其中涉及到的一些操作可能不是十分完全明白,不知道怎么和游戏结合,OK,只要把操作保存到DB中即可,如果是单独用WINFORM操作也可以把它保存到INI文件或XML文件中,注意这可是配置服务参数,不要搞进客户端哦!
游戏站点的设计!辅助工具的编写,接下来就是的最后冲刺部分,启动四核亚光速引擎全面
现在开始全面讲解游戏服务器方面的知识
游戏服务器是非常重要的组成部分,游戏的性能以及安全和稳定全靠它来支撑,所以设计一个合理的游戏服务器是非常重要的~这是一个很严肃的问题,因为太阳风暴就要来临了,搞不好咱们全玩了,回不了地球了~~很严肃,注意看!
在这类设计中,通常会使用C/S的应用程序模型。客户端不会处理逻辑判断,仅仅是一个收发器,如果把重要的逻辑判断放在客户端处理就完了,那还有什么信任可言呀,客户随时可以搞个外挂模拟提交~~隔位刺杀,极速外挂!!全乱套了。客户端负责与玩家的交互,接收玩家的操作事件,然后发送给服务端,服务端负责处理判断,然后再发给客户端,客户端收到服务器的消息后再转成UI展现出来,就是这么一个流程,如果想要让通信安全把这些数据包经过一层加密或数字签名~其实所有的游戏都是这么做的,客户端就是存储着些声音图形等界面性的东西。
好了,花了数十个通宵才完成的一个例子,现在先展示它,一层层剖析,可能这不是最好的方案,但是它代表了一家的风范,有权拒绝也有权接受,是讲人权的呀
由一个DEMO解决方案组成,下面有Chess、ChessServer、Common、DataServer、GameClient、HallServer、LoginServer、OnlyServer八大部分组成。其中Chess是一个棋类游戏的实现,也就是房间,ChessServer是负责这个棋类游戏的服务端逻辑判断部分,Common是存放公存类库的,DataServer是专门用来处理数据库层的服务,GameClient是游戏的客户端是一个Windows应用程序,HallServer是一个负责处理大厅事务请求的服务器服务,LoginServer是负责处理用户登录的服务器服务,OnlyServer是仅给服务器应用程序提供的类库,为了安全起见不能将它与Common弄在一起,要不然发布的时候客户端中也会有这类服务逻辑处理代码,那样就很危险了!
好了介绍完了这个工程下面就开始讲解这些服务器之间是如何协同工作的,以及为什么要采用这种设计模式?之所以采用这种设计模式是有很肯定的原因的,废话,要不然这么做干啥!首先做为客户端它不可能知道有这么多服务器的存在,也就是客户端它只要知道登录到哪里,也就是用户要知道要登录到哪个服务器呀,用户需要选择一个最近的服务器,做的这个客户端是一个典型的休闲棋牌游戏客户端,其中分二层第一层就是浮在上面的登录层,必须要进行登录后才可以进入第二层;其中登录层是可以托动的,实现了不规则窗体透明效果,这些在C# Windows开发下还是比较方便实现的!
大家看到首先会让用户选择服务器,不同的服务器根据用户距离而决定,其次就是这中间还会加上一层智能判断,来协调哪个登录服务器是爆满的哪个登录服务器是空闲的,这个方法利用计数器就可以实现,其中大家可以看到这些登录按钮都是双个图片进行变色的鼠标移上去就会发生变换。上面灰色部分是准备嵌套一个公告网页的,这样就可以动态告知用户系统举行了哪些活动等;还可以收入广告费用~~其它系统都不是这样设计的吗其实这个弹出框就是一个窗体,封装了一个MessageBox窗体类,专门用它来处理与用话对话返回确定,取消还有其它种类的返回值! 再来看一张点击了关闭后弹出的对话框:看界面就已经很明白它要完成哪些功能了,下面再看一下代码:
private TcpListener myListener;
private IPAddress localAddress;
private Service service;
private UserOperat pubuserop = new UserOperat(System.Configuration.ConfigurationManager.AppSettings["Sqlconnstring"].ToString());//单独线程用于处理用户在线;
private System.Collections.Generic.List<User> userList = new List<User>();
以上代码就是一些声前其中把各个服务器也视为User,之所以这儿用的是单线程是有原因的,因为总共服务器才几个,这个数据管理器只为这几个服务器服务的,没必要用多线程,至于那几个服务器放在哪里不管,管它放宇宙的哪个角落,只要能访问的到就可以了。继续看初始化部分的代码:
Code public Form1() { InitializeComponent(); this.timer1.Enabled = false; this.timer1.Interval = 1000 * 60;//每一分钟进行用户在线检测 this.listBox1.HorizontalScrollbar = true; } private void Form1_Load(object sender, EventArgs e) {
this.Text = "数据管理服务器"; //初始化得到本地计算机IP与默认端口 IPAddress[] addrIP = Dns.GetHostAddresses
(Dns.GetHostName()); localAddress = addrIP[0]; this.serverip.Items.Add(localAddress.ToString());
this.serverip.SelectedIndex = 0; this.port.Text = "5001";//登录服务器端口 this.button2.Visible = false;
this.statusStrip1.Items[0].Text = "服务已停止.."; service = new Service(listBox1); }
这中间不过就是做了一些界面初始的显示和取一些IP等数据罢了,service = new Service(listBox1);可能感到奇怪这个是干什么的,这个类就是负责专门用来把处理的结果显
示到界面上的,其中用到了委托,因为C#中线程是不允许直接访问控件的!
再来看看其它代码,好长呀。这儿就再贴出一个核心的部分:
Code //接收客户端连接的线程 private void ListenClientConnection() { while (true) { TcpClient
newClient = null; try { //等待用户进入 newClient = myListener.AcceptTcpClient();
} catch { //当单击[停止监听]或者退出此窗体时AcceptTcpClient会产生异常 //因
此可以利用此异常退出循环 break; } //每接受一个客户端的连接,就创建一个对应的线程循环接收该项客户端发来的
信息 ParameterizedThreadStart pts = new ParameterizedThreadStart(ReceiveData); Thread threadReceive = new Thread(pts);
User user = new User(newClient); threadReceive.Start(user); userList.Add(user); service.SetListBox
(string.Format("{0}进入", newClient.Client.RemoteEndPoint)); service.SetListBox(string.Format("当前连接用户数:{0}", userList.Count));
} } 这个函数是等待用户加入的功能,一但有用户进入就要进行一系列的工作的开始!一系列的工作就由下面来处理: //接收、处理客户端信息的线程式,每客户
1个线程,参数用于区分是哪能个客户 private void ReceiveData(object obj) { UserOperat userop = new UserOperat
(System.Configuration.ConfigurationManager.AppSettings["Sqlconnstring"].ToString()); MessageData mess = new MessageData(); User user
= (User)obj; TcpClient client = user.client; //是否正常退出接收线程 bool normalExit = false; //用于控制是否
退出循环 bool exitWhile = false; while (exitWhile == false) { //保存接收的命令字符串
string receiveString = null; //每条命令均带有一个参数,值为true或false,表示是否有紧跟的字符数组 string[] splitString = null;
byte[] receiveBytes = null; try { //从网络流中读出命令字符串 //此方法
会自动判断字符串长度前缀,并根据长度前缀读出字符串 receiveString = user.br.ReadString(); splitString =
receiveString.Split(','); if (splitString[1] == "true") { //先从网络流中读出32位的长度前缀
int bytesLength = user.br.ReadInt32(); //然后读出指定长度的内容保存到字节数组中
receiveBytes = user.br.ReadBytes(bytesLength); } } catch { //底层套接
字不存在时会出现异常 service.SetListBox("接收数据失败"); } if (receiveString == null) {
if (normalExit == false) { //如果停止了监听,Connected为false if
(client.Connected == true) { service.SetListBox(string.Format("与{0}失去了联系,已终止接收该用户信息",
client.Client.RemoteEndPoint)); } } break; }
service.SetListBox(string.Format("来自{0}:{1}", user.client.Client.RemoteEndPoint, receiveString)); if (receiveBytes != null)
{ service.SetListBox(string.Format("来自{0}:{1}", user.client.Client.RemoteEndPoint, Encoding.Default.GetString(receiveBytes)));
} switch (splitString[0]) { case "rsaPublicKey": //使用传递过来的公钥重新
初始化该客户端 //对应的RSACryptoServiceProvider对象 //然后就可以使用这个对象加密对称加密的密钥了
user.rsa.FromXmlString(Encoding.Default.GetString(receiveBytes)); //加密对称加密的私钥 try
{ //使用RSA算法加密对称加密算法的密钥Key byte[] encryptedKey = user.rsa.Encrypt
(user.tdes.Key, false); service.SendToClient(user, "tdesKey,true", encryptedKey); //加密IV
byte[] encryptedIV = user.rsa.Encrypt(user.tdes.IV, false); service.SendToClient(user, "tdesIV,true",
encryptedIV); } catch (Exception err) { MessageBox.Show
(err.Message); } break; case "dsaPublicKey": //使用传递过来的公
钥重新初始化该客户端的DSA对象 //然后就可以使用这个对象对这个发送者进行数字签名的验证 user.dsac.FromXmlString
(Encoding.Default.GetString(receiveBytes)); byte[] ServerdsaPubkey = Encoding.Default.GetBytes(user.Serverdsac.ToXmlString(false));
//将服务器的数字答名公钥发给该客户端 service.SendToClient(user, "dsaPublicKey,true", ServerdsaPubkey);
break; case "Logout": //格式:Logout service.SetListBox(string.Format
("[{0}]退出", user.client.Client.RemoteEndPoint)); normalExit = true; exitWhile = true;
break; case "GetSqlconnect": //向该客户端发送SQL连接字符串 string sqlconnt =
System.Configuration.ConfigurationManager.AppSettings["Sqlconnstring"].ToString(); mess.EncryptMessage = TripleDES.EncryptText
(sqlconnt, user.tdes.Key, user.tdes.IV); mess.Datalength = TripleDES.GetStrLength(sqlconnt);
mess.SignDataMessage = user.Serverdsac.SignData(Encoding.Default.GetBytes(TripleDES.MD5(sqlconnt))); byte[] buffer =
SerializeData.Serialize(mess); if (buffer != null) { //发送注册信息
service.SendToClient(user, "Getsqlconnect,true", buffer); } break; default:
service.SetListBox("什么意思啊:" + receiveString); break; } }
user.br.Close(); user.bw.Close(); user.dsac.Clear(); user.rsa.Clear(); user.tdes.Clear();
user.Serverdsac.Clear(); userList.Remove(user); client.Close(); service.SetListBox(string.Format("当前用户连接数{0}",
userList.Count)); }
关于这部分的代码不想作过多的解释,因为解释的话得再写N天也写不完,希望大家能够仔细自己阅读,如果有凝问再和讲~其中可能会莫名其妙的用到一些加密的东西,
在之前写的那篇网络通讯篇中就已经指出了加密与解密的原理。这中间的消息会专门用一个消息类来进行传递,然后对这个类进行序列化后进行传输,再次感谢C#,多好的技术呀帮实现了。
消息类如下:
Code using System; using System.Collections.Generic; using System.Text; namespace GameServer { [Serializable] public class MessageData {
private byte[] messdata; private byte[] signdata; private int datalength; public byte[] EncryptMessage { get
{ return messdata; } set { this.messdata = value; }
} public byte[] SignDataMessage { get { return signdata; } set
{ this.signdata = value; } } public int Datalength { get { return
datalength; } set { this.datalength = value; } } } }
TripleDES加密类和序列化类在上篇中也已经指出,大家可以从那儿获取!在这儿再次提出的加密方式:客户端和服务器,以及服务器与服务器之间都会采用公有加密和私有
加密二种方法的结合,因为公有加密安全但速度慢,适合加密少量的数据,用它来加密私有密钥,然后利用私有加密技术加密网络传输数据,通常就是一系列的命令罢了,看到
了吗,就是上面的代码片断中的一些CASE后面的字符,通常把它叫做游戏协议!
无法再忍受贴代码的痛苦了,又长又烦,还是大伙自己下载过去看吗?
好了继续看下面一个服务端:
大厅服务器处理程序(HallServer)
它是用来与用户交互的,同时也会与数据服务器发生交互!其中实现的代码基本上和数据服务器一样,但是需要注意的一个问题就是,一定要注意多线程中的临界资源问题,要不然
锁死~~。代码在这儿就不贴了。
继续往下看,因为马上就要到火星了,抓紧时间。
这是LoginServer:
继续看下面,这是客户端,来讲讲客户端,其中一个很重要的表现手法就是完全用自己的方法去实现不规则的窗体,这一点还是比较好做的,第二点就是负责把接收到
的服务器消息展示出去 客户端的登录部分:
其中这些都不太重要,关注的是代码级的东西和实现它的部分!
大家接下来继续看Chess项目:
看到Table 和 Room了吗,其中Table代表了一个个桌子,而Room代表一个人房间,之前傻到用控件来显示它,后来采用了GDI+来绘图,性能提了很多倍,讲到这儿不得不面对
一个现实,在处理这部分图形编程的时候,不管在Directx还是GDI中,都要解决闪屏问题?来讲一下这个闪屏的概念和如何解决~~需要解决的就是要先知道什么是双
缓冲,什么是离屏技术,这类文章网上都讲疯了,现在只关注怎么解决C#闪屏问题,伟大的C#啊,他已经帮实现了,只要启用这个就可以了,就一句话:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.AllPaintingInWmPaint, true);
只要设置支持它就可以!至于如何把图片桌子等这些组织起来或画到屏幕上。
首先是软件的皮肤:
然后是一张张桌子和人物:
还有好多图没有贴出来,大家下载过去看!可以直接放在自己的项目中使用。
好了,就先贴这些了,更多的部分就看提供的下载包中获取吧! OK,这些图片很容易处理,现在关注就是如何把这些图片与桌子无缝的渲染在一起,太Easy了,因为拥有了C#,只要将利用GDI+就可以搞定它们的图与图的切换等等实现手段,认真的朋友看桌子的图和看人就已经看出名堂来了,叠在一起就是完整的人坐在椅子上手放桌子上!
只要花些时间学习一下这方面的知识就可以了。
哎,写了这么多好累,真的是累了。!~ 本来还想把的最终房间的效果图贴一下的,那真的很漂亮,会令的荷尔蒙极速上升,现在看来没有足够的精力来做了,因为DB环境坏了。
这是完整的项目,VS2005+SQL!
结束语:想必看完此篇文章后您拥有无比
展开阅读全文