20.如何解析我们客户端的信息并且交给ControllerManager来进行处理
首先是Message类,参数分别是数据的长度和一个回掉的函数委托,用来重复解析数据,这里的话前四个是数据长度,后面4-8是RequestCode,8-12是ActionCode,12以后是数据,我们按照这个来重复对数据进行解析
publicvoid ReadMessage(intnewDateAmount,Action<RequestCode,ActionCode,string> ProcessDataCallBack)
{
startIndex += newDateAmount;
while (true)
{
if (startIndex <= 4)
{
return;
}
//读取数组的长度,自动省略前四个字节长度
int count = BitConverter.ToInt32(data, 0);
if (startIndex - 4 >= count)
{
//解析的是传递的RequestCode
RequestCode requestCode =(RequestCode)BitConverter.ToInt32(data, 4);
//解析ActionCode
ActionCode actionCode =(ActionCode)BitConverter.ToInt32(data, 8);
//解析我们的数据,从12开始,解析的长度为count-8
String s =Encoding.UTF8.GetString(data, 12, count-8);
//回调解析
ProcessDataCallBack(requestCode, actionCode,s);
Array.Copy(data, count + 4,data, 0, startIndex - count - 4);
startIndex -= (count + 4);
}
else { break; }
}
}
然后我们要在Sever里提供一个中介方法来调用ControllerManager里的HandelRequest来处理对客户端发送信息的解析
publicvoidHandleRequest(RequestCode requestCode, ActionCode actionCode, string data, Clientclient)
{
//这个作为中介,用来作为client和ControllerManager的中间商
controllerManager.HandleRequest(requestCode,actionCode, data, client);
}
然后我们在客户端里重复调用ReadMessage完成对信息的解析
publicvoidReceiveCallBack(IAsyncResult ar)
{
try
{
int count = clientSocket.EndReceive(ar);
if (count == 0)
{
Close();
}
//使用message对client接收到的信息进行解析,这里分别传递的参数是数据长度和回调的方法
message.ReadMessage(count,OnProcessMessage);
Start();
}
catch (Exception e)
{
Close();
Console.WriteLine(e);
}
}
//回调信息的处理,这里使用sever里的HandleRequest来处理对客户端的响应,为了避免耦合没有直接使用ControllerManager
publicvoid OnProcessMessage(RequestCoderequestCode,ActionCode actionCode,string data)
{
sever.HandleRequest(requestCode,actionCode, data, this);
}
21.打包解析我们客户端发送的数据
1.我们在Message里提供一个工具类来打包我们的客户端发送的数据
publicstaticbyte[]PackData(RequestCode requestCode,string data)
{
//这个方法是用来打包数据
byte[] requestCodeBytes = BitConverter.GetBytes((int)requestCode);
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
int dataAmount = requestCodeBytes.Length + dataBytes.Length;
byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount);
byte[]newdata=dataAmountBytes.Concat(requestCodeBytes).Concat(dataBytes) asbyte[];
return newdata;
}
2.在客户端里向socket发送打包后的信息
publicvoid SendMessage(RequestCoderequestCode,string data)
{
byte[]sendData= Message.PackData(requestCode, data);
clientSocket.Send(sendData);
}
3.在服务器中介中调用这个客户端发送消息的方法
publicvoid SendMessage(Clientclient,RequestCode requestCode,string data)
{
client.SendMessage(requestCode,data);
}
4.ControllerManager判断是否需要向客户端发起回应,如果需要,就会传递对应信息
//通过sever端进行中介来发送数据
sever.SendMessage(client,requestCode, o asstring);
22.创建ConnHelper,处理客户端的连接和关闭
1.我们在工具类里创建和数据库连接与关闭的方法
namespace游戏服务器.Tool
{
classConnHelper
{
//这个是用来处理服务器连接的
conststring CONNECTSTRING = "Database=game;Data Source=localhost;port=3306;UserId=root;Password=1234;";
publicstatic MySqlConnection Connect()
{
//这里创建和数据库的连接
MySqlConnection conn = newMySqlConnection(CONNECTSTRING);
try
{
conn.Open();
return conn;
}
catch (Exception e)
{
Console.WriteLine(e);
returnnull;
}
}
publicstaticvoidCloseConnect(MySqlConnection conn)
{
if (conn != null)
{
conn.Close();
}
else
{
Console.WriteLine("conn不能为空");
}
}
}
}
2.在客户端的开始和关闭时调用这个工具类中的方法
publicvoid Close()
{
ConnHelper.CloseConnect(sqlConnection);
if (clientSocket != null)
{
clientSocket.Close();
sever.RemoverClient(this);
}
}
publicClient(Socket clientSocket, Sever sever)
{
//持有一个对socket的引用
clientSocket = this.clientSocket;
this.sever = sever;
//创建客户端时候开始和数据库的连接
sqlConnection=ConnHelper.Connect();
}
22.引入我们的UI框架并且搭建我们的项目框架
首先创建我们的GameFacade,这个是唯一在场景中进行交互的脚本,然后我们创建我们的BaseManager,这个是我们各种Manager的基类,我们先在里面提供两个虚方法,实例化和销毁,后续再进行扩展,然后我们先将UIManager继承一下BaseManager
publicclassBaseManager {
public virtualvoid OnInit() { }
publicvirtualvoid OnDestory() { }
}
23.搭建客户端的基本框架
这里我们先创建上图中的各个Manager并且将它们实例化出来,这里实例化的操作我们放在GameFacade中进行
publicclassGameFacade : MonoBehaviour {
//这个脚本是用来和Unity进行交互的
// Use this for initialization
privateAudioManager audioManager;
privateCameraManager cameraManager;
privateRequestManager requestManager;
privatePlayerManager playerManager;
privateClientManager clientManager;
void Start () {
InitManager();
}
// Update is called once per frame
void Update () {
}
publicvoid InitManager()
{
audioManager = new AudioManager();
cameraManager = new CameraManager();
requestManager = new RequestManager();
playerManager = new PlayerManager();
clientManager = new ClientManager();
audioManager.OnInit();
cameraManager.OnInit();
requestManager.OnInit();
playerManager.OnInit();
clientManager.OnInit();
}
publicvoid DestoryManager()
{
audioManager.OnDestory();
cameraManager.OnDestory();
requestManager.OnDestory();
playerManager.OnDestory();
clientManager.OnDestory();
}
privatevoid Ondestory()
{
DestoryManager();
}
}
24.初步创建ClientManager并且和服务器端建立连接
这里就是提供IP地址和端口号,重写父类的初始化和销毁方法,用来进行开始连接和断开连接的操作
publicclassClientManager : BaseManager {
privateconststring IP = "127.0.0.1";
privateconstint Port = 6688;
private SocketclientSocket;
publicoverridevoid OnInit()
{
base.OnInit();
//创建客户端socket和服务器连接
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
try
{
//和客户端建立连接
clientSocket.Connect(IP, Port);
}
catch(Exceptione)
{
Console.WriteLine("无法与服务器进行连接" + e);
}
}
publicoverridevoid OnDestory()
{
base.OnDestory();
try
{
clientSocket.Close();
}
catch(Exception e)
{
Console.WriteLine("无法断开服务器进行连接" + e);
}
}
}
25,引入Common的dll文件和Message类
这里我们客户端和服务器端信息的发送大体类似,因此我们可以把服务器端的Message类直接引用到我们的Unity里,也用来做信息的处理
这里我们先要删除之前的Common,重新生成一个以NetFramWork2.0为基础的共享类,然后将它的dll文件导入到我们的unity中的Plugins下,这样我们的Unity就可以成功调用它了
26.客户端向服务器发送信息
上节我们引入了Message类,这里我们重写一下它的打包方法使得它能打包客户端发送的信息,用来组拼数据长度,requestCode,ActionCode,data
publicstaticbyte[]PackData(RequestCode requestCode,ActionCode actionCode, string data)
{
//这个方法是用来打包数据
byte[] requestCodeBytes = BitConverter.GetBytes((int)requestCode);
byte[] actionCodeBytes = BitConverter.GetBytes((int)actionCode);
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
int dataAmount = requestCodeBytes.Length +dataBytes.Length+actionCodeBytes.Length;
byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount);
//byte[]newdata = dataAmountBytes.Concat(requestCodeBytes).ToArray<byte>();
//returnnewdata.Concat(dataBytes).ToArray<byte>();
return dataAmountBytes.Concat(requestCodeBytes).ToArray<byte>().
Concat(actionCodeBytes).ToArray<byte>().
Concat(dataBytes).ToArray<byte>();
}
然后我们呢在ClientMannage里调用这个方法并且提供一个发送给服务器的方法
publicvoidSendRequest(RequestCode requestCode,ActionCode actionCode,string data)
{
byte[]sendData=Message.PackData(requestCode, actionCode, data);
clientSocket.Send(sendData);
}
27.客户端接受服务器发送过来的消息并且解析
这里的话,我们还是用之前提到的异步接受的方法来解析服务器端发送的数据
这里我们创建一个Start的方法并且在建立连接之后就调用它
privatevoid Start()
{
//开始建立和服务器端的连接
clientSocket.BeginReceive(msg.Data, msg.StartIndex, msg.RemainLength,SocketFlags.None, ReceiveCallBack, null);
}
privatevoidReceiveCallBack(IAsyncResult ar)
{
//异步接受服务器传递消息,并且解析它
try
{
int count=clientSocket.EndReceive(ar);
msg.ReadMessage(count,OnProcessCallBack);
}
catch(Exceptione)
{
Debug.Log(e);
}
}
privatevoidOnProcessCallBack(RequestCode requestCode,string data)
{
//这是一个回调函数
}
这里我们修改了一下之前Message里的ReadMessage这个方法
publicvoid ReadMessage(int newDateAmount,Action<RequestCode, string>ProcessDataCallBack)
{
startIndex += newDateAmount;
while (true)
{
if (startIndex <= 4)
{
return;
}
//读取数组的长度,自动省略前四个字节长度
int count = BitConverter.ToInt32(data, 0);
if (startIndex - 4 >= count)
{
//解析的是传递的RequestCode
RequestCode requestCode =(RequestCode)BitConverter.ToInt32(data, 4);
//解析ActionCode
//解析我们的数据,从8开始,解析的长度为count-4
String s =Encoding.UTF8.GetString(data, 8, count - 4);
//回调解析
ProcessDataCallBack(requestCode, s);
Array.Copy(data, count + 4,data, 0, startIndex - count - 4);
startIndex -= (count + 4);
}
else { break; }
}
}
当然如何解析,我们放在下一节讲,这里就是完成了客户端接受服务器端发送信息的搭建
26.修改BaseManager,使得所有Manager都和GameFacade进行连接,然后每个Manager都提供一个对应的构造方法,继承自父类即可
protectedGameFacade facade;
publicBaseManager(GameFacade facade)
{
this.facade= facade;
}
27.创建BaseRequest来处理客户端对服务器的请求
publicclassBaseRequest : MonoBehaviour {
privateRequestCode requestCode = RequestCode.None;
publicvirtualvoid Awake()
{
}
}
28.创建RequestManager来管理Request
首先我们在RequestManager里创建一个字典用来存储客户端创建的Request,并且创建一个移除和添加Request的方法
publicclassRequestManager : BaseManager {
publicRequestManager(GameFacade facade) : base(facade) { }
privateDictionary<RequestCode, BaseRequest> requestDictionary = newDictionary<RequestCode, BaseRequest>();
//添加Request
publicvoidAddRequest(RequestCode requestCode,BaseRequest baseRequest)
{
requestDictionary.Add(requestCode, baseRequest);
}
//移除Request
publicvoidRemoveRequest(RequestCode requestCode)
{
requestDictionary.Remove(requestCode);
}
}
然后我们以GameFacaed作为中介者,提供与BaseRequest的接口
publicclassBaseRequest : MonoBehaviour {
privateRequestCode requestCode = RequestCode.None;
publicvirtualvoid Awake()
{
GameFacade._instance.AddRequest(requestCode, this);
}
publicvirtualvoid Destory()
{
GameFacade._instance.RemoveRequest(requestCode);
}
publicvirtualvoid SendRequest() { }
publicvirtualvoid Response(string data) { }
}
然后我们在BaseRequest里调用GameFacade中的方法,并且提供一个接受和发送Request的虚方法
publicclassBaseRequest : MonoBehaviour {
privateRequestCode requestCode = RequestCode.None;
publicvirtualvoid Awake()
{
GameFacade._instance.AddRequest(requestCode, this);
}
publicvirtualvoid Destory()
{
GameFacade._instance.RemoveRequest(requestCode);
}
publicvirtualvoid SendRequest() { }
publicvirtualvoid Response(string data) { }
}
29.将消息转发给对应的Request进行处理
首先,我们在RequestManager里提供一个方法来获取到对应的RequestCode,如果获取到,就调用baseRequest中的OnRespone对信息进行回复
publicvoidHandleResponse(RequestCode requestCode,string data)
{
//获得对应的Response
BaseRequest baseRequest = requestDictionary.TryGet<RequestCode,BaseRequest>(requestCode);
//回复对应的数据
if(baseRequest == null)
{
Debug.LogWarning("无法得到对应的RequestCode" + requestCode);
}
baseRequest.OnResponse(data);
}
在GameFaced中提供一个调用的接口
publicvoidHandleResponse(RequestCode requestCode,string data)
{
requestManager.HandleResponse(requestCode, data);
}
}
这个方法是是在ClientManager里实现的,当客户端接收到信息后,异步调用这个方法,选取对应RequestCode,这样的话,我们就实现了客户端读取信息的同时选择到对应的RequestCode
privatevoidOnProcessCallBack(RequestCode requestCode,string data)
{
//这是一个回调函数
facade.HandleResponse(requestCode, data);
}
30.开发登陆按钮
这里创建UIText,给它添加button组件,Transation改成Animation,给它添加上高亮时候变动大小的动画,就算初步开发我们的登录按钮了
31.设计UI界面
这里都是UGUI的一些基本操作,就不多赘述了,这里的话,我们引入了掉色板,当我们设计UI时,选取对应的颜色进行使用,会使得色调更加协调
32.创建我们面板管理脚本
这些脚本都继承自BasePanel,用来管理各个对应的面板,接下来的面板控制会放在这里面进行
33.修改一下我们Ui框架的json文件和对应面板的枚举类型,并且将我们的面板都放在这个路径下
publicenumUIPanelType {
Login,
Regist,
Start,
Message
}
{
"infoList":
[
{"panelTypeString":"Regist",
"path":"UIPanel/RegistPanel"},
{"panelTypeString":"Message",
"path":"UIPanel/MessagePanel"},
{"panelTypeString":"Start",
"path":"UIPanel/StartPanel"},
{"panelTypeString":"Login",
"path":"UIPanel/LoginPanel"},
}