《攻城Online》快速原型:客戶端設計

  「攻城」客戶端採用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」。

相關文章
相關標籤/搜索