上一篇文章我们实现了角色创建同步,下面继续来实现玩家的位移同步
在实现之前,我们先整理下位移同步的思路
一、客户端每隔指定时间向服务器端发送位置信息
二、服务器接收并记录该客户端的位置信息
三、服务器每隔指定时间广播给所有在线客户端发送所有客户端的位置信息
四、客户端接收服务器发送的所有客户端位置信息,并修改他们位置
思路很简答,那下面我们就从客户端先着手
客户端位移同步
一、在客户端添加SyncTransformRequest脚本,用于客户端给服务器发送位置信息
二、在客户端添加SyncTransformEvent脚本,用于监听服务器发送的所有客户端位置信息
三、在GameScene场景的Handler物体上挂载SyncTransformEvent
四、在Player脚本添加位置发送消息
五、在Player脚本添加其它玩家位置同步方法
SyncTransformRequest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using UnityEngine; using System.Collections; using System.Collections.Generic; namespace Net { public class SyncTransformRequest : Singleton<SyncTransformRequest> { //发起位置信息请求 public void SendSyncPositionRequest(Vector3 pos) { //把位置信息x,y,z传递给服务器端 Dictionary<byte, object> data = new Dictionary<byte, object>(); data.Add(1, pos.x); data.Add(2, pos.y); data.Add(3, pos.z); PhotonEngine.Peer.OpCustom((byte)OperationCode.SyncPosition, data, true);//把Player位置传递给服务器 } } } |
SyncTransformEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
using UnityEngine; using System.Collections; using System.Collections.Generic; using ExitGames.Client.Photon; using Tools; using System.IO; using System.Xml.Serialization; namespace Net { public class SyncTransformEvent : EventBase { public override void AddListener() { EventMediat.AddListener(EventCode.SyncPosition, OnSyncPositionReceived); } public override void RemoveListener() { EventMediat.RemoveListener(EventCode.SyncPosition, OnSyncPositionReceived); } void OnSyncPositionReceived(EventData eventData) { string playerDataListString = (string)DictTool.GetValue<byte, object>(eventData.Parameters, 1); //进行反序列化接收数据 using (StringReader reader = new StringReader(playerDataListString)) { XmlSerializer serializer = new XmlSerializer(typeof(List<PlayerData>)); List<PlayerData> playerDataList = (List<PlayerData>)serializer.Deserialize(reader); GameObject.FindGameObjectWithTag("Player").GetComponent<Player>().OnSyncPositionEvent(playerDataList); } } } } |
Player
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
using System.Collections.Generic; using Tools; using UnityEngine; public class Player : MonoBehaviour { public GameObject playerPrefab; private Vector3 lastPosition = Vector3.zero; private float moveOffset = 0.1f; //存储所有实例化出来的Player private Dictionary<string, GameObject> playerDic = new Dictionary<string, GameObject>(); void Start() { //设置本地的Player的颜色设置成绿色 GetComponent<Renderer>().material.color = Color.green; SyncPlayerRequest.Instance.SendSyncPlayerRequest(); //参数一 方法名,参数二 从等多久后开始执行这个方法 参数三 同步的时间速率。这里一秒同步十次 InvokeRepeating("SyncPosition", 3, 1 / 10f);//重复调用某个方法 } void Update() { //只有本地的Player,可以控制移动 float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); transform.Translate(new Vector3(h, 0, v) * Time.deltaTime * 4); } //位置信息时时更新 void SyncPosition() { //如果玩家的位置当前玩家的位置和上玩家上一个的位置距离大于0.1,就表示玩家移动了,就需要他位置的同步 if (Vector3.Distance(transform.position, lastPosition) > moveOffset) { lastPosition = transform.position; Net.SyncTransformRequest.Instance.SendSyncPositionRequest(transform.position);//调用位置信息同步的请求 } } //实例化其他客户端的角色 public void OnSyncPlayerResponse(List<string> usernameList) { //创建其他客户端的角色 foreach (string username in usernameList) { OnNewPlayerEvent(username); } } public void OnNewPlayerEvent(string username) { GameObject go = GameObject.Instantiate(playerPrefab); playerDic.Add(username, go);//利用集合保存所有的其他客户端 } 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.x, y = pd.y, z = pd.z }; } } } } |
服务器端位移同步
一、添加SyncTransformHandler脚本,用于接收客户端发来的位置信息
二、在ClientPeer脚本添加username、x\y\z,用于记录每个客户端的用户名,坐标
三、在LoginHandler脚本的OnLoginReceived方法给username赋值
四、在SyncTransformHandler给x\y\z赋值
五、添加SyncPositionTread脚本,用于每隔指定时间向所有在线客户端发送所有客户端的位置信息
六、在MyGameServer脚本添加开启SyncPositionTread线程
SyncTransformHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
using MyGameServer.Tools; using Photon.SocketServer; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGameServer.Handler { class SyncTransformHandler : IHandlerBase { public void AddListener() { HandlerMediat.AddListener(OperationCode.SyncPosition, OnSyncPositionReceived); } public void RemoveListener() { HandlerMediat.RemoveListener(OperationCode.SyncPosition, OnSyncPositionReceived); } //获取客户端位置请求的处理的代码 public void OnSyncPositionReceived(ClientPeer peer, OperationRequest operationRequest, SendParameters sendParameters) { //接收位置并保持起来 float x = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters, 1); float y = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters, 2); float z = (float)DictTool.GetValue<byte, object>(operationRequest.Parameters, 3); peer.x = x; peer.y = y; peer.z = z; MyGameServer.log.Info(x + "--" + y + "--" + z);//输出测试 } } } |
ClientPeer
1 2 3 |
public string username; public float x, y, z; |
LoginHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void OnLoginReceived(ClientPeer peer, OperationRequest operationRequest, SendParameters sendParameters) { //根据发送过来的请求获得用户名和密码 string username = DictTool.GetValue<byte, object>(operationRequest.Parameters, 1) as string; string password = DictTool.GetValue<byte, object>(operationRequest.Parameters, 2) as string; //连接数据库进行校验 UserManager manager = new UserManager(); bool isSuccess = manager.VerifyUser(username, password); OperationResponse response = new OperationResponse(operationRequest.OperationCode); //如果验证成功,把成功的结果利用response.ReturnCode返回成功给客户端 if (isSuccess) { response.ReturnCode = (short)ReturnCode.Success; peer.username = username; } else//否则返回失败给客户端 { response.ReturnCode = (short)ReturnCode.Failed; } response.Parameters = new Dictionary<byte, object>(); response.Parameters.Add(1, username); //把上面的回应给客户端 peer.SendOperationResponse(response, sendParameters); } |
SyncPositionTread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
using MyGameServer.Common; using Photon.SocketServer; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Serialization; 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.x = peer.x;//设置playerdata里面的Position playerdata.y = peer.y; playerdata.z = peer.z; 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(1, 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();//终止线程 } } } |
MyGameServer
1 |
private SyncPositionThread syncPositinThread = new SyncPositionThread(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
protected override void Setup() { Instance = this; syncPositinThread.Run(); // 日志的初始化(定义配置文件log4net位置) // Path.Combine 表示连接目录和文件名,可以屏蔽平台的差异 // Photon: ApplicationLogPath 就是配置文件里面路径定义的属性 //this.ApplicationPath 表示可以获取photon的根目录,就是Photon-OnPremise-Server-SDK_v4-0-29-11263\deploy这个目录 // 这一步是设置日志输出的文档文件的位置,这里我们把文档放在Photon-OnPremise-Server-SDK_v4-0-29-11263\deploy\bin_Win64\log里面 log4net.GlobalContext.Properties["Photon:ApplicationLogPath"] = Path.Combine(Path.Combine(Path.Combine(this.ApplicationRootPath, "bin_win64")), "log"); //this.BinaryPath表示可以获取的部署目录就是目录Photon-OnPremise-Server-SDK_v4-0-29-11263\deploy\MyGameServer\bin FileInfo configFileInfo = new FileInfo(Path.Combine(this.BinaryPath, "log4net.config"));// 告诉log4net日志的配置文件的位置 // 如果这个配置文件存在 if (configFileInfo.Exists) { LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);// 设置photon我们使用哪个日志插件 XmlConfigurator.ConfigureAndWatch(configFileInfo);// 让log4net这个插件读取配置文件 } log.Info("Setup Completed!");// 最后利用log对象就可以输出了 AddHandler(); } // server端关闭的时候 protected override void TearDown() { syncPositinThread.Stop(); RemoveHandler(); log.Info("关闭了服务器"); } |
以上内容完成,服务器端重新生成,就可以在Unity上测试位移同步的效果啦
项目使用版本:Unity5.3.4 GitHub下载地址:
https://github.com/654306663/PhotonServer
- 本文固定链接: http://www.u3d8.com/?p=1489
- 转载请注明: 网虫虫 在 u3d8.com 发表过
你好,启动不了线程怎么办。刚调用Run()启动然后就会报错,“正在终止线程”
第几个步骤出现的问题?是不是配置错了?