PhotonServer(五)Unity客户端场景中Player的同步

下面的代码内容还是根据上一篇文章的代码做更改和添加内容
首先我们搭建一个简单的场景.把游戏物体Player做成一个预制物体Prefab.,创建一个空物体用来挂载各个请求发送和响应的脚本.


image.png

首先我们在PlayerController上创建一个脚本Player,打开脚本写入控制物体移动的代码.

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

public class Player : MonoBehaviour {
     void Start () {
           //设置本地的Player的颜色设置成绿色
            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移动的代码就完成了,并且运行起来本地的Player为绿色.

同步主角的位置信息并发送给服务器端

我们要实现位置信息的同步就需要获取的主角的位置信息然后时时发送给服务器,再将位置信息由服务器同步到各个客户端去,这样我们的位置信息的同步才算完成。首先需要发送请求就需要OperationCode作为编号发送给服务器,所以现在在服务器里面的Common项目下添加一些OperationCode、EventCode、ParameterCode等等的枚举类型


namespace Common
{
    public  enum EventCode :byte//区分服务器向客户端发送的事件的类型
    {
      NewPlayer,
      SyncPosition
    }
}

namespace Common
{
    public enum OperationCode:byte//区分请求和响应的类型
    {
        Login,
        Register,
        Default,
        SyncPosition,
        SyncPlayer
    }
}

namespace Common
{
    public enum ParameterCode:byte//区分传送数据时候参数的类型
    {
        Username,
        Password,
        Position,
        x,y,z,
        UsernameList,
        PlayerDataList
    }
}
namespace Common
{
    public  enum ReturnCode:short//区分请求返回值,成功或者失败
    {
        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; }
    }
}

然后重新生成下服务器,还是把Common.dll重新添加到我们的Unity里面,这样我们下面所需要的所有枚举类型都添加进去了,下面可以放心使用.下面就要开始做位置的同步了.

首先创建一个请求的脚本SyncPositionRequest,然后继承自Request,实现里面的抽象类,我们把位置信息x,y,z都单独发送个给服务器,把OpCode设置为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;
    //发起位置信息请求
    public override void DefaultRequse()
    {
        //把位置信息x,y,z传递给服务器端
        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位置传递给服务器
    }

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

下面就要发起这个位置同步请求的方法的调用了。我们把这个调用给Player,因为位置信息是需要时时同步的所以这个方法我们也需要时时去调用。这个时候就需要使用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 () {
           //设置本地的Player的颜色设置成绿色
        player. GetComponent<Renderer>().material.color = Color.green;
        SyncPosRequest = GetComponent<SyncPositionRequest>();
        //参数一 方法名,参数二 从等多久后开始执行这个方法  参数三 同步的时间速率。这里一秒同步十次
        InvokeRepeating("SyncPosition", 3, 1 / 10f);//重复调用某个方法                                      
    }
      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()
    {
        //如果玩家的位置当前玩家的位置和上玩家上一个的位置距离大于0.1,就表示玩家移动了,就需要他位置的同步
        if (Vector3.Distance(player.transform.position, lastPosition) > moveOffset)
        {
            lastPosition = player.transform.position;
            SyncPosRequest.pos = player.transform.position;//把这个位置信息传递给SyncPosRequest
            SyncPosRequest.DefaultRequse();//调用位置信息同步的请求
        }
    }
}

这样位置请求就发送给了服务器,下面我们就可以去服务器接收这些位置信息了。在服务器端的Handler文件夹下创建SyncPositionHandler类并继承自BaseHandler这个类并实现里面的抽象方法,然后我们我们到MyGameServer类里面将SyncPositionHandler放入集合里面管理起来,和前面的注册请求登陆请求操作一样。在MyGameServer的InitHandler方法里面添加


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

然后再ClientPeer类里面添加,因为ClientPeer是跟客户端对应的,最后我们还是要把位置信息给各个客户端,所以这里把位置信息保存到ClientPeer里面。

public float x, y, z;

最后在SyncPositionHandler类里面写入客户端请求的操作并接收数据,代码如下

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;//把位置数据传递给Clientpeer保存管理起来

            MyGameServer.log.Info(x + "--" + y+"--"+z);//输出测试
        }
    }
}

这样我们就完成了服务器端的数据接收,最后我就打印输出测试,重新生成服务器端,运行客户端,然后在服务器端移动Player的位置,看看在服务器端的日志输出结果,我们可以看到,我们在客户端一移动,服务器端就会输出位置信息,这样我们的位置就传递给了服务器。
Log.png

客户端服务器保存登陆的用户名并在服务器把所有连接的客户端用集合管理起来

下面我们要在客户端和服务器里面都保存用户登录进去的用户名,客户端里面我们保存到PhotonEngine脚本里面,首先在PhotonEngine里面创建一个变量

    public static string username;//保持当前用户的用户名

然后LoginRequest脚本里面的OnOperationResponse()方法去获取到这个用户名,在这个方法里修改成以下代码

//得到响应
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        ReturnCode returnCode = (ReturnCode)operationResponse.ReturnCode;
        if (returnCode == ReturnCode.Success)//如果登陆成功保持当前用户的用户名
        {
            PhotonEngine.username = Username;
        }
        loginpanel.OnLoginResponse(returnCode);
      
    }

这样我们的用户名就保存到了PhotonEngine里面,后面我们就要在服务器里面保持登陆的用户名了,首先在ClientPeer类里面创建变量

 public string username;

然后在LoginHandler类里面的OnOperationRequest方法里面去获取到用户名

image.png
这样服务器端的用户名也保存了起来,下面我们就要用一个List集合去保存各个客户端的ClientPeer,在MyGameServer类里面先创建一个集合,然后再每次客户端连接的进来后去实例这个客户端的ClientPeer,并保存起来,所以这里在CreatePeer()方法里面写入保存的代码

        //存放所有的Client客户端
        public List<ClientPeer> peerlist = new List<ClientPeer>();//通过这个集合可以访问到所有客户端的Peer,从而向任何一个客户端发送数据
        //当一个客户端请求连接的时候,服务器端就会调用这个方法
        //我们使用peerbase,表示和一个客户端的链接,然后photon就会把这些链接管理起来
        protected override PeerBase CreatePeer(InitRequest initRequest)
        {
            log.Info("一个客户端连接进来了!");
             ClientPeer peer = new ClientPeer(initRequest);//每链接一个客户端过来我们就把这个客户端存储起来添加到List里面
            peerlist.Add(peer);
            return peer;
        }

这样我们每一次有客户端连接进来的时候我们都保存起来,然后再客户端断开连接的时候我们去清空所有保存起来的客户端就行了,在ClientPeer里面有一个断开连接的方法,我们写入

//处理客户端断开连接的后续工作
        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            MyGameServer.Instance.peerlist.Remove(this);//断开连接的时候List里面移除当前的ClientPeer客户端
        }

同步其他客户端的角色(请求连接其他客户端的角色)

首先在客户端创建一个请求的脚本SyncPlayerRequest,继承自Request,并实现其抽象方法,OpCode选择SyncPlayer.

image.png
然后在SyncPlayerRequest脚本里面写入发送请求的代码和接受响应的代码

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>();
    }
    //发起请求
    public override void DefaultRequse()
    {
        PhotonEngine.Peer.OpCustom((byte)OpCode, null, true);//把Player位置传递给服务器
    }
    //处理服务器响应给客户端的数据
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
       string usernameListString=(string)DictTool.GetValue<byte, object>(operationResponse.Parameters,(byte)ParameterCode.UsernameList);

        //通过xml反序列化接收服务器传输过来的List数据
        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脚本里面调用一下这两个方法

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>();
    }
    //发起请求
    public override void DefaultRequse()
    {
        PhotonEngine.Peer.OpCustom((byte)OpCode, null, true);//把Player位置传递给服务器
    }
    //处理服务器响应给客户端的数据
    public override void OnOperationResponse(OperationResponse operationResponse)
    {
        //接收xml格式的字符串
       string usernameListString=(string)DictTool.GetValue<byte, object>(operationResponse.Parameters,(byte)ParameterCode.UsernameList);

        //通过xml反序列化解析传输过来的List数据 接受完后关闭
        using (StringReader reader = new StringReader(usernameListString))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<string>));
           List<string> usernameList= (List<string>)serializer.Deserialize(reader);//表示读取字符串

            player.OnSyncPlayerResponse(usernameList);
        }
    }
}

这样就完成了客户端这边的请求并收到了服务器的响应,下面我们要去服务器端去接受这个请求并且回应数据给客户端,首先在服务器端Handler文件夹下创建一个SyncPlayerHandler类并继承自BaseHandler抽象类,然后实现里面的抽象方法,接着在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);
        }

这样就把所有的请求数据都管理起来。下面我们要去SyncPlayerHandler类里面处理请求了,因为发送的数据不支持List<>类型所以我们需要将List类型通过Xml序列化和反序列化的方式给其进行数据的传输

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;
        }
        //获取其他客户端相对应的用户名请求的处理代码
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            //取得所有已经登陆(在线玩家)的用户名
            List<string> usernameList = new List<string>();
            
            foreach (ClientPeer tempPeer in MyGameServer.Instance.peerlist)
            {
              //string.IsNullOrEmpty(tempPeer.username);//如果用户名为空表示没有登陆
              //如果连接过来的客户端已经登陆了有用户名了并且这个客户端不是当前的客户端
                if (string.IsNullOrEmpty(tempPeer.username )== false&&tempPeer!=peer)
                {
                    //把这些客户端的Usernam添加到集合里面
                    usernameList.Add(tempPeer.username);
                }
            }
         
            //通过xml序列化进行数据传输,传输给客户端
            StringWriter sw = new StringWriter();
            XmlSerializer serlizer = new XmlSerializer(typeof(List<string>));
            serlizer.Serialize(sw,usernameList);
            sw.Close();
            string usernameListString = sw.ToString();

            //给客户端响应
            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);

          

        }
    }
}

下面我们就可以来测试下看看结果,首先打包一到电脑上,然后先运行电脑上的客户端移动下位置,然后我们再运行Unity3D里面的客户端登陆上去,这时候就可以看到我们Unity里面的其他客户端已经同步了进来,这时候我们就完成了一大半了。


image.png

服务器直接给客户端发消息告诉其他客户端有新客户端接收进来

首先我们在服务器的SyncPlayerHandler类的OnOperationRequest()方法里面添加代码,告诉其他客户端有新的客户端进来

image.png

那么服务器端发送事件后我们就要去客户端去接收这个事件了,首先在客户端创建一个脚本BaseEvent用作事件的公共基类.里面的方法和Request脚本里面是差不多的。


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

public abstract class BaseEvent : MonoBehaviour
{

    public EventCode EvCode;
    public abstract void OnEvent(EventData eventData);//接收服务器发送过来的数据与消息

    //当这个组件初始化的时候添加这个Request
    public virtual void Start()
    {
        PhotonEngine.Instance.AddEvent(this);
    }
    //当这个组件被销毁的时候移除这个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;
           }
        }
    //创建一个字典,根据OperationCode去找到所有相对应的Request对象
    private Dictionary<OperationCode, Request> RequestDict = new Dictionary<OperationCode, Request>();

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

    public static string username;//保存当前用户的用户名

    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 () {
        //连接服务器端
        //通过Listender连接服务器端的响应
        //第一个参数 指定一个Licensed(监听器) ,第二个参数使用什么协议
         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();//需要一直调用Service方法,时时处理跟服务器端的连接
    }
    //当游戏关闭的时候(停止运行)调用OnDestroy
    private void OnDestroy()
    {
        //如果peer不等于空并且状态为正在连接
        if (peer != null && peer.PeerState == PeerStateValue.Connected)
        {
            peer.Disconnect();//断开连接
        }
    }

    //
    public void DebugReturn(DebugLevel level, string message)
    {
     
    }
    //如果客户端没有发起请求,但是服务器端向客户端通知一些事情的时候就会通过OnEvent来进行响应 
    public void OnEvent(EventData eventData)
    {
        EventCode code = (EventCode)eventData.Code;
        BaseEvent e=  DictTool.GetValue<EventCode, BaseEvent>(EventDict, code);
        e.OnEvent(eventData);

    }
    //当我们在客户端向服务器端发起请求后,服务器端接受处理这个请求给客户端一个响应就会在这个方法里进行处理
    public void OnOperationResponse(OperationResponse operationResponse)
    {
        OperationCode opCode = (OperationCode)operationResponse.OperationCode;//得到响应的OperationCode
        Request request = null;
        bool temp = RequestDict.TryGetValue(opCode, out request);//是否得到这个响应
       // 如果得到这个响应
        if (temp)
        {
            request.OnOperationResponse(operationResponse);//处理Request里面的响应
        }
        else
        {
            Debug.Log("没有找到对应的响应处理对象");
        }
    }
    //如果连接状态发生改变的时候就会触发这个方法。
    //连接状态有五种,正在连接中(PeerStateValue.Connecting),已经连接上(PeerStateValue.Connected),正在断开连接中( PeerStateValue.Disconnecting),已经断开连接(PeerStateValue.Disconnected),正在进行初始化(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);
    }
}

这样事件的公共基类就完成了,事件的公共基类完成后,我们开始创建服务器端发送过来对应的事件处理了,在客户端中创建一个脚本NewPlayerEvent,让它继承自我们刚刚创建的BaseEvent,并实现里面的抽象方法


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);
    }

}

这样就完成了,这样只要有新客户端加入进来,服务器就会发送消息,然后我们的客户端就会实例化出新的角色。这样就完成了角色的同步,但是它的位置信息是没有同步的。接下来就要吧前面在服务器中接收到的位置信息去同步到各个客户端


image.png

把服务器端接收到的位置信息同步到各个客户端

因为位置的发送我们是需要一直不断的去发送给各个客户端位置信息,所以我们这里放在一个线程中去做,所以在服务器中创建一个文件夹Threads,用来存放所有的线程,然后在Threads文件夹下面创建一个类SyncPositionThread,创建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;//后台运行
            t.Start();//启动线程
        }

        private void UpdataPosition()
        {

         }
        //关闭线程
        public  void Stop()
        {
            t.Abort();//终止线程
        }
    }
}

然后我们去MyGameServer这个类里面去启动线程,所以在MyGameServer里面添加下面的代码,在初始化的时候其启动这个线程,服务器关闭的时候线程也去关闭了.

private SyncPositionThread syncPositinThread = new SyncPositionThread();
    //初始化(当整个服务器启动起来的时候调用这个初始化)
        protected override void Setup()
        {
        syncPositinThread.Run();
        } 
        //server端关闭的时候
        protected override void TearDown()
        {
            syncPositinThread.Stop();
            log.Info("关闭了服务器");
        }

然后我们再去SyncPositionThread类里面去把所有客户端的位置信息发送到各个客户端,在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;//后台运行
            t.Start();//启动线程
        }

        private void UpdataPosition()
        {
            Thread.Sleep(5000);//开始的时候休息5秒开始同步
            while (true)//死循环
            {
                Thread.Sleep(100);//没隔0.1秒同步一次位置信息
                //进行同步
                SendPosition();

            }
        }
        //把所有客户端的位置信息发送到各个客户端
        //封装位置信息,封装到字典里,然后利用Xml序列化去发送
        private void SendPosition()
        {
            //装载PlayerData里面的信息
            List<PlayerData> playerDatraList = 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里面的username
                    playerdata.pos = new Vector3Data() { x = peer.x, y = peer.y, z = peer.z };//设置playerdata里面的Position
                    playerDatraList.Add(playerdata);//把playerdata放入集合
                }
            }
            //进行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装载进字典里面
            //把Xml序列化的信息装在字典里发送给各个客户端
            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());

                }
            }

        }
        //关闭线程
        public  void Stop()
        {
            t.Abort();//终止线程
        }
    }
}

这样我们就把服务器端的所有客户端的位置信息都同步到了各个客户端去了,接下来只需要去客户端去接受这些数据就OK了。首先我们在客户端添加先对应的事件脚本SyncPlayerEvent继承自BaseEvent并实现里面的抽象方法,然后写入接受服务器端发数据

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);

        //进行反序列化接收数据
        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)//遍历所有的数据
        {
            GameObject go = DictTool.GetValue<string, GameObject>(playerDic, pd.Username);//根据传递过来的Username去找到所对应的实例化出来的Player

            //如果查找到了相应的角色,就把相应的位置信息赋值给这个角色的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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容