《攻城Online》快速原型:客户端设计

  “攻城”客户端采用Unity引擎并结合Photon框架进行开发。

  关于将Photon配置进游戏引擎中的操作本篇直接省略。

  在展开之前,先来看看Unity的文件夹组织层次。

  

  Audios放音频文件,Libs放一些外部的动态链接库文件,Models放模型资源,预留的Resources文件夹主要服务于Resource类,Scenes放场景文件,重点是Scripts文件夹。

  首先ClientLogic层存放与逻辑相关的脚本。Photon客户端采用事件监听体制,Event中存放事件数据类的定义,从下面的项目图能看出来。Scene又分为多个以“Scene”为后缀的文件夹,每个文件夹则代表一个场景的逻辑,每一个场景的逻辑脚本都存放在其对应的场景文件夹内,并且绑定在对应场景的一个游戏对象上。Data文件中有存放缓存数据的类的脚本,例如人物数据、怪物数据等将存放在这些类的实例中,以便访问和更新数据。PhotonClient文件夹则存放Photon客户端应用程序的脚本。下图是客户端原型的项目组织。

  

  可以看到,Event文件夹都是些自定义事件数据类,在上一层中有个EventCollection文件,其中对这些委托、事件进行声明。再讲下Scene,比方说CharacterScene对应Character这个场景,其中目前有两个脚本,他们继承于MonoBehavior绑定在场景中的一个名为Character的物体上,当加载场景后便会启动这两个脚本,跳转到其他场景便销毁,频繁地相互引用脚本是个坏习惯,这样做便是要求编写代码时要始终遵循单一职责原则。此外,这里还有两个接口,IEventReceive和IResonseReceive,一个要求OnEvent方法,另一个要求OnResponse方法,场景脚本依照需求实现这两个接口。

 1 //-----------------------------------------------------------------------------------------------------------
 2 // Copyright (C) 2015-2016 SiegeOnline
 3 // 版权所有
 4 //
 5 // 文件名:IEventReceive.cs
 6 //
 7 // 文件功能描述:
 8 //
 9 // 处理广播事件接口,由各场景逻辑实现
10 //
11 // 创建标识:taixihuase 20150720
12 //
13 // 修改标识:
14 // 修改描述:
15 // 
16 //
17 // 修改标识:
18 // 修改描述:
19 //
20 //----------------------------------------------------------------------------------------------------------
21 
22 using ExitGames.Client.Photon;
23 using SiegeOnlineClient.PhotonClient;
24 // ReSharper disable CheckNamespace
25 
26 namespace SiegeOnlineClient.ClientLogic
27 {
28     /// <summary>
29     /// 类型:接口
30     /// 名称:IEventReceive
31     /// 作者:taixihuase
32     /// 作用:广播处理接口
33     /// 编写日期:2015/7/20
34     /// </summary>
35     interface IEventReceive
36     {
37         /// <summary>
38         /// 类型:方法
39         /// 名称:OnEvent
40         /// 作者:taixihuase
41         /// 作用:当接收到广播时,对广播进行处理
42         /// 编写日期:2015/7/20
43         /// </summary>
44         /// <param name="eventData"></param>
45         /// <param name="service"></param>
46         void OnEvent(EventData eventData, PhotonService service);
47     }
48 }
//-----------------------------------------------------------------------------------------------------------
// Copyright (C) 2015-2016 SiegeOnline
// 版权所有
//
// 文件名:IResponseReceive.cs
//
// 文件功能描述:
//
// 处理消息回应接口,由各场景逻辑实现
//
// 创建标识:taixihuase 20150720
//
// 修改标识:
// 修改描述:
// 
//
// 修改标识:
// 修改描述:
//
//----------------------------------------------------------------------------------------------------------

using ExitGames.Client.Photon;
using SiegeOnlineClient.PhotonClient;
// ReSharper disable CheckNamespace

namespace SiegeOnlineClient.ClientLogic
{
    /// <summary>
    /// 类型:接口
    /// 名称:IResponseReceive
    /// 作者:taixihuase
    /// 作用:回应处理接口
    /// 编写日期:2015/7/20
    /// </summary>
    interface IResponseReceive
    {
        /// <summary>
        /// 类型:方法
        /// 名称:OnResponse
        /// 作者:taixihuase
        /// 作用:当接收到回应消息时,对消息进行处理
        /// 编写日期:2015/7/20
        /// </summary>
        /// <param name="operationResponse"></param>
        /// <param name="service"></param>
        void OnResponse(OperationResponse operationResponse, PhotonService service);
    }
}

  最底下的PhotonClient文件夹有一个PhotonService和PhotonSingleton,前者是真正的主逻辑脚本,在其中调用其他脚本的方法,后者是一个单例类,其中声明了一个静态的PhotonService对象,客户端代码便通过单例获取到Service,下面先贴上这两份代码。

  1 //-----------------------------------------------------------------------------------------------------------
  2 // Copyright (C) 2015-2016 SiegeOnline
  3 // 版权所有
  4 //
  5 // 文件名:PhotonSingleton.cs
  6 //
  7 // 文件功能描述:
  8 //
  9 // Photon 客户端单例,存放客户端进程实例及服务端信息
 10 //
 11 // 创建标识:taixihuase 20150717
 12 //
 13 // 修改标识:
 14 // 修改描述:
 15 // 
 16 //
 17 // 修改标识:
 18 // 修改描述:
 19 //
 20 //----------------------------------------------------------------------------------------------------------
 21 
 22 using UnityEngine;
 23 // ReSharper disable UnusedMember.Local
 24 // ReSharper disable CheckNamespace
 25 
 26 namespace SiegeOnlineClient.PhotonClient
 27 {
 28     /// <summary>
 29     /// 类型:类
 30     /// 名称:PhotonSingleton
 31     /// 作者:taixihuase
 32     /// 作用:Photon 单例类,Unity 通过实例化该单例启动 PhotonService 客户端主处理进程
 33     /// 编写日期:2015/7/17
 34     /// </summary>
 35     public class PhotonSingleton : MonoBehaviour
 36     {
 37         // 全局静态单例
 38         private static PhotonSingleton _instance;
 39 
 40         // 单例属性
 41         public static PhotonSingleton Instance
 42         {
 43             get
 44             {
 45                 // 若获取不到单例,则寻找该单例,并拒绝销毁单例所挂载的对象上
 46                 if (_instance == null)
 47                 {
 48                     _instance = FindObjectOfType<PhotonSingleton>();
 49                     DontDestroyOnLoad(_instance.gameObject);
 50                 }
 51 
 52                 return _instance;
 53             }
 54         }
 55 
 56         // 客户端主服务进程
 57         public static PhotonService Service;
 58 
 59         // 服务端 IP 地址
 60         public string ServerIp = "localhost";
 61 
 62         // 服务端端口号
 63         public int ServerPort = 5055;
 64 
 65         // 服务端进程名
 66         public string ServerName = "SiegeOnlineServer";
 67 
 68         /// <summary>
 69         /// 类型:方法
 70         /// 名称:Awake
 71         /// 作者:taixihuase
 72         /// 作用:创建单例
 73         /// 编写日期:2015/7/17
 74         /// </summary>
 75         void Awake()
 76         {
 77             // 若当前不存在单例,则创建单例并实例化客户端服务进程
 78             if (_instance == null)
 79             {
 80                 _instance = this;
 81                 Service = new PhotonService();
 82                 DontDestroyOnLoad(this);
 83             }
 84             else
 85             {
 86                 // 若已存在一个单例,则销毁该单例所挂载的对象
 87                 if (this != _instance)
 88                 {
 89                     Destroy(gameObject);
 90                 }
 91             }
 92         }
 93 
 94         // Use this for initialization
 95         void Start()
 96         {
 97             Service.Connect(ServerIp, ServerPort, ServerName);
 98         }
 99 
100         // Update is called once per frame
101         void Update()
102         {
103             Service.Service();
104             Debug.Log(Service.ServerConnected);
105         }
106 
107         /// <summary>
108         /// 类型:方法
109         /// 名称:OnApplicationQuit
110         /// 作者:taixihuase
111         /// 作用:退出进程
112         /// 编写日期:2015/7/17
113         /// </summary>
114         void OnApplicationQuit()
115         {
116             Service.Disconnect();
117         }
118     }
119 }

  关于单例模式,这里不做介绍,只要注意加上DontDestroyOnLoad(this);这一句,单例类继承自MonoBehavior,Start方法启动Service的连接方法Connect,Update中让Service持续进行服务。因为使用的是Visual Studio进行开发,所以using引用下方有Resharper插件的识别语句,不必管它。本篇的重点是下面的这个PhotonService类。

  1 //-----------------------------------------------------------------------------------------------------------
  2 // Copyright (C) 2015-2016 SiegeOnline
  3 // 版权所有
  4 //
  5 // 文件名:PhotonService.cs
  6 //
  7 // 文件功能描述:
  8 //
  9 // Photon 客户端主进程,进行连线、消息收发及监听等操作
 10 //
 11 // 创建标识:taixihuase 20150717
 12 //
 13 // 修改标识:
 14 // 修改描述:
 15 // 
 16 //
 17 // 修改标识:
 18 // 修改描述:
 19 //
 20 //----------------------------------------------------------------------------------------------------------
 21 
 22 using ExitGames.Client.Photon;
 23 using SiegeOnline.ClientLogic.Scene.LoginScene;
 24 using SiegeOnline.ClientLogic.Scene.WorldScene;
 25 using SiegeOnlineServer.Protocol;
 26 using SiegeOnlineClient.ClientLogic;
 27 using SiegeOnlineClient.Data.Player;
 28 using UnityEngine;
 29 // ReSharper disable UseNullPropagation
 30 // ReSharper disable CheckNamespace
 31 
 32 namespace SiegeOnlineClient.PhotonClient
 33 {
 34     /// <summary>
 35     /// 类型:类
 36     /// 名称:PhotonService
 37     /// 作者:taixihuase
 38     /// 作用:Photon 客户端主进程
 39     /// 编写日期:2015/7/17
 40     /// </summary>
 41     public class PhotonService : IPhotonPeerListener
 42     {
 43         // 连线用的 peer
 44         public PhotonPeer Peer { protected set; get; }
 45 
 46         // 连线状态
 47         public bool ServerConnected { protected set; get; }
 48 
 49         // 存放 Debug 信息
 50         public string DebugMessage { protected set; get; }
 51 
 52         // 事件集合
 53         public static EventCollection Events = new EventCollection();
 54 
 55 
 56         // 玩家数据缓存
 57         public static PlayerData Player = new PlayerData();
 58 
 59         /// <summary>
 60         /// 类型:方法
 61         /// 名称:PhotonService
 62         /// 作者:taixihuase
 63         /// 作用:程序运行后,构造客户端主进程实例
 64         /// 编写日期:2015/7/17
 65         /// </summary>
 66         public PhotonService()
 67         {
 68             Peer = null;
 69             ServerConnected = false;
 70             DebugMessage = "";
 71         }
 72 
 73         /// <summary>
 74         /// 类型:方法
 75         /// 名称:Connect
 76         /// 作者:taixihuase
 77         /// 作用:尝试通过 ip 地址、端口及服务端进程名,与服务端连线
 78         /// 编写日期:2015/7/17
 79         /// </summary>
 80         /// <param name="ip"></param>
 81         /// <param name="port"></param>
 82         /// <param name="serverName"></param>
 83         public void Connect(string ip, int port, string serverName)
 84         {
 85             string serverAddress = ip + ":" + port.ToString();
 86             Peer = new PhotonPeer(this, ConnectionProtocol.Udp);
 87             Peer.Connect(serverAddress, serverName);
 88         }
 89 
 90         /// <summary>
 91         /// 类型:方法
 92         /// 名称:DebugReturn
 93         /// 作者:taixihuase
 94         /// 作用:获取返回的 Debug 消息
 95         /// 编写日期:2015/7/17
 96         /// </summary>
 97         /// <param name="level"></param>
 98         /// <param name="message"></param>
 99         public void DebugReturn(DebugLevel level, string message)
100         {
101             DebugMessage = message;
102         }
103 
104         /// <summary>
105         /// 类型:方法
106         /// 名称:OnOperationResponse
107         /// 作者:taixihuase
108         /// 作用:客户端发送请求后,接收并处理相应的服务端响应内容
109         /// 编写日期:2015/7/17
110         /// </summary>
111         /// <param name="operationResponse"></param>
112         public void OnOperationResponse(OperationResponse operationResponse)
113         {
114             switch (operationResponse.OperationCode)
115             {
116                 // 账号登陆
117                 case (byte) OperationCode.Login:
118                     Object.FindObjectOfType<Login>().OnResponse(operationResponse, this);
119                     break;
120 
121                 // 玩家进入场景
122                 case (byte) OperationCode.WorldEnter:
123                     Object.FindObjectOfType<World>().OnResponse(operationResponse, this);
124                     break;
125 
126                 
127             }
128         }
129 
130         /// <summary>
131         /// 类型:方法
132         /// 名称:OnStatusChanged
133         /// 作者:taixihuase
134         /// 作用:当连接状态发生改变时,回调触发
135         /// 编写日期:2015/7/17
136         /// </summary>
137         /// <param name="statusCode"></param>
138         public void OnStatusChanged(StatusCode statusCode)
139         {
140             switch (statusCode)
141             {
142                 case StatusCode.Connect:            // 连接
143                     ServerConnected = true;
144                     break;
145 
146                 case StatusCode.Disconnect:         // 断线
147                     ServerConnected = false;
148                     Peer = null;
149                     break;
150             }
151         }
152 
153         /// <summary>
154         /// 类型:方法
155         /// 名称:OnEvent
156         /// 作者:taixihuase
157         /// 作用:监听服务端发来的广播并回调触发事件
158         /// 编写日期:2015/7/17
159         /// </summary>
160         /// <param name="eventData"></param>
161         public void OnEvent(EventData eventData)
162         {
163             switch (eventData.Code)
164             { 
165                 // 有玩家进入场景
166                 case (byte) EventCode.WorldEnter:
167                     Object.FindObjectOfType<World>().OnEvent(eventData, this);
168                     break;
169 
170 
171             }
172         }
173 
174         /// <summary>
175         /// 类型:方法
176         /// 名称:Service
177         /// 作者:taixihuase
178         /// 作用:呼叫服务
179         /// 编写日期:2015/7/17
180         /// </summary>
181         public void Service()
182         {
183             if (Peer != null)
184             {
185                 Peer.Service();
186             }
187         }
188 
189         /// <summary>
190         /// 类型:方法
191         /// 名称:Disconnect
192         /// 作者:taixihuase
193         /// 作用:断开与服务端之间的连接
194         /// 编写日期:2015/7/17
195         /// </summary>
196         public void Disconnect()
197         {
198             if (Peer != null)
199             {
200                 Peer.Disconnect();
201             }
202         }
203     }
204 }

  要使用Photon,必须让其继承接口IPhotonPeerListener,然后实现其方法。

  先看字段声明。PhotonPeer类的对象代表该客户端连线,用于与服务端连接。另一个重点是一些静态声明的集合类,如上面的EventCollection类,其定义是这样的:

  1 //-----------------------------------------------------------------------------------------------------------
  2 // Copyright (C) 2015-2016 SiegeOnline
  3 // 版权所有
  4 //
  5 // 文件名:EventCollection.cs
  6 //
  7 // 文件功能描述:
  8 //
  9 // 事件集合,存放委托、事件的声明及调用
 10 //
 11 // 创建标识:taixihuase 20150719
 12 //
 13 // 修改标识:
 14 // 修改描述:
 15 // 
 16 //
 17 // 修改标识:
 18 // 修改描述:
 19 //
 20 //----------------------------------------------------------------------------------------------------------
 21 
 22 using SiegeOnlineClient.ClientLogic.Event;
 23 // ReSharper disable UseNullPropagation
 24 // ReSharper disable CheckNamespace
 25 
 26 namespace SiegeOnlineClient.ClientLogic
 27 {
 28     /// <summary>
 29     /// 类型:类
 30     /// 名称:EventCollection
 31     /// 作者:taixihuase
 32     /// 作用:事件集合,用于声明委托、事件及其相应判空函数
 33     /// 编写日期:2015/7/19
 34     /// </summary>
 35     public class EventCollection
 36     {
 37         #region 与登录相关的委托及事件
 38 
 39         // 登录委托
 40         public delegate void LoginEventHandler(object sender, LoginEventArgs e);
 41 
 42         // 登录事件
 43         public event LoginEventHandler MyLogin;
 44 
 45         public void OnLogin(object sender, LoginEventArgs e)
 46         {
 47             if (MyLogin != null)
 48             {
 49                 MyLogin(sender, e);
 50             }
 51         }
 52 
 53         #endregion
 54 
 55         #region 与创建角色相关的委托及事件
 56 
 57         // 创建角色委托
 58         public delegate void CreateCharacterEventHandler(object sender, CreateCharacterEventArgs e);
 59 
 60         // 创建角色事件
 61         public event CreateCharacterEventHandler MyCreateCharacter;
 62 
 63         public void OnCreateCharacter(object sender, CreateCharacterEventArgs e)
 64         {
 65             if (MyCreateCharacter != null)
 66             {
 67                 MyCreateCharacter(sender, e);
 68             }
 69         }
 70 
 71         #endregion
 72 
 73         #region 与加载角色相关的委托及事件
 74 
 75         // 加载角色委托
 76         public delegate void LoadCharacterEventHandler(object sender, LoadCharacterEventArgs e);
 77 
 78         // 加载角色事件
 79         public event LoadCharacterEventHandler MyLoadCharacter;
 80 
 81         public void OnLoadCharacter(object sender, LoadCharacterEventArgs e)
 82         {
 83             if (MyLoadCharacter != null)
 84             {
 85                 MyLoadCharacter(sender, e);
 86             }
 87         }
 88 
 89         #endregion
 90 
 91         #region 与玩家角色进入场景相关的委托及事件
 92 
 93         // 进入场景委托
 94         public delegate void WorldEnterEventHandler(object sender, WorldEnterEventArgs e);
 95 
 96         // 自身进入场景
 97         public event WorldEnterEventHandler MyWorldEnter;
 98 
 99         // 任意进入场景
100         public event WorldEnterEventHandler AnyWorldEnter;
101 
102         public void OnWorldEnter(object sender, WorldEnterEventArgs e)
103         {
104             if (e.MyCharacter != null)
105             {
106                 if (MyWorldEnter != null)
107                 {
108                     MyWorldEnter(sender, e);
109                 }
110             }
111             else if (e.AnyCharacter != null)
112             {
113                 if (AnyWorldEnter != null)
114                 {
115                     AnyWorldEnter(sender, e);
116                 }
117             }
118         }
119 
120         #endregion
121 
122         #region 其他
123 
124         #endregion
125     }
126 }

  该集合存放了委托、事件声明及调用,事件的使用这类不介绍。这些类对象设为静态,只实例化一次便一直存在到程序结束运行为止,因此可以一直访问。后面的PlayerData玩家数据缓存也是一样的道理。

  接下来讲PhotonService的两个主要方法:OnOperationResponse和OnEvent。它们对应于服务端的SendOperationResponse和SendEvent或者SentTo等方法。

 1         /// <summary>
 2         /// 类型:方法
 3         /// 名称:OnOperationResponse
 4         /// 作者:taixihuase
 5         /// 作用:客户端发送请求后,接收并处理相应的服务端响应内容
 6         /// 编写日期:2015/7/17
 7         /// </summary>
 8         /// <param name="operationResponse"></param>
 9         public void OnOperationResponse(OperationResponse operationResponse)
10         {
11             switch (operationResponse.OperationCode)
12             {
13                 // 账号登陆
14                 case (byte) OperationCode.Login:
15                     Object.FindObjectOfType<Login>().OnResponse(operationResponse, this);
16                     break;
17 
18                 // 玩家进入场景
19                 case (byte) OperationCode.WorldEnter:
20                     Object.FindObjectOfType<World>().OnResponse(operationResponse, this);
21                     break;
22 
23                 
24             }
25         }

   OnOperationResponse,接受OperationResponse类型的一个参数,该参数包含的内容与服务端发送过来的OperationResponse对象内容完全一致,包含操作识别码和字典存储的数据。用一个多分支语句判别其中的OperationCode,然后查找相应的场景脚本,并将OperationResponse对象原封不动传给该脚本的OnResponse方法,同时把Service也传给它。能够发送某个操作请求给服务端,然后再接收回相同操作码的服务端答应,则一般都存在相应的脚本或场景,因此一般不必当心FindObjectOfType操作失败。当然加上判空处理也是没问题的,因为还是可能有一些设计上的先后顺序问题,比如跳转场景,如果场景过大加载缓慢,则可能相应脚本未能赶在服务端消息到来前实例化,那么便会出现对象为空的错误情况。

  这里以登录作为讲解。

  1 //-----------------------------------------------------------------------------------------------------------
  2 // Copyright (C) 2015-2016 SiegeOnline
  3 // 版权所有
  4 //
  5 // 文件名:Login.cs
  6 //
  7 // 文件功能描述:
  8 //
  9 // 登录场景脚本,处理登录的逻辑及相关 UI
 10 //
 11 // 创建标识:taixihuase 20150717
 12 //
 13 // 修改标识:
 14 // 修改描述:
 15 // 
 16 //
 17 // 修改标识:
 18 // 修改描述:
 19 //
 20 //----------------------------------------------------------------------------------------------------------
 21 
 22 using System.Collections;
 23 using System.Collections.Generic;
 24 using ExitGames.Client.Photon;
 25 using SiegeOnline.ClientLogic.Scene.CharacterScene;
 26 using SiegeOnlineClient.ClientLogic;
 27 using SiegeOnlineClient.PhotonClient;
 28 using SiegeOnlineServer.Protocol;
 29 using UnityEngine;
 30 using UnityEngine.UI;
 31 using SiegeOnlineClient.ClientLogic.Event;
 32 using SiegeOnlineServer.Protocol.Common.Character;
 33 using SiegeOnlineServer.Protocol.Common.User;
 34 // ReSharper disable CheckNamespace
 35 // ReSharper disable UnusedMember.Local
 36 
 37 namespace SiegeOnline.ClientLogic.Scene.LoginScene
 38 {
 39     /// <summary>
 40     /// 类型:类
 41     /// 名称:Login
 42     /// 作者:taixihuase
 43     /// 作用:客户端登录类
 44     /// 编写日期:2015/7/17
 45     /// </summary>
 46     public class Login : MonoBehaviour, IResponseReceive
 47     {
 48         // 登录参数
 49         private LoginInfo _loginInfo;
 50 
 51         // 账号输入框
 52         public InputField Account;
 53 
 54         // 密码输入框
 55         public InputField Password;
 56 
 57         // 登录按钮
 58         public Button LoginButton;
 59 
 60         // 退出按钮
 61         public Button ExitButton;
 62 
 63         // Use this for initialization
 64         private void Start()
 65         {
 66             // 注册方法
 67             PhotonService.Events.MyLogin += CharacterNotExist;
 68             PhotonService.Events.MyLogin += ErrorInput;
 69             PhotonService.Events.MyLogin += RepeatedLogin;
 70             PhotonService.Events.MyLogin += CharacterExist;
 71         }
 72 
 73         public void OnResponse(OperationResponse operationResponse, PhotonService service)
 74         {
 75             LoginEventArgs e = new LoginEventArgs(operationResponse);
 76             PhotonService.Events.OnLogin(service, e);
 77         }
 78 
 79         #region UI 方法
 80 
 81         /// <summary>
 82         /// 类型:方法
 83         /// 名称:OnLoginButtonDown
 84         /// 作者:taixihuase
 85         /// 作用:当按下登录按钮时触发登录事件,将登录信息发送给服务端
 86         /// 编写日期:2015/7/17
 87         /// </summary>
 88         public void OnLoginButtonDown()
 89         {
 90             if (PhotonSingleton.Service.ServerConnected)
 91             {
 92                 if (Account.text.Length > 0 && Password.text.Length > 0)
 93                 {
 94                     _loginInfo = new LoginInfo(Account.text, Password.text);
 95 
 96                     byte[] data = Serialization.Serialize(_loginInfo);
 97                     var parameter = new Dictionary<byte, object>
 98                     {
 99                         {(byte) ParameterCode.Login, data}
100                     };
101 
102                     PhotonSingleton.Service.Peer.OpCustom((byte)OperationCode.Login, parameter, true);
103                 }
104             }
105         }
106 
107         /// <summary>
108         /// 类型:方法
109         /// 名称:OnExitButtonDown
110         /// 作者:taixihuase
111         /// 作用:当按下退出按钮时触发退出事件,退出进程,Debug模式无效
112         /// 编写日期:2015/7/17
113         /// </summary>
114         public void OnExitButtonDown()
115         {
116             Application.Quit();
117         }
118 
119         #endregion
120 
121         #region 用于注册事件的方法
122 
123         /// <summary>
124         /// 类型:方法
125         /// 名称:ErrorInput
126         /// 作者:taixihuase
127         /// 作用:当账号或密码有误时触发
128         /// 编写日期:2015/7/29
129         /// </summary>
130         /// <param name="sender"></param>
131         /// <param name="e"></param>
132         private void ErrorInput(object sender, LoginEventArgs e)
133         {
134             if (e.OperationResponse.ReturnCode == (short) ErrorCode.InvalidOperation)
135             {
136                 Debug.Log(e.OperationResponse.DebugMessage);
137             }
138         }
139 
140         /// <summary>
141         /// 类型:方法
142         /// 名称:RepeatedLogin
143         /// 作者:taixihuase
144         /// 作用:当尝试登录一个已在线账号时触发
145         /// 编写日期:2015/7/29
146         /// </summary>
147         /// <param name="sender"></param>
148         /// <param name="e"></param>
149         private void RepeatedLogin(object sender, LoginEventArgs e)
150         {
151             if (e.OperationResponse.ReturnCode == (short) ErrorCode.RepeatedOperation)
152             {
153                 Debug.Log(e.OperationResponse.DebugMessage);
154             }
155         }
156 
157         /// <summary>
158         /// 类型:方法
159         /// 名称:CharacterExist
160         /// 作者:taixihuase
161         /// 作用:当登录账号成功并且成功获取到当前账号的角色数据时触发
162         /// 编写日期:2015/7/29
163         /// </summary>
164         /// <param name="sender"></param>
165         /// <param name="e"></param>
166         private void CharacterExist(object sender, LoginEventArgs e)
167         {
168             if (e.OperationResponse.ReturnCode == (short) ErrorCode.Ok)
169             {
170                 DontDestroyOnLoad(transform.parent);
171                 Application.LoadLevel("Character");
172 
173                 Character character = (Character)
174                     Serialization.Deserialize(e.OperationResponse.Parameters[(byte) ParameterCode.Login]);
175 
176                 StartCoroutine(LoadCharacter(sender, character));
177             }
178         }
179 
180         /// <summary>
181         ///  类型:方法
182         /// 名称:CharacterNotExist
183         /// 作者:taixihuase
184         /// 作用:当登录账号成功并且该账号未创建角色时触发
185         /// 编写日期:2015/7/29
186         /// </summary>
187         /// <param name="sender"></param>
188         /// <param name="e"></param>
189         private void CharacterNotExist(object sender, LoginEventArgs e)
190         {
191             if (e.OperationResponse.ReturnCode == (short)ErrorCode.CharacterNotFound)
192             {
193                 DontDestroyOnLoad(transform.parent);
194                 Application.LoadLevel("Character");
195 
196                 UserBase user = (UserBase)
197                     Serialization.Deserialize(e.OperationResponse.Parameters[(byte)ParameterCode.Login]);
198                 Debug.Log(user.Nickname + " have no character...");
199 
200                 StartCoroutine(CreateCharacter(sender, user));
201             }
202         }
203 
204         #endregion
205 
206         #region 协程方法
207 
208         /// <summary>
209         /// 类型:方法
210         /// 名称:LoadCharacter
211         /// 作者:taixihuase
212         /// 作用:当成功获取到角色数据时触发加载角色事件
213         /// 编写日期:2015/7/29
214         /// </summary>
215         /// <param name="sender"></param>
216         /// <param name="character"></param>
217         /// <returns></returns>
218         private IEnumerator LoadCharacter(object sender, Character character)
219         {
220             LoadCharacter load;
221             while ((load = FindObjectOfType<LoadCharacter>()) == null)
222             {
223                 yield return null;
224             }
225             LoadCharacterEventArgs lc = new LoadCharacterEventArgs(character);
226             load.OnLoad(sender, lc);
227 
228             Destroy(transform.parent.gameObject);
229         }
230 
231         /// <summary>
232         /// 类型:方法
233         /// 名称:CreateCharacter
234         /// 作者:taixihuase
235         /// 作用:当成功获取到角色数据时触发创建角色事件
236         /// 编写日期:2015/7/29
237         /// </summary>
238         /// <param name="sender"></param>
239         /// <param name="user"></param>
240         /// <returns></returns>
241         private IEnumerator CreateCharacter(object sender, UserBase user)
242         {
243             CreateCharacter create;
244             while ((create = FindObjectOfType<CreateCharacter>()) == null)
245             {
246                 yield return null;
247             }
248             CreateCharacterEventArgs cc = new CreateCharacterEventArgs(user);
249             create.OnCreate(sender, cc);
250 
251             Destroy(transform.parent.gameObject);
252         }
253 
254         #endregion
255 
256         private void OnDestroy()
257         {
258             PhotonService.Events.MyLogin -= CharacterNotExist;
259             PhotonService.Events.MyLogin -= ErrorInput;
260             PhotonService.Events.MyLogin -= RepeatedLogin;
261             PhotonService.Events.MyLogin -= CharacterExist;
262         }
263     }
264 }

   该脚本需要继承IResponseReceive接口,Start方法先为EventCollection事件集合实例中的MyLogin事件绑定几个方法,当进行登录并接收到回应后,从Service调用OnResponse方法,该方法实例化一个LoginEventArgs类的事件数据e,LoginEventArgs类需要接收一个OperationResponse类型的对象。然后调用事件集合中的OnLogin方法,OnLogin将会把数据e发送给所有已绑定的方法,然后这四个被绑定的方法对其ReturnCode进行判别,从而执行相应处理。整个流程其实非常清晰。PhotonService接收答应,识别并调用相应脚本的OnResponse方法,OnResponse方法实例化一个事件数据对象,然后调用其方法。注意脚本销毁时需要解除绑定,如OnDestroy方法那样做。

  好了,最后还差一个OnEvent,道理其实跟OnResponse一模一样,只是调用的方法改为对应场景脚本的OnEvent方法而已,其后依旧是事件处理操作,当服务端对客户端发送广播时,便会触发OnEvent回调方法。

 1         /// <summary>
 2         /// 类型:方法
 3         /// 名称:OnEvent
 4         /// 作者:taixihuase
 5         /// 作用:监听服务端发来的广播并回调触发事件
 6         /// 编写日期:2015/7/17
 7         /// </summary>
 8         /// <param name="eventData"></param>
 9         public void OnEvent(EventData eventData)
10         {
11             switch (eventData.Code)
12             { 
13                 // 有玩家进入场景
14                 case (byte) EventCode.WorldEnter:
15                     Object.FindObjectOfType<World>().OnEvent(eventData, this);
16                     break;
17 
18 
19             }
20         }

  如果上面理解的话,看World场景的代码也自然能够通了。

  1 //-----------------------------------------------------------------------------------------------------------
  2 // Copyright (C) 2015-2016 SiegeOnline
  3 // 版权所有
  4 //
  5 // 文件名:World.cs
  6 //
  7 // 文件功能描述:
  8 //
  9 // 世界场景脚本,处理游戏主场景的逻辑及相关 UI
 10 //
 11 // 创建标识:taixihuase 20150719
 12 //
 13 // 修改标识:
 14 // 修改描述:
 15 // 
 16 //
 17 // 修改标识:
 18 // 修改描述:
 19 //
 20 //----------------------------------------------------------------------------------------------------------
 21 
 22 using ExitGames.Client.Photon;
 23 using SiegeOnlineClient.ClientLogic;
 24 using SiegeOnlineClient.ClientLogic.Event;
 25 using SiegeOnlineClient.PhotonClient;
 26 using SiegeOnlineServer.Protocol;
 27 using UnityEngine;
 28 using UnityEngine.UI;
 29 // ReSharper disable UnusedMember.Local
 30 // ReSharper disable CheckNamespace
 31 
 32 namespace SiegeOnline.ClientLogic.Scene.WorldScene
 33 {
 34     public class World : MonoBehaviour, IEventReceive, IResponseReceive
 35     {
 36         // 玩家上线提示文本
 37         public Text LoginTip;
 38 
 39         // Use this for initialization
 40         void Start()
 41         {
 42             PhotonService.Events.MyWorldEnter += MyWorldPlayerEnter;
 43             PhotonService.Events.AnyWorldEnter += AnyPlayerEnter;
 44         }
 45 
 46         // Update is called once per frame
 47         private void Update()
 48         {
 49 
 50         }
 51 
 52         public void OnResponse(OperationResponse operationResponse, PhotonService service)
 53         {
 54             // 判断事件类型并调用对应的方法
 55             switch (operationResponse.OperationCode)
 56             {
 57                 // 玩家角色进入场景
 58                 case (byte) OperationCode.WorldEnter:
 59                     OnEnter(operationResponse, service);
 60                     break;
 61             }
 62         }
 63 
 64         public void OnEvent(EventData eventData, PhotonService service)
 65         {
 66             // 判断事件类型并调用对应的方法
 67             switch (eventData.Code)
 68             {
 69                 // 玩家角色进入场景
 70                 case (byte) EventCode.WorldEnter:
 71                     OnEnter(eventData, service);
 72                     break;
 73             }
 74         }
 75 
 76         #region 用于触发事件时选择的事件类型
 77 
 78         #region 玩家角色进入场景
 79 
 80         /// <summary>
 81         /// 类型:方法
 82         /// 名称:OnEnter
 83         /// 作者:taixihuase
 84         /// 作用:当自身角色进入场景时,触发事件
 85         /// 编写日期:2015/7/22
 86         /// </summary>
 87         /// <param name="operationResponse"></param>
 88         /// <param name="service"></param>
 89         private void OnEnter(OperationResponse operationResponse, PhotonService service)
 90         {
 91             WorldEnterEventArgs e = new WorldEnterEventArgs(operationResponse);
 92             PhotonService.Events.OnWorldEnter(service, e);
 93         }
 94 
 95         /// <summary>
 96         /// 类型:方法
 97         /// 名称:OnEnter
 98         /// 作者:taixihuase
 99         /// 作用:当有玩家进入场景时,触发事件
100         /// 编写日期:2015/7/22
101         /// </summary>
102         /// <param name="eventData"></param>
103         /// <param name="service"></param>
104         private void OnEnter(EventData eventData, PhotonService service)
105         {
106             WorldEnterEventArgs e = new WorldEnterEventArgs(eventData);
107             PhotonService.Events.OnWorldEnter(service, e);
108         }
109 
110         #endregion
111 
112         #endregion
113 
114         #region 用于注册事件的方法
115 
116         #region 玩家角色进入场景
117 
118         /// <summary>
119         /// 类型:方法
120         /// 名称:MyWorldPlayerEnter
121         /// 作者:taixihuase
122         /// 作用:当自己角色进入游戏场景时
123         /// 编写日期:2015/7/29
124         /// </summary>
125         /// <param name="sender"></param>
126         /// <param name="e"></param>
127         private void MyWorldPlayerEnter(object sender, WorldEnterEventArgs e)
128         {
129             Debug.Log(e.MyCharacter.Attribute.WorldEnterTime);
130         }
131 
132         /// <summary>
133         /// 类型:方法
134         /// 名称:AnyWorldPlayerEnter
135         /// 作者:taixihuase
136         /// 作用:当任意角色进入游戏场景时
137         /// 编写日期:2015/7/29
138         /// </summary>
139         /// <param name="sender"></param>
140         /// <param name="e"></param>
141         private void AnyPlayerEnter(object sender, WorldEnterEventArgs e)
142         {
143             LoginTip.text = "玩家 " + e.AnyCharacter.Nickname + " 上线了!";
144         }
145 
146         #endregion
147 
148         #endregion
149 
150         #region UI方法
151 
152         #endregion
153 
154         void OnDestroy()
155         {
156             PhotonService.Events.MyWorldEnter -= MyWorldPlayerEnter;
157             PhotonService.Events.AnyWorldEnter -= AnyPlayerEnter;
158         }
159     }
160 }

  最后附上几张与客户端相关的UML图。

  

  

  

  

  好了,这就是客户端的框架,下篇则介绍协议“Protocol”。

posted @ 2015-08-01 01:10  攻城Online  阅读(406)  评论(0编辑  收藏  举报