「攻城」客戶端採用Unity引擎並結合Photon框架進行開發。ios
關於將Photon配置進遊戲引擎中的操做本篇直接省略。緩存
在展開以前,先來看看Unity的文件夾組織層次。框架
Audios放音頻文件,Libs放一些外部的動態連接庫文件,Models放模型資源,預留的Resources文件夾主要服務於Resource類,Scenes放場景文件,重點是Scripts文件夾。函數
首先ClientLogic層存放與邏輯相關的腳本。Photon客戶端採用事件監聽體制,Event中存放事件數據類的定義,從下面的項目圖能看出來。Scene又分爲多個以「Scene」爲後綴的文件夾,每一個文件夾則表明一個場景的邏輯,每個場景的邏輯腳本都存放在其對應的場景文件夾內,而且綁定在對應場景的一個遊戲對象上。Data文件中有存放緩存數據的類的腳本,例如人物數據、怪物數據等將存放在這些類的實例中,以便訪問和更新數據。PhotonClient文件夾則存放Photon客戶端應用程序的腳本。下圖是客戶端原型的項目組織。ui
能夠看到,Event文件夾都是些自定義事件數據類,在上一層中有個EventCollection文件,其中對這些委託、事件進行聲明。再講下Scene,比方說CharacterScene對應Character這個場景,其中目前有兩個腳本,他們繼承於MonoBehavior綁定在場景中的一個名爲Character的物體上,當加載場景後便會啓動這兩個腳本,跳轉到其餘場景便銷燬,頻繁地相互引用腳本是個壞習慣,這樣作即是要求編寫代碼時要始終遵循單一職責原則。此外,這裏還有兩個接口,IEventReceive和IResonseReceive,一個要求OnEvent方法,另外一個要求OnResponse方法,場景腳本依照需求實現這兩個接口。this
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,下面先貼上這兩份代碼。spa
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類,其定義是這樣的:3d
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」。