PhotonServer(五)Unity客戶端場景中Player的同步

下面的代碼內(nèi)容還是根據(jù)上一篇文章的代碼做更改和添加內(nèi)容
首先我們搭建一個簡單的場景.把游戲物體Player做成一個預(yù)制物體Prefab.,創(chuàng)建一個空物體用來掛載各個請求發(fā)送和響應(yīng)的腳本.


image.png

首先我們在PlayerController上創(chuàng)建一個腳本Player,打開腳本寫入控制物體移動的代碼.

using System.Collections.Generic;
using UnityEngine;
using Common;
using Common.Tools;

public class Player : MonoBehaviour {
     void Start () {
           //設(shè)置本地的Player的顏色設(shè)置成綠色
            player. GetComponent<Renderer>().material.color = Color.green;
                   }
    void Update () {
        //只有本地的Player贞瞒,可以控制移動     
            float h = Input.GetAxis("Horizontal");
            float v = Input.GetAxis("Vertical");
           player.transform.Translate(new Vector3(h,0,v)*Time.deltaTime*4);
    }

這樣Player移動的代碼就完成了,并且運(yùn)行起來本地的Player為綠色.

同步主角的位置信息并發(fā)送給服務(wù)器端

我們要實現(xiàn)位置信息的同步就需要獲取的主角的位置信息然后時時發(fā)送給服務(wù)器,再將位置信息由服務(wù)器同步到各個客戶端去趁曼,這樣我們的位置信息的同步才算完成军浆。首先需要發(fā)送請求就需要OperationCode作為編號發(fā)送給服務(wù)器,所以現(xiàn)在在服務(wù)器里面的Common項目下添加一些OperationCode挡闰、EventCode乒融、ParameterCode等等的枚舉類型


namespace Common
{
    public  enum EventCode :byte//區(qū)分服務(wù)器向客戶端發(fā)送的事件的類型
    {
      NewPlayer,
      SyncPosition
    }
}

namespace Common
{
    public enum OperationCode:byte//區(qū)分請求和響應(yīng)的類型
    {
        Login,
        Register,
        Default,
        SyncPosition,
        SyncPlayer
    }
}

namespace Common
{
    public enum ParameterCode:byte//區(qū)分傳送數(shù)據(jù)時候參數(shù)的類型
    {
        Username,
        Password,
        Position,
        x,y,z,
        UsernameList,
        PlayerDataList
    }
}
namespace Common
{
    public  enum ReturnCode:short//區(qū)分請求返回值,成功或者失敗
    {
        Success,
        Failed
    }
}
using System;

namespace Common
{
    [Serializable]
   public  class PlayerData
    {
        public Vector3Data pos { get; set; }
        public  string Username { get; set; }
    }
}

using System;

namespace Common
{
    [Serializable]
   public  class Vector3Data
    {
        public float x { get; set; }
        public float y { get; set; }
        public float z { get; set; }
    }
}

然后重新生成下服務(wù)器摄悯,還是把Common.dll重新添加到我們的Unity里面,這樣我們下面所需要的所有枚舉類型都添加進(jìn)去了赞季,下面可以放心使用.下面就要開始做位置的同步了.

首先創(chuàng)建一個請求的腳本SyncPositionRequest,然后繼承自Request,實現(xiàn)里面的抽象類,我們把位置信息x,y,z都單獨(dú)發(fā)送個給服務(wù)器,把OpCode設(shè)置為SyncPosition

image.png

using System;
using System.Collections.Generic;
using ExitGames.Client.Photon;
using UnityEngine;
using Common;

public class SyncPositionRequest : Request {

    [HideInInspector]
    public Vector3 pos;
    //發(fā)起位置信息請求
    public override void DefaultRequse()
    {
        //把位置信息x,y,z傳遞給服務(wù)器端
        Dictionary<byte, object> data = new Dictionary<byte, object>();
        data.Add((byte)ParameterCode.x,pos.x);
        data.Add((byte)ParameterCode.y, pos.y);
        data.Add((byte)ParameterCode.z, pos.z);

        PhotonEngine.Peer.OpCustom((byte)OpCode, data, true);//把Player位置傳遞給服務(wù)器
    }

    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        throw new NotImplementedException();
    }
}

下面就要發(fā)起這個位置同步請求的方法的調(diào)用了奢驯。我們把這個調(diào)用給Player,因為位置信息是需要時時同步的所以這個方法我們也需要時時去調(diào)用申钩。這個時候就需要使用InvokeRepeating()方法
腳本Player的Start方法里面添加

using System.Collections.Generic;
using UnityEngine;
using Common;
using Common.Tools;

public class Player : MonoBehaviour {

    private SyncPositionRequest SyncPosRequest;
   private Vector3 lastPosition = Vector3.zero;
   private float moveOffset = 0.1f;
      void Start () {
           //設(shè)置本地的Player的顏色設(shè)置成綠色
        player. GetComponent<Renderer>().material.color = Color.green;
        SyncPosRequest = GetComponent<SyncPositionRequest>();
        //參數(shù)一 方法名,參數(shù)二 從等多久后開始執(zhí)行這個方法  參數(shù)三 同步的時間速率瘪阁。這里一秒同步十次
        InvokeRepeating("SyncPosition", 3, 1 / 10f);//重復(fù)調(diào)用某個方法                                      
    }
      void Update () {
        //只有本地的Player撒遣,可以控制移動     
            float h = Input.GetAxis("Horizontal");
            float v = Input.GetAxis("Vertical");
           player.transform.Translate(new Vector3(h,0,v)*Time.deltaTime*4);
       }
  //位置信息時時更新
    void SyncPosition()
    {
        //如果玩家的位置當(dāng)前玩家的位置和上玩家上一個的位置距離大于0.1,就表示玩家移動了管跺,就需要他位置的同步
        if (Vector3.Distance(player.transform.position, lastPosition) > moveOffset)
        {
            lastPosition = player.transform.position;
            SyncPosRequest.pos = player.transform.position;//把這個位置信息傳遞給SyncPosRequest
            SyncPosRequest.DefaultRequse();//調(diào)用位置信息同步的請求
        }
    }
}

這樣位置請求就發(fā)送給了服務(wù)器义黎,下面我們就可以去服務(wù)器接收這些位置信息了。在服務(wù)器端的Handler文件夾下創(chuàng)建SyncPositionHandler類并繼承自BaseHandler這個類并實現(xiàn)里面的抽象方法,然后我們我們到MyGameServer類里面將SyncPositionHandler放入集合里面管理起來豁跑,和前面的注冊請求登陸請求操作一樣廉涕。在MyGameServer的InitHandler方法里面添加


       //用來初始化Handler
        public void InitHandler()
        {
SyncPositionHandler syncPositionHandler = new SyncPositionHandler();
HandlerDict.Add(syncPositionHandler.opCode, syncPositionHandler);
         }

然后再ClientPeer類里面添加,因為ClientPeer是跟客戶端對應(yīng)的,最后我們還是要把位置信息給各個客戶端狐蜕,所以這里把位置信息保存到ClientPeer里面壶愤。

public float x, y, z;

最后在SyncPositionHandler類里面寫入客戶端請求的操作并接收數(shù)據(jù),代碼如下

using Photon.SocketServer;
using Common;
using Common.Tools;

namespace MyGameServer.Handler
{
    class SyncPositionHandler : BaseHandler

    {
        public SyncPositionHandler()
        {
            opCode = OperationCode.SyncPosition;
        }
        //獲取客戶端位置請求的處理的代碼
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            //接收位置并保持起來
            float x = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters,(byte)ParameterCode.x);
            float y = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.y);
            float z = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParameterCode.z);

            peer.x = x;peer.y = y;peer.z = z;//把位置數(shù)據(jù)傳遞給Clientpeer保存管理起來

            MyGameServer.log.Info(x + "--" + y+"--"+z);//輸出測試
        }
    }
}

這樣我們就完成了服務(wù)器端的數(shù)據(jù)接收馏鹤,最后我就打印輸出測試征椒,重新生成服務(wù)器端,運(yùn)行客戶端湃累,然后在服務(wù)器端移動Player的位置勃救,看看在服務(wù)器端的日志輸出結(jié)果,我們可以看到,我們在客戶端一移動治力,服務(wù)器端就會輸出位置信息蒙秒,這樣我們的位置就傳遞給了服務(wù)器。
Log.png

客戶端服務(wù)器保存登陸的用戶名并在服務(wù)器把所有連接的客戶端用集合管理起來

下面我們要在客戶端和服務(wù)器里面都保存用戶登錄進(jìn)去的用戶名宵统,客戶端里面我們保存到PhotonEngine腳本里面,首先在PhotonEngine里面創(chuàng)建一個變量

    public static string username;//保持當(dāng)前用戶的用戶名

然后LoginRequest腳本里面的OnOperationResponse()方法去獲取到這個用戶名晕讲,在這個方法里修改成以下代碼

//得到響應(yīng)
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        ReturnCode returnCode = (ReturnCode)operationResponse.ReturnCode;
        if (returnCode == ReturnCode.Success)//如果登陸成功保持當(dāng)前用戶的用戶名
        {
            PhotonEngine.username = Username;
        }
        loginpanel.OnLoginResponse(returnCode);
      
    }

這樣我們的用戶名就保存到了PhotonEngine里面,后面我們就要在服務(wù)器里面保持登陸的用戶名了马澈,首先在ClientPeer類里面創(chuàng)建變量

 public string username;

然后在LoginHandler類里面的OnOperationRequest方法里面去獲取到用戶名

image.png
這樣服務(wù)器端的用戶名也保存了起來,下面我們就要用一個List集合去保存各個客戶端的ClientPeer瓢省,在MyGameServer類里面先創(chuàng)建一個集合,然后再每次客戶端連接的進(jìn)來后去實例這個客戶端的ClientPeer,并保存起來痊班,所以這里在CreatePeer()方法里面寫入保存的代碼

        //存放所有的Client客戶端
        public List<ClientPeer> peerlist = new List<ClientPeer>();//通過這個集合可以訪問到所有客戶端的Peer勤婚,從而向任何一個客戶端發(fā)送數(shù)據(jù)
        //當(dāng)一個客戶端請求連接的時候,服務(wù)器端就會調(diào)用這個方法
        //我們使用peerbase,表示和一個客戶端的鏈接,然后photon就會把這些鏈接管理起來
        protected override PeerBase CreatePeer(InitRequest initRequest)
        {
            log.Info("一個客戶端連接進(jìn)來了涤伐!");
             ClientPeer peer = new ClientPeer(initRequest);//每鏈接一個客戶端過來我們就把這個客戶端存儲起來添加到List里面
            peerlist.Add(peer);
            return peer;
        }

這樣我們每一次有客戶端連接進(jìn)來的時候我們都保存起來,然后再客戶端斷開連接的時候我們?nèi)デ蹇账斜4嫫饋淼目蛻舳司托辛寺ǎ贑lientPeer里面有一個斷開連接的方法,我們寫入

//處理客戶端斷開連接的后續(xù)工作
        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            MyGameServer.Instance.peerlist.Remove(this);//斷開連接的時候List里面移除當(dāng)前的ClientPeer客戶端
        }

同步其他客戶端的角色(請求連接其他客戶端的角色)

首先在客戶端創(chuàng)建一個請求的腳本SyncPlayerRequest,繼承自Request凝果,并實現(xiàn)其抽象方法,OpCode選擇SyncPlayer.

image.png
然后在SyncPlayerRequest腳本里面寫入發(fā)送請求的代碼和接受響應(yīng)的代碼

using System.Collections.Generic;
using ExitGames.Client.Photon;
using Common;
using Common.Tools;
using System.IO;
using System.Xml.Serialization;

public class SyncPlayerRequest : Request
{

    private Player player;
    public override void Start()
    {
        base.Start();
        player = GetComponent<Player>();
    }
    //發(fā)起請求
    public override void DefaultRequse()
    {
        PhotonEngine.Peer.OpCustom((byte)OpCode, null, true);//把Player位置傳遞給服務(wù)器
    }
    //處理服務(wù)器響應(yīng)給客戶端的數(shù)據(jù)
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
       string usernameListString=(string)DictTool.GetValue<byte, object>(operationResponse.Parameters,(byte)ParameterCode.UsernameList);

        //通過xml反序列化接收服務(wù)器傳輸過來的List數(shù)據(jù)
        using (StringReader reader = new StringReader(usernameListString))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<string>));
           List<string> usernameList= (List<string>)serializer.Deserialize(reader);//表示讀取字符串

            player.OnSyncPlayerResponse(usernameList);
        }
    }
}

然后去Player腳本里面調(diào)用一下這兩個方法

using System.Collections.Generic;
using ExitGames.Client.Photon;
using Common;
using Common.Tools;
using System.IO;
using System.Xml.Serialization;

public class SyncPlayerRequest : Request
{
    private Player player;
    public override void Start()
    {
        base.Start();
        player = GetComponent<Player>();
    }
    //發(fā)起請求
    public override void DefaultRequse()
    {
        PhotonEngine.Peer.OpCustom((byte)OpCode, null, true);//把Player位置傳遞給服務(wù)器
    }
    //處理服務(wù)器響應(yīng)給客戶端的數(shù)據(jù)
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        //接收xml格式的字符串
       string usernameListString=(string)DictTool.GetValue<byte, object>(operationResponse.Parameters,(byte)ParameterCode.UsernameList);

        //通過xml反序列化解析傳輸過來的List數(shù)據(jù) 接受完后關(guān)閉
        using (StringReader reader = new StringReader(usernameListString))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<string>));
           List<string> usernameList= (List<string>)serializer.Deserialize(reader);//表示讀取字符串

            player.OnSyncPlayerResponse(usernameList);
        }
    }
}

這樣就完成了客戶端這邊的請求并收到了服務(wù)器的響應(yīng)祝迂,下面我們要去服務(wù)器端去接受這個請求并且回應(yīng)數(shù)據(jù)給客戶端,首先在服務(wù)器端Handler文件夾下創(chuàng)建一個SyncPlayerHandler類并繼承自BaseHandler抽象類,然后實現(xiàn)里面的抽象方法器净,接著在MyGaneServer類里面把SyncPlayerHandler交給集合管理起來

 //用來初始化Handler
        public void InitHandler()
        {
            LoginHandler loginHandler = new LoginHandler();
            HandlerDict.Add(loginHandler.opCode,loginHandler);
            DefaultHandler defaultHandler = new DefaultHandler();
            HandlerDict.Add(defaultHandler.opCode,defaultHandler);

            RegisterHandler registerHandler = new RegisterHandler();
            HandlerDict.Add(registerHandler.opCode, registerHandler);

            SyncPositionHandler syncPositionHandler = new SyncPositionHandler();
            HandlerDict.Add(syncPositionHandler.opCode, syncPositionHandler);

            SyncPlayerHandler syncPlayerHandler = new SyncPlayerHandler();
            HandlerDict.Add(syncPlayerHandler.opCode,syncPlayerHandler);
        }

這樣就把所有的請求數(shù)據(jù)都管理起來型雳。下面我們要去SyncPlayerHandler類里面處理請求了,因為發(fā)送的數(shù)據(jù)不支持List<>類型所以我們需要將List類型通過Xml序列化和反序列化的方式給其進(jìn)行數(shù)據(jù)的傳輸

using System.Collections.Generic;
using Photon.SocketServer;
using Common;
using System.Xml.Serialization;
using System.IO;

namespace MyGameServer.Handler
{
    class SyncPlayerHandler : BaseHandler
    {
        public SyncPlayerHandler()
        {
            opCode = OperationCode.SyncPlayer;
        }
        //獲取其他客戶端相對應(yīng)的用戶名請求的處理代碼
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            //取得所有已經(jīng)登陸(在線玩家)的用戶名
            List<string> usernameList = new List<string>();
            
            foreach (ClientPeer tempPeer in MyGameServer.Instance.peerlist)
            {
              //string.IsNullOrEmpty(tempPeer.username);//如果用戶名為空表示沒有登陸
              //如果連接過來的客戶端已經(jīng)登陸了有用戶名了并且這個客戶端不是當(dāng)前的客戶端
                if (string.IsNullOrEmpty(tempPeer.username )== false&&tempPeer!=peer)
                {
                    //把這些客戶端的Usernam添加到集合里面
                    usernameList.Add(tempPeer.username);
                }
            }
         
            //通過xml序列化進(jìn)行數(shù)據(jù)傳輸,傳輸給客戶端
            StringWriter sw = new StringWriter();
            XmlSerializer serlizer = new XmlSerializer(typeof(List<string>));
            serlizer.Serialize(sw,usernameList);
            sw.Close();
            string usernameListString = sw.ToString();

            //給客戶端響應(yīng)
            Dictionary<byte, object> data = new Dictionary<byte, object>();
            data.Add((byte)ParameterCode.UsernameList, usernameListString);
            OperationResponse response = new OperationResponse(operationRequest.OperationCode);
            response.Parameters = data;
            peer.SendOperationResponse(response, sendParameters);

          

        }
    }
}

下面我們就可以來測試下看看結(jié)果掌动,首先打包一到電腦上四啰,然后先運(yùn)行電腦上的客戶端移動下位置,然后我們再運(yùn)行Unity3D里面的客戶端登陸上去粗恢,這時候就可以看到我們Unity里面的其他客戶端已經(jīng)同步了進(jìn)來柑晒,這時候我們就完成了一大半了。


image.png

服務(wù)器直接給客戶端發(fā)消息告訴其他客戶端有新客戶端接收進(jìn)來

首先我們在服務(wù)器的SyncPlayerHandler類的OnOperationRequest()方法里面添加代碼眷射,告訴其他客戶端有新的客戶端進(jìn)來

image.png

那么服務(wù)器端發(fā)送事件后我們就要去客戶端去接收這個事件了匙赞,首先在客戶端創(chuàng)建一個腳本BaseEvent用作事件的公共基類.里面的方法和Request腳本里面是差不多的佛掖。


using Common;
using ExitGames.Client.Photon;
using UnityEngine;

public abstract class BaseEvent : MonoBehaviour
{

    public EventCode EvCode;
    public abstract void OnEvent(EventData eventData);//接收服務(wù)器發(fā)送過來的數(shù)據(jù)與消息

    //當(dāng)這個組件初始化的時候添加這個Request
    public virtual void Start()
    {
        PhotonEngine.Instance.AddEvent(this);
    }
    //當(dāng)這個組件被銷毀的時候移除這個Request
    public void OnDestroy()
    {
        PhotonEngine.Instance.RemoveEvent(this);
    }
}

然后再PhotonEngine里面修改里面的代碼和添加代碼,如下

using System.Collections.Generic;
using UnityEngine;
using ExitGames.Client.Photon;
using Common;
using Common.Tools;

public class PhotonEngine : MonoBehaviour,IPhotonPeerListener {

    public  static PhotonEngine Instance;
    public static PhotonPeer Peer//讓外界可以訪問我們的PhotonPeer
    {
        get
          {
            return peer;
           }
        }
    //創(chuàng)建一個字典涌庭,根據(jù)OperationCode去找到所有相對應(yīng)的Request對象
    private Dictionary<OperationCode, Request> RequestDict = new Dictionary<OperationCode, Request>();

    private Dictionary<EventCode, BaseEvent> EventDict = new Dictionary<EventCode, BaseEvent>();

    public static string username;//保存當(dāng)前用戶的用戶名

    private  static PhotonPeer peer;
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(this.gameObject);
        }else if (Instance != this)
        {
            Destroy(this.gameObject);return;
        }
    }

    // Use this for initialization
    void Start () {
        //連接服務(wù)器端
        //通過Listender連接服務(wù)器端的響應(yīng)
        //第一個參數(shù) 指定一個Licensed(監(jiān)聽器) ,第二個參數(shù)使用什么協(xié)議
         peer = new PhotonPeer(this,ConnectionProtocol.Udp);
        //連接 UDP的 Ip地址:端口號芥被,Application的名字
        peer.Connect("127.0.0.1:5055", "MyGame1");
        
    }
    
    // Update is called once per frame
    void Update () {
      
            peer.Service();//需要一直調(diào)用Service方法,時時處理跟服務(wù)器端的連接
    }
    //當(dāng)游戲關(guān)閉的時候(停止運(yùn)行)調(diào)用OnDestroy
    private void OnDestroy()
    {
        //如果peer不等于空并且狀態(tài)為正在連接
        if (peer != null && peer.PeerState == PeerStateValue.Connected)
        {
            peer.Disconnect();//斷開連接
        }
    }

    //
    public void DebugReturn(DebugLevel level, string message)
    {
     
    }
    //如果客戶端沒有發(fā)起請求,但是服務(wù)器端向客戶端通知一些事情的時候就會通過OnEvent來進(jìn)行響應(yīng) 
    public void OnEvent(EventData eventData)
    {
        EventCode code = (EventCode)eventData.Code;
        BaseEvent e=  DictTool.GetValue<EventCode, BaseEvent>(EventDict, code);
        e.OnEvent(eventData);

    }
    //當(dāng)我們在客戶端向服務(wù)器端發(fā)起請求后坐榆,服務(wù)器端接受處理這個請求給客戶端一個響應(yīng)就會在這個方法里進(jìn)行處理
    public void OnOperationResponse(OperationResponse operationResponse)
    {
        OperationCode opCode = (OperationCode)operationResponse.OperationCode;//得到響應(yīng)的OperationCode
        Request request = null;
        bool temp = RequestDict.TryGetValue(opCode, out request);//是否得到這個響應(yīng)
       // 如果得到這個響應(yīng)
        if (temp)
        {
            request.OnOperationResponse(operationResponse);//處理Request里面的響應(yīng)
        }
        else
        {
            Debug.Log("沒有找到對應(yīng)的響應(yīng)處理對象");
        }
    }
    //如果連接狀態(tài)發(fā)生改變的時候就會觸發(fā)這個方法拴魄。
    //連接狀態(tài)有五種,正在連接中(PeerStateValue.Connecting)席镀,已經(jīng)連接上(PeerStateValue.Connected)匹中,正在斷開連接中( PeerStateValue.Disconnecting),已經(jīng)斷開連接(PeerStateValue.Disconnected)豪诲,正在進(jìn)行初始化(PeerStateValue.InitializingApplication)
    public void OnStatusChanged(StatusCode statusCode) { 
    
    }

    //添加Requst
    public void AddRequst(Request requst)
    {
    RequestDict.Add(requst.OpCode, requst);

    }
    //移除Requst
    public void RemoveRequst(Request request)
    {
        RequestDict.Remove(request.OpCode);
    }
    //添加Event事件
    public void AddEvent(BaseEvent Event)
    {
        EventDict.Add(Event.EvCode, Event);
    }
    //移除Event事件
    public void RemoveEvent(BaseEvent Event)
    {
        EventDict.Remove(Event.EvCode);
    }
}

這樣事件的公共基類就完成了顶捷,事件的公共基類完成后,我們開始創(chuàng)建服務(wù)器端發(fā)送過來對應(yīng)的事件處理了屎篱,在客戶端中創(chuàng)建一個腳本NewPlayerEvent,讓它繼承自我們剛剛創(chuàng)建的BaseEvent,并實現(xiàn)里面的抽象方法


using ExitGames.Client.Photon;
using Common.Tools;
using Common;


public class NewPlayerEvent : BaseEvent {

    private Player player;
    public override void Start()
    {
        base.Start();
        player = GetComponent<Player>();
    }
    public override void OnEvent(EventData eventData)
    {
        string username = (string)DictTool.GetValue<byte, object>(eventData.Parameters,(byte) ParameterCode.Username);
        player.OnNewPlayerEvent(username);
    }

}

這樣就完成了服赎,這樣只要有新客戶端加入進(jìn)來,服務(wù)器就會發(fā)送消息交播,然后我們的客戶端就會實例化出新的角色重虑。這樣就完成了角色的同步,但是它的位置信息是沒有同步的堪侯。接下來就要吧前面在服務(wù)器中接收到的位置信息去同步到各個客戶端


image.png

把服務(wù)器端接收到的位置信息同步到各個客戶端

因為位置的發(fā)送我們是需要一直不斷的去發(fā)送給各個客戶端位置信息嚎尤,所以我們這里放在一個線程中去做,所以在服務(wù)器中創(chuàng)建一個文件夾Threads伍宦,用來存放所有的線程,然后在Threads文件夾下面創(chuàng)建一個類SyncPositionThread,創(chuàng)建OK后乏梁,在SyncPositionThread寫入下面的代碼

using Common;
using System.Xml.Serialization;
using System.IO;
using Photon.SocketServer;

namespace MyGameServer.Threads
{
    class SyncPositionThread
    {
        private Thread t;

        //啟動線程的方法
        public void Run()
        {
            t = new Thread(UpdataPosition);//UpdataPosition表示線程要啟動的方法
            t.IsBackground = true;//后臺運(yùn)行
            t.Start();//啟動線程
        }

        private void UpdataPosition()
        {

         }
        //關(guān)閉線程
        public  void Stop()
        {
            t.Abort();//終止線程
        }
    }
}

然后我們?nèi)yGameServer這個類里面去啟動線程,所以在MyGameServer里面添加下面的代碼,在初始化的時候其啟動這個線程,服務(wù)器關(guān)閉的時候線程也去關(guān)閉了.

private SyncPositionThread syncPositinThread = new SyncPositionThread();
    //初始化(當(dāng)整個服務(wù)器啟動起來的時候調(diào)用這個初始化)
        protected override void Setup()
        {
        syncPositinThread.Run();
        } 
        //server端關(guān)閉的時候
        protected override void TearDown()
        {
            syncPositinThread.Stop();
            log.Info("關(guān)閉了服務(wù)器");
        }

然后我們再去SyncPositionThread類里面去把所有客戶端的位置信息發(fā)送到各個客戶端次洼,在SyncPositionThread添加以下代碼

using System.Collections.Generic;
using System.Threading;
using Common;
using System.Xml.Serialization;
using System.IO;
using Photon.SocketServer;

namespace MyGameServer.Threads
{
    class SyncPositionThread
    {
        private Thread t;

        //啟動線程的方法
        public void Run()
        {
            t = new Thread(UpdataPosition);//UpdataPosition表示線程要啟動的方法
            t.IsBackground = true;//后臺運(yùn)行
            t.Start();//啟動線程
        }

        private void UpdataPosition()
        {
            Thread.Sleep(5000);//開始的時候休息5秒開始同步
            while (true)//死循環(huán)
            {
                Thread.Sleep(100);//沒隔0.1秒同步一次位置信息
                //進(jìn)行同步
                SendPosition();

            }
        }
        //把所有客戶端的位置信息發(fā)送到各個客戶端
        //封裝位置信息,封裝到字典里,然后利用Xml序列化去發(fā)送
        private void SendPosition()
        {
            //裝載PlayerData里面的信息
            List<PlayerData> playerDatraList = new List<PlayerData>();
            foreach (ClientPeer peer in MyGameServer.Instance.peerlist)//遍歷所有客戶段
            {
                if (string.IsNullOrEmpty(peer.username)==false)//取得當(dāng)前已經(jīng)登陸的客戶端
                {
                    PlayerData playerdata = new PlayerData();
                    playerdata.Username = peer.username;//設(shè)置playerdata里面的username
                    playerdata.pos = new Vector3Data() { x = peer.x, y = peer.y, z = peer.z };//設(shè)置playerdata里面的Position
                    playerDatraList.Add(playerdata);//把playerdata放入集合
                }
            }
            //進(jìn)行Xml序列化成String
            StringWriter sw = new StringWriter();
            XmlSerializer serializer = new XmlSerializer(typeof(List<PlayerData>));
            serializer.Serialize(sw,playerDatraList);
            sw.Close();
            string playerDataListString = sw.ToString();


            Dictionary<byte, object> data = new Dictionary<byte, object>();
            data.Add((byte)ParameterCode.PlayerDataList,playerDataListString);//把所有的playerDataListString裝載進(jìn)字典里面
            //把Xml序列化的信息裝在字典里發(fā)送給各個客戶端
            foreach (ClientPeer peer in MyGameServer.Instance.peerlist)
            {
                if (string.IsNullOrEmpty(peer.username) == false)
                {
                    EventData ed = new EventData((byte)EventCode.SyncPosition);
                    ed.Parameters = data;
                    peer.SendEvent(ed,new SendParameters());

                }
            }

        }
        //關(guān)閉線程
        public  void Stop()
        {
            t.Abort();//終止線程
        }
    }
}

這樣我們就把服務(wù)器端的所有客戶端的位置信息都同步到了各個客戶端去了遇骑,接下來只需要去客戶端去接受這些數(shù)據(jù)就OK了卖毁。首先我們在客戶端添加先對應(yīng)的事件腳本SyncPlayerEvent繼承自BaseEvent并實現(xiàn)里面的抽象方法,然后寫入接受服務(wù)器端發(fā)數(shù)據(jù)

using System.Collections.Generic;
using ExitGames.Client.Photon;
using Common.Tools;
using Common;
using System.IO;
using System.Xml.Serialization;

public class SyncPlayerEvent : BaseEvent
{
    private Player player;
    public override void Start()
    {
        base.Start();
        player = GetComponent<Player>();
    }

    public override void OnEvent(EventData eventData)
    {
        string playerDataListString=(string)DictTool.GetValue<byte, object>(eventData.Parameters, (byte)ParameterCode.PlayerDataList);

        //進(jìn)行反序列化接收數(shù)據(jù)
        using (StringReader reader = new StringReader(playerDataListString))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<PlayerData>));
            List<PlayerData> playerDataList=(List<PlayerData>)serializer.Deserialize(reader);

            player.OnSyncPositionEvent(playerDataList);
        } 
    }
}

然后在Player腳本里面添加OnSyncPositionEvent()方法。落萎,在這個方法里面寫入代碼

  public void OnSyncPositionEvent(List<PlayerData> playerDataList)
    {
        foreach (PlayerData pd in playerDataList)//遍歷所有的數(shù)據(jù)
        {
            GameObject go = DictTool.GetValue<string, GameObject>(playerDic, pd.Username);//根據(jù)傳遞過來的Username去找到所對應(yīng)的實例化出來的Player

            //如果查找到了相應(yīng)的角色亥啦,就把相應(yīng)的位置信息賦值給這個角色的position
            if (go != null)
            {
                go.transform.position = new Vector3() { x = pd.pos.x, y = pd.pos.y, z = pd.pos.z };
            }
        }
    }
Paste_Image.png

這樣我們整個位置的同步也完成了,下面我們就可以來測試下了,這樣就完成了位置的同步练链。


39秒.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翔脱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子媒鼓,更是在濱河造成了極大的恐慌届吁,老刑警劉巖错妖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疚沐,居然都是意外死亡暂氯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門亮蛔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痴施,“玉大人,你說我怎么就攤上這事究流×榔剩” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵梯嗽,是天一觀的道長齿尽。 經(jīng)常有香客問我,道長灯节,這世上最難降的妖魔是什么循头? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮炎疆,結(jié)果婚禮上卡骂,老公的妹妹穿的比我還像新娘。我一直安慰自己形入,他們只是感情好全跨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著亿遂,像睡著了一般浓若。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛇数,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天挪钓,我揣著相機(jī)與錄音,去河邊找鬼耳舅。 笑死碌上,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浦徊。 我是一名探鬼主播馏予,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盔性!你這毒婦竟也來了霞丧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤纯出,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體王滤,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骚露,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖饭豹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情务漩,我是刑警寧澤拄衰,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饵骨,受9級特大地震影響翘悉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜居触,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一妖混、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轮洋,春花似錦制市、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汉柒,卻和暖如春误褪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背竭翠。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工振坚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斋扰。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像啃洋,于是被迫代替她去往敵國和親传货。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容