评价用的不是很爽,但不得不用...
翻译文档:虽让版本很老,但是还是有价值
https://vibrantlink.com/pun_multiplay_manual/?tdsourcetag=s_pcqq_aiomsg
1.创建连接
NetWork.onClick.AddAudioListener(() => {if (PhotonNetwork.IsConnected){UIMgr.OpenPopPanel<W_SelectNetworkGameMode>();}else{UIMgr.OpenPanel<W_ConnectTip>();PhotonNetwork.ConnectUsingSettings();PhotonNetwork.ConnectToBestCloudServer();}ShengYaMgrData.Inst.type = 3;});//配套回调public override void OnConnectedToMaster(){base.OnConnectedToMaster();}
2.创建或加入房间
//创建房间 创建一个带名字的房间
PhotonNetwork.CreateRoom(CreateRoomInputField.text, new RoomOptions { MaxPlayers = 5 },new TypedLobby(lobbyName, LobbyType.SqlLobby));//配套回调public override void OnJoinedRoom(){base.OnJoinedRoom();UIMgr.ClosePanel<W_ConnectTip>();UIMgr.OpenPopPanel<W_NetworkRoom>();}//随机加入一个指定类型的房间
PhotonNetwork.JoinRandomRoom(null, 2, MatchmakingMode.FillRoom, new TypedLobby(PassionSpeedMgrData.inst.curLobbyName.ToString(), LobbyType.SqlLobby), null);//随机加入房间失败会返回OnJoinRandomFailed 需要在里面创建一个新房间public override void OnJoinRandomFailed(short returnCode, string message){base.OnJoinRandomFailed(returnCode, message);Debug.LogError("OnJoinRandomFailed");if (PassionSpeedMgrData.inst.curNetworkRoom == NetworkRoom.DEFAULT){//string lobbyName = string.Equals(PassionSpeedMgrData.Inst.curLobbyName.ToString(), LobbyName.SPEED.ToString()) ? LobbyName.SPEED.ToString() : LobbyName.OVERTAKE.ToString();PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = 5 }, new TypedLobby(PassionSpeedMgrData.inst.curLobbyName.ToString(), LobbyType.SqlLobby));}else{//string lobbyName = string.Equals(PassionSpeedMgrData.Inst.curLobbyName.ToString(), LobbyName.SPEED.ToString()) ? LobbyName.RANKINGSPEED.ToString() : LobbyName.RANKINGOVERTAKE.ToString();PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = 5 }, new TypedLobby(PassionSpeedMgrData.inst.curLobbyName.ToString(), LobbyType.SqlLobby));}}//查找房间string lobbyName = string.Equals(PassionSpeedMgrData.Inst.curLobbyName.ToString(), LobbyName.SPEED.ToString()) ? LobbyName.SPEED.ToString() : LobbyName.OVERTAKE.ToString();TypedLobby typedLobby = new TypedLobby(lobbyName, LobbyType.SqlLobby);//if (ShengYaMgrData.Inst.type == 2)//sqlLobby = new TypedLobby(Lobby.SPEED.ToString(), LobbyType.SqlLobby);//else if (ShengYaMgrData.Inst.type == 2)// sqlLobby = new TypedLobby(Lobby.SPEED.ToString(), LobbyType.SqlLobby);RestRoom();PhotonNetwork.GetCustomRoomList(typedLobby, "10000");//配套回调public override void OnRoomListUpdate(List<RoomInfo> roomList){foreach (RoomInfo entry in roomList){if(entry.Name.Contains(containsString)){ GameObject item= GameObject.Instantiate(PassionSpeedMgr.Inst.Loader.LoadSync("UIItem_Rooms", "UI/UIItem/{0}")) as GameObject;item.transform.SetParent(Content, false);item.transform.GetChild(0).GetComponent<Text>().text = entry.Name;item.transform.GetChild(1).GetComponent<Text>().text = entry.PlayerCount+"/"+ entry.MaxPlayers;item.transform.GetChild(2).GetComponent<Button>().onClick.AddAudioListener(() => {UIMgr.OpenPanel<W_ConnectTip>();PhotonNetwork.JoinRoom(entry.Name);});// Debug.LogError}roomListCell = roomList;}}
3.事件发送PhotonNetwork.RaiseEvent
//特别注意,坑1
//向其它玩家发送事件,只有其它玩家才能收到此事件的监听(疑惑?)(自己不能给自己发)
public void SendEvnet(NetClientEventCode _id, ExitGames.Client.Photon.Hashtable _paras =null){PhotonNetwork.RaiseEvent((byte)_id,_paras,new RaiseEventOptions() { Receivers = ReceiverGroup.All, CachingOption = EventCaching.AddToRoomCache },SendOptions.SendReliable);}
ex:public void UpRankData(){//void UpData()//{ExitGames.Client.Photon.Hashtable moveHt = new ExitGames.Client.Photon.Hashtable();moveHt.Add("playerID", PhotonNetwork.LocalPlayer.UserId);moveHt.Add("playerCarId", PassionSpeedMgrData.inst.CurCarTable.id);moveHt.Add("playerCarJiFen", PassionSpeedMgrData.inst.ChaoCheCount);moveHt.Add("playerDis", PassionSpeedMgrData.inst.XingShiLiCheng);moveHt.Add("playerName", PassionSpeedMgrData.inst.UserName);moveHt.Add("playerTime", TTOTool.inst.SecondToDate(PassionSpeedMgrData.Inst.XingShiShiJian));PassionSpeedMgr.Inst.SendEvnet(NetClientEventCode.UpNetData, moveHt);//}}//相关回调
public void OnEvent(EventData photonEvent){// throw new System.NotImplementedException();Debug.Log("{0}: {1}".TTOFormat(this.name,photonEvent.Code));if (photonEvent.Code == (byte)NetClientEventCode.UpNetData){ExitGames.Client.Photon.Hashtable evTable = photonEvent.CustomData as ExitGames.Client.Photon.Hashtable;var playerID = (string)evTable["playerID"];var _playerCarId = (int)evTable["playerCarId"];var _playerCarJiFen = (int)evTable["playerCarJiFen"];var _playerDis = (float)evTable["playerDis"];var _playerName = (string)evTable["playerName"];var _playerTime = (string)evTable["playerTime"];NetRankInfo ni = new NetRankInfo{playerCarId = _playerCarId,playerCarJiFen = _playerCarJiFen,playerDis = _playerDis,playerName = _playerName,playerTime = _playerTime};if (netRankList.ContainsKey(playerID)){netRankList[playerID] = ni;}else{netRankList.Add(playerID, ni);}var win = UIMgr.GetPanel<W_NetworkRank>();var giWin= UIMgr.GetPanel<W_GameIn>();if (win != null){//刷新数据win.ShutData(netRankList.Values.ToList());}if (giWin != null){giWin.InitGameInNetRank();}Debug.Log("{0}: {1}:{2}:{3}:{4}".TTOFormat("UpNetData:", _playerName,_playerCarId, playerID,""));}else if (photonEvent.Code == (byte)NetClientEventCode.MasterPaly|| photonEvent.Code == (byte)NetClientEventCode.PaiWeiSaiStart|| photonEvent.Code == (byte)NetClientEventCode.OtherrPlayerQuitRoom|| photonEvent.Code == 253// || photonEvent.Code == 255|| photonEvent.Code == 226){var win = UIMgr.GetPanel<W_NetworkRoom>();if (win != null){win.ShutWinByEventCode(photonEvent);}}else if (photonEvent.Code == 254){// netRankList.Clear();}}
事件代码(在LoadBalancingPeer.cs 文件里的 OperationCode.RaiseEvent )
4.RPC调用
直接发送通知给指定玩家
//RPC 方法定义[PunRPC]void NetPaiWeiFinish(){Debug.Log("排位赛结束,游戏结束");// PhotonNetwork.LeaveRoom();PassionSpeedMgrData.inst.PauseGame(true);//var win= UIMgr.OpenPanel<W_GameSuck>();//win.SetAniTrigger("fail");PassionSpeedMgr.Inst.UpRankData();if (!PassionSpeedMgrData.Inst.IsNetOver){var win = UIMgr.OpenPanel<W_NetworkRank>();PassionSpeedMgrData.Inst.IsNetOver = true;}//获取胜利者}
ex: PhotonView.Get(this).RPC("NetSceneFinish", RpcTarget.Others);
5.实例实时同步
OnPhotonSerializeView方法是同步方法 需理解发送方和接收方
using HotFix_PassionSpeed;
using Photon.Pun;
using QFramework;
using QFramework.PassionSpeed;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TransPosSynchronization : GameInst<TransPosSynchronization>, IPunObservable
{//需要实时同步的一些数据private float m_Distance;private float m_Angle;private PhotonView m_PhotonView;private Vector3 m_Direction;private Vector3 m_NetworkPosition;// Vector3 m_NetworkPositionZFrame;//单帧速度private Vector3 m_StoredPosition;private Quaternion m_NetworkRotation;public bool m_SynchronizePosition = true;public bool m_SynchronizeRotation = true;public bool m_SynchronizeScale = false;bool m_firstTake = false;public void Awake(){m_PhotonView = GetComponent<PhotonView>();m_StoredPosition = transform.position;m_NetworkPosition = Vector3.zero;m_NetworkRotation = Quaternion.identity;}void OnEnable(){m_firstTake = true;}[PunRPC]void NetSceneFinish(){Debug.Log("你们失败了,游戏结束");// PhotonNetwork.LeaveRoom();PassionSpeedMgrData.inst.PauseGame(true);//var win= UIMgr.OpenPanel<W_GameSuck>();//win.SetAniTrigger("fail");PassionSpeedMgr.Inst.UpRankData();// UpData();var win = UIMgr.OpenPanel<W_NetworkRank>();PassionSpeedMgrData.Inst.IsNetOver = true;}[PunRPC]void NetPaiWeiFinish(){Debug.Log("排位赛结束,游戏结束");// PhotonNetwork.LeaveRoom();PassionSpeedMgrData.inst.PauseGame(true);//var win= UIMgr.OpenPanel<W_GameSuck>();//win.SetAniTrigger("fail");PassionSpeedMgr.Inst.UpRankData();if (!PassionSpeedMgrData.Inst.IsNetOver){var win = UIMgr.OpenPanel<W_NetworkRank>();PassionSpeedMgrData.Inst.IsNetOver = true;}//获取胜利者}float DataUpTime = 1;public void Update(){if (PassionSpeedMgrData.inst.IsNetModel && !TTOSceneMgr.inst.IsCheKu()){if (PassionSpeedMgrData.inst.curNetworkRoom == NetworkRoom.DEFAULT){if (PassionSpeedMgrData.inst.curLobbyName == LobbyName.SPEED){//满足条件直接胜利if (PassionSpeedMgrData.inst.XingShiLiCheng >= PassionSpeedMgrData.Inst.NetJinSuTarget){if (!PassionSpeedMgrData.Inst.IsNetOver){PhotonView.Get(this).RPC("NetSceneFinish", RpcTarget.Others);Debug.Log("suck res");PassionSpeedMgrData.inst.PauseGame(true);//var win= UIMgr.OpenPanel<W_GameSuck>();// win.SetAniTrigger("victory");PassionSpeedMgr.Inst.UpRankData();var win = UIMgr.OpenPanel<W_NetworkRank>();//PhotonNetwork.LeaveRoom();PassionSpeedMgrData.Inst.IsNetOver = true;//获取排位信息//PhotonNetwork.PlayerList.//win.SetAniTrigger("victory");}}}else{if (PassionSpeedMgrData.inst.ChaoCheCount >= PassionSpeedMgrData.Inst.NetChaoCheCount){if (!PassionSpeedMgrData.Inst.IsNetOver){PhotonView.Get(this).RPC("NetSceneFinish", RpcTarget.Others);//PhotonNetwork.LeaveRoom();Debug.Log("suck res");PassionSpeedMgrData.inst.PauseGame(true);PassionSpeedMgr.Inst.UpRankData();//PassionSpeedMgr.Inst.NetRankListAddSelf();var win = UIMgr.OpenPanel<W_NetworkRank>();//var win = UIMgr.OpenPanel<W_GameSuck>();//win.SetAniTrigger("victory");PassionSpeedMgrData.Inst.IsNetOver = true;}}}}else if (PassionSpeedMgrData.inst.curNetworkRoom == NetworkRoom.RANKING){if (!PassionSpeedMgrData.Inst.IsNetOver){//排位赛按规定时间去处理if (PassionSpeedMgrData.inst.XingShiShiJian >= PassionSpeedMgrData.inst.NetJinSuTime){//通知所有人 结束了PhotonView.Get(this).RPC("NetPaiWeiFinish", RpcTarget.All);//PassionSpeedMgr.Inst//PassionSpeedMgrData.Inst.IsNetOver = true;}}//if (PassionSpeedMgrData.Inst.curLobbyName == LobbyName.RANKINGSPEED)//{//}}//定时同步信息DataUpTime -= Time.deltaTime;if (DataUpTime <= 0){PassionSpeedMgr.Inst.UpRankData();}}if (!this.m_PhotonView.IsMine){var disz = transform.position.z - this.m_NetworkPosition.z;this.m_Distance =Vector3.Distance(transform.position, this.m_NetworkPosition);//var disz = transform.position.z - this.m_NetworkPosition.z;//解决抖动问题//Debug.Log("{0}:{1}:{2}".TTOFormat(transform.position, this.m_NetworkPosition, m_Distance));//transform.position = Vector3.MoveTowards(transform.position, this.m_NetworkPosition, this.m_Distance *1f * (1.0f / PhotonNetwork.SerializationRate));transform.position = Vector3.MoveTowards(transform.position, this.m_NetworkPosition, this.m_Distance *Time.deltaTime);transform.rotation = Quaternion.RotateTowards(transform.rotation, this.m_NetworkRotation, this.m_Angle * (1.0f / PhotonNetwork.SerializationRate));}}public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){//是否是发送方if (stream.IsWriting){if (this.m_SynchronizePosition){this.m_Direction = transform.position - this.m_StoredPosition;this.m_StoredPosition = transform.position;stream.SendNext(new Vector3(transform.position.x, transform.position.y, PassionSpeedMgrData.inst.NetLength));stream.SendNext(this.m_Direction);}if (this.m_SynchronizeRotation){stream.SendNext(transform.rotation);}if (this.m_SynchronizeScale){stream.SendNext(transform.localScale);}}else{
//接收方if (this.m_SynchronizePosition){this.m_NetworkPosition = (Vector3)stream.ReceiveNext();var dis = this.m_NetworkPosition.z - (PassionSpeedMgrData.inst.NetLength);this.m_NetworkPosition = new Vector3(this.m_NetworkPosition.x, this.m_NetworkPosition.y, dis);this.m_Direction = (Vector3)stream.ReceiveNext();if (m_firstTake){//初始化位置transform.position = this.m_NetworkPosition;this.m_Distance = 0f;}else{float lag = Mathf.Abs((float)(PhotonNetwork.Time - info.SentServerTime));this.m_NetworkPosition += this.m_Direction * lag;// this.m_Distance = Vector3.Distance(transform.position, this.m_NetworkPosition);}}if (this.m_SynchronizeRotation){this.m_NetworkRotation = (Quaternion)stream.ReceiveNext();if (m_firstTake){this.m_Angle = 0f;transform.rotation = this.m_NetworkRotation;}else{this.m_Angle = Quaternion.Angle(transform.rotation, this.m_NetworkRotation);}}if (this.m_SynchronizeScale){transform.localScale = (Vector3)stream.ReceiveNext();}if (m_firstTake){m_firstTake = false;}}}}
6.玩家属性同步
public static void SetIsStart(this Player player, int isStart){Hashtable IsStart = new Hashtable(); // using PUN's implementation of HashtableIsStart[PlayerIsStartProp] = isStart;player.SetCustomProperties(IsStart); // this locally sets the score and will sync it in-game asap.}public static int GetIsStart(this Player player){object isStart;if (player.CustomProperties.TryGetValue(PlayerIsStartProp, out isStart)){return (int)isStart;}return 0;}
//调用参考public bool IsReady(){bool isReady = true;PhotonNetwork.PlayerList.ForEach(s=> {if (s.GetIsStart() != 1){isReady = false;}});return isReady;}
7.常用api 备忘
自定义类继承自Photon.PunBehaviour
加入房间: PhotonNetwork.JoinRandomRoom();
加入房间失败时的回调方法: OnPhotonRandomJoinFailed(object[] codeAndMsg) {}
创建房间: PhotonNetwork.CreateRoom(string roomName);
创建或加入房间: PhotonNetwork.JoinOrCreateRoom(string roomName);
PhotonNetwork.LocalPlayer.SetIsReady(!PhotonNetwork.LocalPlayer.GetIsReady());
PhotonNetwork.IsConnectedAndReady (连接成功并已经准备完毕)
创建房间成功的回调方法: OnCreatedRoom() {}
加入房间成功时的回调方法: OnJoinedRoom() {}
OnPlayerEnteredRoom
override void W_NetworkRoom.OnLeftRoom()
OnPhotonPlayerConnected(PhotonPlayer newPlayer) {}
房间内所有玩家是否同步场景的属性: PhotonNetwork.automaticallySyncScene == true;
// defines if all clients in a room should load the same level/scene as the Master client (if that used PhotonNetwork.LoadLevel())
判断当前客户端是否为主机(房主): PhotonNetwork.isMasterClient
载场景: PhotonNetwork.LoadLevel(string sceneName); // 比如 PhotonNetwork.LoadLevel("main");
//经常用到的函数
PhotonNetwork.Instantiate
PhotonNetwork.InstantiateSceneObject
//事件代码(在LoadBalancingPeer.cs 文件里的 OperationCode.RaiseEvent )
public class OperationCode{[Obsolete("Exchanging encrpytion keys is done internally in the lib now. Don't expect this operation-result.")]public const byte ExchangeKeysForEncryption = 250;/// <summary>(255) Code for OpJoin, to get into a room.</summary>[Obsolete]public const byte Join = 255;/// <summary>(231) Authenticates this peer and connects to a virtual application</summary>public const byte AuthenticateOnce = 231;/// <summary>(230) Authenticates this peer and connects to a virtual application</summary>public const byte Authenticate = 230;/// <summary>(229) Joins lobby (on master)</summary>public const byte JoinLobby = 229;/// <summary>(228) Leaves lobby (on master)</summary>public const byte LeaveLobby = 228;/// <summary>(227) Creates a game (or fails if name exists)</summary>public const byte CreateGame = 227;/// <summary>(226) Join game (by name)</summary>public const byte JoinGame = 226;/// <summary>(225) Joins random game (on master)</summary>public const byte JoinRandomGame = 225;// public const byte CancelJoinRandom = 224; // obsolete, cause JoinRandom no longer is a "process". now provides result immediately/// <summary>(254) Code for OpLeave, to get out of a room.</summary>public const byte Leave = (byte)254;/// <summary>(253) Raise event (in a room, for other actors/players)</summary>public const byte RaiseEvent = (byte)253;/// <summary>(252) Set Properties (of room or actor/player)</summary>public const byte SetProperties = (byte)252;/// <summary>(251) Get Properties</summary>public const byte GetProperties = (byte)251;/// <summary>(248) Operation code to change interest groups in Rooms (Lite application and extending ones).</summary>public const byte ChangeGroups = (byte)248;/// <summary>(222) Request the rooms and online status for a list of friends (by name, which should be unique).</summary>public const byte FindFriends = 222;/// <summary>(221) Request statistics about a specific list of lobbies (their user and game count).</summary>public const byte GetLobbyStats = 221;/// <summary>(220) Get list of regional servers from a NameServer.</summary>public const byte GetRegions = 220;/// <summary>(219) WebRpc Operation.</summary>public const byte WebRpc = 219;/// <summary>(218) Operation to set some server settings. Used with different parameters on various servers.</summary>public const byte ServerSettings = 218;/// <summary>(217) Get the game list matching a supplied sql filter (SqlListLobby only) </summary>public const byte GetGameList = 217;}