Photon Server與Unity3D的交互分為3篇博文實現
(3)Photon Server與Unity3D客戶端的交互
1.客戶端向服務器端發送請求
PhotonEngine.Peer.OpCustom(byte customOpCode, Dictionary<byte, object> customOpParameters, bool sendReliable);
在客戶端任何位置都能調用此方法。
customOpCode,請求類型,不推薦直接傳數字,用枚舉類型enum。
customOpParameters,傳遞的數據是Dictionary<byte, object>類型,object類是所有類的父類。
sendReliable,是否使用穩定的連接。
示例:
登錄的時候客戶端向服務器端發送用戶名跟密碼。本示例創建一個Common類庫存放客戶端與服務器端都需要的一些枚舉類、工具類。每次Common修改都要重新生成dll文件重新添加到客戶端跟服務器端。
namespace Common { //客戶端請求類型。枚舉類型默認時是int,-2147483648~2147483647,byte 是 0~255之間的整數 public enum OperationCode:byte { Login, Register, Default, SyncPosition, SyncPlayer } }
namespace Common { //數據Dictionary<byte, object> customOpParameters里object數據的類型 public enum ParameterCode:byte { Username, Password, Position, X,Y,Z, UsernameList, PlayerDataList } }
//客戶端向服務器端發送用戶名跟密碼請求登錄
Dictionary<byte, object> opParameters = new Dictionary<byte, object>(); opParameters.Add((byte)ParameterCode.Username, usename); opParameters.Add((byte)ParameterCode.Password, password);
PhotonEngine.Peer.OpCustom((byte)OperationCode.Login, opParameters, true);
2.服務器端接收客戶端的請求
protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
在服務器端ClientPeer類里的OnOperationRequest里處理客戶端的請求。每一個客戶端連接進來都會創建一個ClientPeer,並且Photon Server為我們管理好它。
operationRequest.OperationCode可以得到客戶端傳過來的byte customOpCode。
operationRequest.Parameters可以得到客戶端傳過來的Dictionary<byte, object> customOpParameters。
opParameters.TryGetValue((byte)ParameterCode.Username, out object username)可以得到operationRequest.Parameters里面對應的數據。
sendParameters是發送請求的一些相關屬性。
SendOperationResponse(OperationResponse operationResponse, SendParameters sendParameters);
響應客戶端需要調用SendOperationResponse方法,只能在ClientPeer類里的OnOperationRequest方法里調用。
OperationResponse operationResponse = new OperationResponse(operationRequest.OperationCode);
operationResponse.SetParameters(newCustomOpParameters)可以設置Parameters。
operationResponse.ReturnCode可以設置一個short返回值對應一種請求結果,用枚舉類型enum。
示例:
namespace Common { public enum ReturnCode:short { Success, Failed } }
using Common; namespace MinecraftServer { //每一個客戶端連接進來都會創建一個ClientPeer public class ClientPeer : Photon.SocketServer.ClientPeer { //處理客戶端的請求 protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters) { //不建議直接使用switch,推薦把每一種請求的處理封裝成類 switch (operationRequest.OperationCode) { case (byte)OperationCode.Login: //接收客戶端請求 Dictionary<byte, object> opParameters1= operationRequest.Parameters; opParameters1.TryGetValue((byte)ParameterCode.Username, out object username); opParameters1.TryGetValue((byte)ParameterCode.Password, out object password); MyGameServer.log.Info("得到的參數是" + username.ToString() + "和" + password.ToString()); //此處省略判斷用戶名密碼是否正確的操作 //響應客戶端用SendOperationResponse,只能在OnOperationRequest方法里調用 OperationResponse operationResponse = new OperationResponse(operationRequest.OperationCode); operationResponse.ReturnCode = (short)ReturnCode.Success;
//Dictionary<byte, object> opParameters2 = new Dictionary<byte, object>();
//opParameters2.Add(,);
//opParameters2.Add(,);
//operationResponse.SetParameters(opParameters2); SendOperationResponse(operationResponse, sendParameters); break; case (byte)OperationCode.Register: break; default: break; } } } }
3.客戶端接收服務器端的響應
public void OnOperationResponse(OperationResponse operationResponse)
在客戶端的PhotonEngine類里的OnOperationResponse類里接收服務器端的響應。
operationResponse.OperationCode可以得到operationResponse構造函數里的operationRequest.OperationCode。
operationResponse.Parameters可以得到服務器端operationResponse.SetParameters(opParameters2)的opParameters2。
opParameters.TryGetValue((byte)ParameterCode.Username, out object username)可以得到operationResponse.Parameters里面對應的數據。
operationResponse.ReturnCode可以得到服務器端operationResponse.ReturnCode=(short)ReturnCode.Success的(short)ReturnCode.Success。
4.服務器端向客戶端發送事件
peer.SendEvent向客戶端peer發送事件,可以在任何地方調用。
EventData eventData = new EventData((byte)EventCode.SyncPosition); Dictionary<byte, object> opParameters = new Dictionary<byte, object>(); opParameters.Add(,); opParameters.Add(,); eventData.Parameters = opParameters; peer.SendEvent(eventData, new SendParameters());
5.客戶端接收服務器端發送的事件
public void OnEvent(EventData eventData)
在PhotonEngine類的OnEvent方法接收服務器端發送的事件。
eventData.Code可以得到eventData構造函數里的(byte)EventCode.SyncPosition。
eventData.Parameters可以得到服務器端eventData.Parameters。
opParameters.TryGetValue((byte)ParameterCode.Username, out object username)可以得到operationResponse.Parameters里面對應的數據。
6.數據的序列化跟反序列化
序列化
一般序列化List<T> list。如果有多種數據,新建一個類例如Player,將人物的各種屬性字段賦值后存入list。然后序列化list
List<PlayerData> playDataList = new List<PlayerData>(); StringWriter sw = new StringWriter(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<PlayerData>)); xmlSerializer.Serialize(sw, playDataList); sw.Close(); string playDataListString = sw.ToString();
反序列化
using (StringReader sr = new StringReader(playerDataListString)) { XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<PlayerData>)); List<PlayerData> playerDataList = (List<PlayerData>)xmlSerializer.Deserialize(sr); }
7.將請求、事件封裝類
(1)把客戶端的請求封裝成類,里面包括請求的OperationCode、發送請求和接收響應的方法。
public abstract class ARequest:MonoBehaviour{ public OperationCode OpCode;//設置public自動識別在Inspector里面,並且會識別枚舉類型選擇 public abstract void DefaultRequest(); public abstract void OnOperationResponse(OperationResponse operationResponse); public virtual void Start() { PhotonEngine.Instance.AddRequest(this); } public void OnDestroy() { PhotonEngine.Instance.RemoveRequest(this); } } public class LoginRequest : ARequest { [HideInInspector] public string usename; [HideInInspector] public string password; private LoginPanel loginPanel; public override void Start() { base.Start(); loginPanel=GetComponent<LoginPanel>(); } public override void DefaultRequest() { Dictionary<byte, object> data = new Dictionary<byte, object>(); data.Add((byte)ParameterCode.Username, usename); data.Add((byte)ParameterCode.Password, password); PhotonEngine.Peer.OpCustom((byte)OpCode,data, true); } public override void OnOperationResponse(OperationResponse operationResponse) { ReturnCode returnCode= (ReturnCode)operationResponse.ReturnCode; loginPanel.OnLoginResponse(returnCode); } }
(2)把服務器端處理客戶端請求封裝成類,里面包括請求的OperationCode和處理請求的方法。
public abstract class AHandler { public OperationCode opCode; public abstract void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer); } class LoginHandler : AHandler { public LoginHandler() { opCode = OperationCode.Login; } public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer) { operationRequest.Parameters.TryGetValue((byte)ParameterCode.Username, out object username); operationRequest.Parameters.TryGetValue((byte)ParameterCode.Password, out object password); UserManager userManager = new UserManager(); bool isSuccess = userManager.Vertify(username.ToString(), password.ToString()); OperationResponse operationResponse = new OperationResponse(operationRequest.OperationCode); if (isSuccess) { operationResponse.ReturnCode = (short)ReturnCode.Success; peer.username = username.ToString(); } else { operationResponse.ReturnCode = (short)ReturnCode.Failed; } peer.SendOperationResponse(operationResponse, sendParameters); } }
(3)服務器端發送事件有2種情況,一種是處理客戶端請求時發送,二是需要不斷同步的數據由線程來發送。
把客戶端接收服務器事件封裝成類,里面包括事件的EventCode和處理事件的方法。
public abstract class AEvent : MonoBehaviour { public EventCode eventCode;//設置public自動識別在Inspector里面,並且會識別枚舉類型選擇 public abstract void OnEvent(EventData eventData); public virtual void Start() { PhotonEngine.Instance.AddEvent(this); } public void OnDestroy() { PhotonEngine.Instance.RemoveEvent(this); } } public class NewPlayerEvent :AEvent{ private SyncController player; public override void Start() { base.Start(); player = GetComponent<SyncController>(); } public override void OnEvent(EventData eventData) { object username; eventData.Parameters.TryGetValue((byte)ParameterCode.Username, out username); player.OnNewPlayerEvent(username.ToString()); } }
(4)對應的客戶端的PhotonEngine、服務器端ClientPeer修改
public class PhotonEngine : MonoBehaviour, IPhotonPeerListener { //存放所有可能出現的請求和事件 private Dictionary<OperationCode, ARequest> RequestDict = new Dictionary<OperationCode, ARequest>(); private Dictionary<EventCode, AEvent> EventDict = new Dictionary<EventCode, AEvent>(); //服務器端向客戶端發送請求時調用 public void OnEvent(EventData eventData) { AEvent tempEvent; EventDict.TryGetValue((EventCode)eventData.Code, out tempEvent); tempEvent.OnEvent(eventData); } //客戶端向服務器端發送請求后,服務器端響應客戶端時調用 public void OnOperationResponse(OperationResponse operationResponse) { ARequest tempRequest; bool temp = RequestDict.TryGetValue((OperationCode)operationResponse.OperationCode, out tempRequest); if (temp) { request.OnOperationResponse(operationResponse); } else { Debug.Log("沒找到對應的響應對象"); } } public void AddRequest(ARequest request) { RequestDict.Add(request.OpCode, request); } public void RemoveRequest(ARequest request) { RequestDict.Remove(request.OpCode); } public void AddEvent(AEvent tempEvent) { EventDict.Add(tempEvent.eventCode, tempEvent); } public void RemoveEvent(AEvent tempEvent) { EventDict.Remove(tempEvent.eventCode); } }
public class ClientPeer : Photon.SocketServer.ClientPeer { public float x, y, z; public string username; public ClientPeer(InitRequest initRequest) : base(initRequest) { MyGameServer.Instance.peerList.Add(this); } //處理客戶端斷開連接的后續工作 protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail) { MyGameServer.Instance.peerList.Remove(this); } //處理客戶端的請求 protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters) { MyGameServer.Instance.HandleDict.TryGetValue((OperationCode)operationRequest.OperationCode, out AHandler handler); if (handler != null) { handler.OnOperationRequest(operationRequest, sendParameters, this); } else { AHandler defaultHandler = DictTool.GetValue<OperationCode, AHandler>(MyGameServer.Instance.HandleDict, OperationCode.Default); defaultHandler.OnOperationRequest(operationRequest, sendParameters, this); } } }
8.線程
服務器持續向服務器端發送事件時需要用到線程,下面是線程簡單的應用。
public class SyncPositionThread { private Thread t; public void Run() { t = new Thread(UpdatePosition); t.IsBackground = true; t.Start(); } public void Stop() { t.Abort(); } private void UpdatePosition() { Thread.Sleep(5000); while (true) { Thread.Sleep(50); //執行上傳位置 SendPosition(); } } private void SendPosition() { //如果沒有客戶端連接上則不用傳數據 if (MyGameServer.Instance.peerList.Count == 0) return; //把所有客戶端的位置裝箱到playerDataList List<PlayerData> playDataList = new List<PlayerData>(); foreach (ClientPeer peer in MyGameServer.Instance.peerList) { if (string.IsNullOrEmpty(peer.username) == false) { PlayerData playerData = new PlayerData(); playerData.Username = peer.username; playerData.Position = new Vector3Data() { X = peer.x, Y = peer.y, Z = peer.z }; playDataList.Add(playerData); } } //如果有客戶端登錄但是還沒連接上,也不用傳數據 if (playDataList.Count == 0) return; //playDataList序列化為playDataListString然后放在data里面 StringWriter sw = new StringWriter(); XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<PlayerData>)); xmlSerializer.Serialize(sw, playDataList); sw.Close(); string playDataListString = sw.ToString(); Dictionary<byte, object> data = new Dictionary<byte, object>(); data.Add((byte)ParameterCode.PlayerDataList, playDataListString); //playDataListString發送到各個客戶端 foreach (ClientPeer peer in MyGameServer.Instance.peerList) { if (string.IsNullOrEmpty(peer.username) == false) { EventData eventData = new EventData((byte)EventCode.SyncPosition); eventData.Parameters = data; peer.SendEvent(eventData, new SendParameters()); } } } }