标签:
“攻城”客户端采用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”。
标签:
原文地址:http://www.cnblogs.com/SiegeOL/p/4693496.html