斗地主项目总结
一、运行流程
开始界面
1. 点击开始游戏按钮,执行 PlayPanel 类 startClick 函数,
1 /// <summary> 2 /// 开始游戏按钮响应函数 3 /// </summary> 4 private void startClick() 5 { 6 Dispatch(AreaCode.UI, UIEvent.START_PANEL_ACTIVE, true); 7 }
该函数向 UI 层发送 START_PANEL_ACTIVE 事件,传输消息 true。
然后是 StartPanel 类 Execute 函数,
1 public override void Execute(int eventCode, object message) 2 { 3 switch (eventCode) 4 { 5 case UIEvent.START_PANEL_ACTIVE: 6 setPanelActive((bool)message); 7 break; 8 default: 9 break; 10 } 11 }
该函数接收消息,调用 setPanelActive 函数显示开始游戏面板。
点击注册账号按钮流程类似。
登录界面:
2. 点击登录按钮,执行 StartPanel 类 loginClick 函数,
1 /// <summary> 2 /// 登录按钮的点击事件处理 3 /// </summary> 4 private void loginClick() 5 { 6 // 若用户名输入为空 7 if (string.IsNullOrEmpty(inputAccount.text)) 8 { 9 promptMsg.Change("用户名输入为空", Color.red); 10 Dispatch(AreaCode.UI, UIEvent.PROMPT_MSG, promptMsg); 11 return; 12 } 13 // 若密码输入为空 14 if (string.IsNullOrEmpty(inputPassword.text)) 15 { 16 promptMsg.Change("密码输入为空", Color.red); 17 Dispatch(AreaCode.UI, UIEvent.PROMPT_MSG, promptMsg); 18 return; 19 } 20 // 若密码输入不是4到16位 21 if(inputPassword.text.Length < 4 22 || inputPassword.text.Length > 16) 23 { 24 promptMsg.Change("密码输入不是4到16位", Color.red); 25 Dispatch(AreaCode.UI, UIEvent.PROMPT_MSG, promptMsg); 26 return; 27 } 28 29 //需要和服务器交互了 30 AccountDto dto = new AccountDto(inputAccount.text, inputPassword.text); 31 socketMsg.Change(OpCode.ACCOUNT, AccountCode.LOGIN, dto); 32 Dispatch(AreaCode.NET, 0, socketMsg); 33 }
该函数先验证输入是否符合规定,若不符合,则输出相应消息;若符合,则与服务器交互。
输出相应消息实现,自定义消息类 PromptMsg,
1 /// <summary> 2 /// 提示窗口 3 /// </summary> 4 class PromptMsg 5 { 6 public string text; 7 public Color color; 8 9 public PromptMsg() 10 { 11 12 } 13 14 public PromptMsg(string text, Color color) 15 { 16 this.text = text; 17 this.color = color; 18 } 19 20 /// <summary> 21 /// 改变属性,防止多次 new 22 /// </summary> 23 /// <param name="text"></param> 24 /// <param name="color"></param> 25 public void Change(string text, Color color) 26 { 27 this.text = text; 28 this.color = color; 29 } 30 }
然后向 UI 层发送 PROMPT_MSG 事件,
然后是 PromptPanel 类 Execute 函数接收消息,
1 public override void Execute(int eventCode, object message) 2 { 3 switch (eventCode) 4 { 5 case UIEvent.PROMPT_MSG: // 提示信息 6 PromptMsg msg = message as PromptMsg; 7 PromptMessage(msg.text, msg.color); 8 break; 9 default: 10 break; 11 } 12 }
通过 PromptMessage 函数显示提醒效果,
1 /// <summary> 2 /// 提示消息 3 /// </summary> 4 /// <param name="text">显示文字</param> 5 /// <param name="color">显示颜色</param> 6 private void PromptMessage(string text, Color color) 7 { 8 txt.text = text; 9 txt.color = color; 10 cg.alpha = 0; 11 StartCoroutine(PromptAnim()); // 播放动画 12 }
从上面两个事件流程可得出 UI 事件的一般流程为:
首先为每个面板创建一个脚本来控制该面板,之后再 Awake 函数里使用 Bind 函数注册事件码,
1 void Awake() 2 { 3 Bind(UIEvent.START_PANEL_ACTIVE); // 注册事件码 4 }
然后添加 Execute 函数来接收外界发送的事件,
1 public override void Execute(int eventCode, object message) 2 { 3 switch (eventCode) 4 { 5 default: 6 break; 7 } 8 }
那么当我们需要实现某一事件的时候,只需要调用 Dispatch 函数来发送想发送的消息即可。
1 /// <summary> 2 /// 开始游戏按钮响应函数 3 /// </summary> 4 private void startClick() 5 { 6 Dispatch(AreaCode.UI, UIEvent.START_PANEL_ACTIVE, true); 7 }
那么,如何与服务器交互呢?
首先也需要自定义消息类 AccountDto(服务器端生成),
1 [Serializable] 2 public class AccountDto 3 { 4 public string Account; 5 public string Password; 6 7 public AccountDto(); 8 public AccountDto(string acc, string pwd); 9 }
和实际传输的信息类 SocketMsg,
1 /// <summary> 2 /// 网络消息 3 /// 作用:发送的时候 都要发送这个类 4 /// </summary> 5 public class SocketMsg 6 { 7 /// <summary> 8 /// 操作码 9 /// </summary> 10 public int OpCode { get; set; } 11 12 /// <summary> 13 /// 子操作 14 /// </summary> 15 public int SubCode { get; set; } 16 17 /// <summary> 18 /// 参数 19 /// </summary> 20 public object Value { get; set; } 21 22 public SocketMsg() 23 { 24 25 } 26 27 public SocketMsg(int opCode, int subCode, object value) 28 { 29 this.OpCode = opCode; 30 this.SubCode = subCode; 31 this.Value = value; 32 } 33 34 public void Change(int opCode, int subCode, object value) 35 { 36 this.OpCode = opCode; 37 this.SubCode = subCode; 38 this.Value = value; 39 } 40 }
接下来是把发送消息这个事件和发送的内容使用 Dispatch 派发出去
NetManager 类中的 Execute 函数接收事件,并向服务器发送信息,
1 public override void Execute(int eventCode, object message) 2 { 3 switch (eventCode) 4 { 5 case 0: // 发送消息 6 client.Send(message as SocketMsg); 7 break; 8 default: 9 break; 10 } 11 }
这时候服务器已经收到消息了, 服务器端 NetMsgCenter 类 OnReceive 函数接收到消息,并执行相应的逻辑,
1 public void OnReceive(ClientPeer client, SocketMsg msg) 2 { 3 switch (msg.OpCode) 4 { 5 case OpCode.ACCOUNT: 6 account.OnReceive(client, msg.SubCode, msg.Value); 7 break; 8 case OpCode.USER: 9 user.OnReceive(client, msg.SubCode, msg.Value); 10 break; 11 case OpCode.MATCH: 12 match.OnReceive(client, msg.SubCode, msg.Value); 13 break; 14 case OpCode.CHAT: 15 chat.OnReceive(client, msg.SubCode, msg.Value); 16 break; 17 case OpCode.FIGHT: 18 fight.OnReceive(client, msg.SubCode, msg.Value); 19 break; 20 default: 21 break; 22 } 23 }
我们发送的消息的 OpCode 为 ACCOUNT,所以会调用 account.OnReceive 函数,
1 public void OnReceive(ClientPeer client, int subCode, object value) 2 { 3 switch (subCode) 4 { 5 case AccountCode.REGIST_CREQ: 6 { 7 AccountDto dto = value as AccountDto; 8 //Console.WriteLine(dto.Account); 9 //Console.WriteLine(dto.Password); 10 regist(client, dto.Account, dto.Password); 11 } 12 break; 13 case AccountCode.LOGIN: 14 { 15 AccountDto dto = value as AccountDto; 16 //Console.WriteLine(dto.Account); 17 //Console.WriteLine(dto.Password); 18 login(client, dto.Account, dto.Password); 19 } 20 break; 21 default: 22 break; 23 } 24 }
我们发送的消息的 subCode 为 LOGIN,所以会调用 login 函数,
1 /// <summary> 2 /// 登录 3 /// </summary> 4 /// <param name="client"></param> 5 /// <param name="account"></param> 6 /// <param name="password"></param> 7 private void login(ClientPeer client, string account, string password) 8 { 9 SingleExecute.Instance.Execute(() => // 单线程 10 { 11 if (!accountCache.IsExist(account)) 12 { 13 //表示账号不存在 14 //client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, "账号不存在"); 15 client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, -1); 16 return; 17 } 18 19 if (accountCache.IsOnline(account)) 20 { 21 //表示账号在线 22 //client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, "账号在线"); 23 client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, -2); 24 return; 25 } 26 27 if (!accountCache.IsMatch(account, password)) 28 { 29 //表示账号密码不匹配 30 //client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, "账号密码不匹配"); 31 client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, -3); 32 return; 33 } 34 35 //登陆成功 36 accountCache.Online(client, account); 37 //client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, "登陆成功"); 38 client.Send(OpCode.ACCOUNT, AccountCode.LOGIN, 0); 39 }); 40 }
该函数验证账号密码,并向客户端发送相应的消息,
那么客户端已经收到回复了,客户端 NetManager 类 ProcessSocketMsg 函数会处理该消息,
1 private void ProcessSocketMsg(SocketMsg msg) 2 { 3 switch (msg.OpCode) 4 { 5 case OpCode.ACCOUNT : 6 accountHandler.OnReceive(msg.SubCode, msg.Value); 7 break; 8 case OpCode.USER: 9 userHandler.OnReceive(msg.SubCode, msg.Value); 10 break; 11 case OpCode.MATCH: 12 matchHandler.OnReceive(msg.SubCode, msg.Value); 13 break; 14 default: 15 break; 16 } 17 }
然后根据接收到的消息会调用 AccountHandler 类的 OnReceive 函数,
1 public override void OnReceive(int subCode, object message) 2 { 3 switch (subCode) 4 { 5 case AccountCode.LOGIN: // 登录 6 LoginResponce((int)message); 7 break; 8 case AccountCode.REGIST_SRES: // 注册 9 RegistResponce((int)message); 10 break; 11 default: 12 break; 13 } 14 }
根据 sunCode 会调用 LoginResponce 函数,
1 // 处理登录信息 2 private void LoginResponce(int value) 3 { 4 switch (value) 5 { 6 case 0: 7 //promptMsg.Change("登录成功", Color.green); 8 //Dispatch(AreaCode.UI, UIEvent.PROMPT_MSG, promptMsg); 9 // 跳到下一个场景 10 LoadSceneMsg msg = new LoadSceneMsg(1, 11 delegate() // 匿名委托 12 { 13 // 加载游戏信息 14 SocketMsg socketMsg = new SocketMsg(OpCode.USER, UserCode.GET_INFO_CREQ, null); 15 Dispatch(AreaCode.NET, 0, socketMsg); 16 Debug.Log("场景加载完成!"); 17 } 18 ); 19 Dispatch(AreaCode.SCENE, SceneEvent.LOAD_SCENE, msg); 20 break; 21 case -1: 22 promptMsg.Change("账号不存在", Color.red); 23 Dispatch(AreaCode.UI, UIEvent.PROMPT_MSG, promptMsg); 24 break; 25 case -2: 26 promptMsg.Change("账号在线", Color.red); 27 Dispatch(AreaCode.UI, UIEvent.PROMPT_MSG, promptMsg); 28 break; 29 case -3: 30 promptMsg.Change("账号密码不匹配", Color.red); 31 Dispatch(AreaCode.UI, UIEvent.PROMPT_MSG, promptMsg); 32 break; 33 default: 34 break; 35 }
该函数根据接收到的消息给出相应提示信息。
这就是一个相对完整的客户端服务器交互。