下面的代码内容还是根据上一篇文章的代码做更改和添加内容
添加场景
新建场景,命名GameScene,添加在上一章登录成功后跳转至该场景
下面操作内容都是在GameScene场景开发
一、新建Player预设体,并Copy一份作为其它玩家预设体,把Player预设体拖入场景
二、修改Player的Tag为Player,并拖入场景中
三、在Player物体上新建Player.cs脚本,添加控制移动代码,并挂载OtherPlayer预设体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { public GameObject playerPrefab; void Start () { //设置本地的Player的颜色设置成绿色 GetComponent<Renderer>().material.color = Color.green; } void Update () { //只有本地的Player,可以控制移动 float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); transform.Translate(new Vector3(h,0,v)*Time.deltaTime*4); } |
现在运行起来,本地Player是绿色的,可以通过方向来控制移动了
同步客户端准备
在客户端与服务器端同步创建玩家这个步骤里
客户端需要做三件事情:
1.当前客户端创建角色,发送告知服务器端
2.当前客户端接收服务器发送的其它在线客户端的玩家角色信息
3.其它客户端接收服务器发送的当前客户端的角色信息
服务器端需要做两件事情:
1.告诉当前客户端 其它在线客户端的信息,让当前客户端创建其它客户端玩家角色
2.告诉其它客户端 有新的客户端加入,需要创建新客户端玩家角色
在操作之前,我们先在客户端和服务器端同时添加事件枚举和与服务器发送的数据类型
新建文件EventData、PlayerData,并添加OperationCode类型(OperationCode枚举的Default类型在客户端和服务器端都调整到第一位,方便阅读)
1 2 3 4 5 6 7 8 |
namespace Net { public enum EventCode : byte { NewPlayer, SyncPosition, } } |
1 2 3 4 5 6 7 8 9 10 |
using System; [Serializable] public class PlayerData { public float x { get; set; } public float y { get; set; } public float z { get; set; } public string Username { get; set; } } |
1 2 3 4 5 6 7 8 9 10 11 |
namespace Net { public enum OperationCode : byte { Default, Login, Register, SyncPosition, SyncPlayer } } |
同步客户端玩家角色(客户端)
一、在客户端添加SyncPlayerRequest脚本,用于客户端发送创建角色消息给服务器
二、在客户端添加SyncPlayerHandler脚本,用于接收服务器发送过来的其它客户端玩家角色信息
三、在客户端添加EventBase、EventMediat、SyncPlayerEvent脚本,用于监听服务器端主动发来的新客户端加入的玩家角色信息
四、修改PhotonEngine脚本的OnEvent方法,分发Event消息
五、在Player类添加实例化其他客户端方法
六、在Player类添加发送客户端创建角色消息
七、在场景添加空物体,命名为Handler,并挂载SyncPlayerHandler、SyncPlayerEvent,用于监听服务器发来的客户端玩家信息
其中第六条是实现 “当前客户端创建角色,发送告知服务器端”
第一、二条实现 “当前客户端接收服务器发送的其它在线客户端的玩家角色信息”
第三、四条实现 “其它客户端接收服务器发送的当前客户端的角色信息”
SyncPlayerRequest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using UnityEngine; using System.Collections; using Net; public class SyncPlayerRequest : Singleton<SyncPlayerRequest> { /// <summary> /// 发送玩家信息 /// </summary> public void SendSyncPlayerRequest() { PhotonEngine.Peer.OpCustom((byte)OperationCode.SyncPlayer, null, true); } } |
SyncPlayerHandler
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 |
using UnityEngine; using System.Collections; using System; using ExitGames.Client.Photon; using Tools; using System.IO; using System.Xml.Serialization; using System.Collections.Generic; namespace Net { public class SyncPlayerHandler : HandlerBase { public override void AddListener() { HandlerMediat.AddListener(OperationCode.SyncPlayer, OnSyncPlayerReceived); } public override void RemoveListener() { HandlerMediat.RemoveListener(OperationCode.SyncPlayer, OnSyncPlayerReceived); } void OnSyncPlayerReceived(OperationResponse response) { string usernameListString = (string)DictTool.GetValue<byte, object>(response.Parameters, 1); Debug.Log(usernameListString); //通过xml反序列化接收服务器传输过来的List数据 using (StringReader reader = new StringReader(usernameListString)) { XmlSerializer serializer = new XmlSerializer(typeof(List<string>)); List<string> usernameList = (List<string>)serializer.Deserialize(reader);//表示读取字符串 GameObject.FindGameObjectWithTag("Player").GetComponent<Player>().OnSyncPlayerResponse(usernameList); } } } } |
EventBase
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 |
using UnityEngine; using System.Collections; namespace Net { public abstract class EventBase : MonoBehaviour { public virtual void Awake() { AddListener(); } public virtual void OnDestroy() { RemoveListener(); } public abstract void AddListener(); public abstract void RemoveListener(); } } |
EventMediat
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using ExitGames.Client.Photon; namespace Net { public class EventMediat { public delegate void Act(EventData t); static Dictionary<EventCode, Delegate> messageTable = new Dictionary<EventCode, Delegate>(); /// <summary> /// 注册监听 /// </summary> /// <param name="type"></param> /// <param name="act"></param> public static void AddListener(EventCode type, Act act) { if (!messageTable.ContainsKey(type)) { messageTable.Add(type, null); } Delegate d = messageTable[type]; if (d != null && d.GetType() != act.GetType()) { Debug.LogError(string.Format("Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}", type, d.GetType().Name, act.GetType().Name)); } else { messageTable[type] = (Act)messageTable[type] + act; } } /// <summary> /// 移除监听 /// </summary> /// <param name="type"></param> /// <param name="act"></param> public static void RemoveListener(EventCode type, Act act) { if (messageTable.ContainsKey(type)) { Delegate d = messageTable[type]; if (d == null) { Debug.LogError(string.Format("Attempting to remove listener with for event type \"{0}\" but current listener is null.", type)); } else if (d.GetType() != act.GetType()) { Debug.LogError(string.Format("Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}", type, d.GetType().Name, act.GetType().Name)); } else { messageTable[type] = (Act)messageTable[type] - act; if (d == null) { messageTable.Remove(type); } } } else { Debug.LogError(string.Format("Attempting to remove listener for type \"{0}\" but Messenger doesn't know about this event type.", type)); } } /// <summary> /// 发送事件 /// </summary> /// <param name="type"></param> /// <param name="param"></param> public static void Dispatch(EventCode type, EventData param) { Delegate d; if (messageTable.TryGetValue(type, out d)) { Act callback = d as Act; if (callback != null) { callback(param); } else { Debug.LogError(string.Format("no such event type {0}", type)); } } } /// <summary> /// 移除所有监听 /// </summary> /// <param name="type"></param> public static void RemoveAllListener(EventCode type) { if (messageTable.ContainsKey(type)) { messageTable[type] = null; messageTable.Remove(type); } else { Debug.LogError(string.Format("Attempting to remove listener for type \"{0}\" but Messenger doesn't know about this event type.", type)); } } } } |
SyncPlayerEvent
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 |
using UnityEngine; using System.Collections; using System; using ExitGames.Client.Photon; using Tools; using System.IO; using System.Xml.Serialization; using System.Collections.Generic; namespace Net { public class SyncPlayerEvent : EventBase { public override void AddListener() { EventMediat.AddListener(EventCode.NewPlayer, OnSyncPlayerReceived); } public override void RemoveListener() { EventMediat.RemoveListener(EventCode.NewPlayer, OnSyncPlayerReceived); } void OnSyncPlayerReceived(EventData eventData) { string username = (string)DictTool.GetValue<byte, object>(eventData.Parameters, 1); Debug.Log(username); GameObject.FindGameObjectWithTag("Player").GetComponent<Player>().OnNewPlayerEvent(username); } } } |
PhotonEngine
1 2 3 4 5 |
public void OnEvent(EventData eventData) { EventCode code = (EventCode)eventData.Code;//得到响应的EventCode EventMediat.Dispatch(code, eventData); } |
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 |
using System.Collections.Generic; using Tools; using UnityEngine; public class Player : MonoBehaviour { public GameObject playerPrefab; //存储所有实例化出来的Player private Dictionary<string, GameObject> playerDic = new Dictionary<string, GameObject>(); void Start() { //设置本地的Player的颜色设置成绿色 GetComponent<Renderer>().material.color = Color.green; // 客户端发送创建角色消息 SyncPlayerRequest.Instance.SendSyncPlayerRequest(); } void Update() { //只有本地的Player,可以控制移动 float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); transform.Translate(new Vector3(h, 0, v) * Time.deltaTime * 4); } //实例化其他客户端的角色 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);//利用集合保存所有的其他客户端 } } |
同步客户端玩家角色(服务器端)
在服务器端,同样需要添加EventData、PlayerData,并添加OperationCode类型这步操作。
为了保持一致,如果客户端和服务器公用的类、枚举类型等数据比较多,可以考虑发布成dll来共享,暂时我们省略此步骤
注:在服务器端这三个脚本没有加入Net命名空间,如果自己加入了,记得调用时引用命名空间~
这些准备工作操作完成后,我们就来做服务器段的消息处理
一、添加SyncPlayerHandler类,用来处理客户端发来的玩家角色创建请求
二、在MyGameServer类添加对SyncPlayerHandler的监听
三、在ClientPeer类添加username字段,记录玩家用户名
四、在LoginHandler脚本的OnLoginReceived方法给username赋值
五、在MyGameServer类里面创建一个集合,然后在每次客户端连接的进来后去实例这个客户端的ClientPeer,并保存起来
SyncPlayerHandler
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 |
using MyGameServer.Tools; using Photon.SocketServer; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; namespace MyGameServer.Handler { class SyncPlayerHandler : IHandlerBase { public void AddListener() { HandlerMediat.AddListener(OperationCode.SyncPlayer, OnSyncPlayerReceived); } public void RemoveListener() { HandlerMediat.RemoveListener(OperationCode.SyncPlayer, OnSyncPlayerReceived); } // public void OnSyncPlayerReceived(ClientPeer peer, OperationRequest operationRequest, SendParameters sendParameters) { //取得所有已经登陆(在线玩家)的用户名 List<string> usernameList = new List<string>(); foreach (ClientPeer tempPeer in MyGameServer.Instance.peerList) { //string.IsNullOrEmpty(tempPeer.username);//如果用户名为空表示没有登陆 //如果连接过来的客户端已经登陆了有用户名了并且这个客户端不是当前的客户端 if (!string.IsNullOrEmpty(tempPeer.username) && 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(1, usernameListString); OperationResponse response = new OperationResponse(operationRequest.OperationCode); response.Parameters = data; peer.SendOperationResponse(response, sendParameters); // 告诉其它客户端有新的客户端加入 foreach (ClientPeer tempPeer in MyGameServer.Instance.peerList) { if (!string.IsNullOrEmpty(tempPeer.username) && tempPeer != peer) { EventData ed = new EventData((byte)EventCode.NewPlayer); Dictionary<byte, object> data2 = new Dictionary<byte, object>(); data2.Add(1, peer.username); // 把新进来的用户名传递给其它客户端 ed.Parameters = data2; tempPeer.SendEvent(ed, sendParameters); // 发送事件 } } } } } |
MyGameServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#region 所有Handler LoginHandler loginHandler; SyncPlayerHandler syncPlayerHandler; #endregion // 初始化所有Handler public void AddHandler() { loginHandler = new LoginHandler(); loginHandler.AddListener(); syncPlayerHandler = new SyncPlayerHandler(); syncPlayerHandler.AddListener(); } // 移除所有Handler void RemoveHandler() { loginHandler.RemoveListener(); syncPlayerHandler.RemoveListener(); } |
ClientPeer
1 |
public string username; |
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 |
// 登陆请求的处理的代码 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; } //把上面的回应给客户端 peer.SendOperationResponse(response, sendParameters); } |
MyGameServer
1 2 3 4 5 6 7 8 9 10 11 |
//存放所有的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
1 2 3 4 5 |
//处理客户端断开连接的后续工作 protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail) { MyGameServer.Instance.peerlist.Remove(this);//断开连接的时候List里面移除当前的ClientPeer客户端 } |
以上内容完成,就可以用Unity发布出来,用两个用户登录后 查看效果
- 本文固定链接: http://www.u3d8.com/?p=1481
- 转载请注明: 网虫虫 在 u3d8.com 发表过