U3D Pun2 官方文档学习和翻译

这里是个人学习photon的一些总结,以及学习photon官方文档和对其的部分翻译和整理,都是些个人觉得基础和常用的部分,有什么错误谢谢指出~

pun2官方文档链接https://doc.photonengine.com/zh-cn/pun/current/getting-started/pun-intro

文章目录

连接和鉴权

区域

Photon提供全球范围内的低延迟游戏,client会初始化连接到photon的name server服务器上以获得一个可用的region列表,每个region彼此完全分离,一个region由若干MasterServer 【用于匹配】和 GameServers组成

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SsKVlFCl-1645156427976)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

  • RealTimeApi能够筛选最佳的region并提供连接。当client每次连接时会从Name Server处获取可用区域列表,这用于获取最新区域列表时检查最佳区域是否有变化,在ping通一个servers后,结果会以字符串的形式返回,并进行存储,这个字符串包括当前最佳区域、ping值、当前可用区域列表。当没有旧区域列表结果字符串时会去ping所有region,此操作十分耗时。如果有之前的字符串记录,则会去检查:a.是否区域列表发生变化 b.某个区域ping值是否已无法接受(大于以前ping值的1.5倍);如果这两个条件任一满足,则会去ping所有region并保存一个新结果。可以结合区域筛选器去改变玩家获取的区域列表。

  • PUN会使用unity的playerprefs去自动保存最佳区域的信息,并在下次连接时去再次ping这个region。 为了方便和调试,当前的“最佳区域”及其 ping 显示在 Unity 编辑器的 PhotonServerSettings 中。但是,这仅对 Unity Editor 的播放模式有效。

PhotonNetwork.ConnectUsingSettings() //使用时默认连接最佳区域
  • 最佳区域 这个选项不是确定的,有时可能因为相同或者差不多ping值导致随机。可能出现以下情况:1.一个设备对多个region具有相同的ping 2.连接到同一网络上的不同设备对同一region具有不同ping值;从而导致随机。可以通过在线区域白名单去筛选,或者显式选择region
  • 从 PUN v2.17 开始,添加了一个名为“Dev Region”的新功能。这是 PhotonServerSettings 中提供的新设置。使用此设置,所有开发版本都将使用相同的区域,避免“最佳区域”选择的初始匹配问题。创建 PhotonServerSettings 并在 Unity 编辑器的第一次运行 (PlayMode) 期间设置“开发区域”时,会自动启用“ Development build”。当您使用连接PhotonNetwork.ConnectUsingSettings()时,“Dev Region”仅在 Unity 编辑器和“ Development”构建中使用。还可以通过简单地删除值来禁用 Unity Editor 和“Development Build”中的“Dev Region”。因此,为避免在开发阶段出现最佳区域选择问题,请确保更新到最新的 PUN 2 版本。在 Unity Editor 中运行一次(进入 PlayMode 并连接)。Unity Editor 的第一个连接将设置“Dev Region”,这可以从 PhotonServerSettings 检查器中看到。 这样,所有客户端(Unity Editor 和builds)都将连接到同一个“开发区域”。
  • 每个photon region都有一个令牌,为了使用PhotonNetwork.ConnectToRegion连接到指定region,需要手动设置中的AppId和App Versions等配置(PhotonServerSettings在这种情况下不生效)
PhotonNetwork.ConnectToRegion

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sZ2lbexs-1645156427977)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

Region Hosted in Token
Asia Singapore asia
Australia Melbourne au
Canada, East Montreal cae
Chinese Mainland1 【需要单独申请,要给photon发送邮件解锁appid 】 Shanghai cn
Europe Amsterdam eu
India Chennai in
Japan Tokyo jp
Russia Moscow ru
Russia, East Khabarovsk rue
South Africa Johannesburg za
South America Sao Paulo sa
South Korea Seoul kr
Turkey Istanbul tr
USA, East Washington D.C. us
USA, West San José usw
  • 可以为每个app设置各自的region filter,dashboard-》Manage-》Edit

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3GAxNMNv-1645156427978)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

  • 中国区Setting设置,可以选择使用PhotonServerSettings或者Code

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wphjrYfG-1645156427978)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

void ConnectToChina(){
   
    AppSettings chinaSettings = new AppSettings();
    chinaSettings.UseNameServer = true;
    chinaSettings.ServerAddress = "ns.photonengine.cn";
    chinaSettings.AppIdRealtime = "ChinaPUNAppId"; // TODO: replace with your own PUN AppId unlocked for China region
    chinaSettings.AppVersion = "ChinaAppVersion"; // optional
    PhotonNetwork.ConnectUsingSettings(chinaSettings);
}

TCP和UDP

根据 API 的不同,连接到 Photon Cloud 可能需要根据应用程序的协议和目的传递适当的端口。在大多数情况下,不必担心端口号。下表包含每个协议和每个服务器连接要与 Photon Cloud 一起使用的默认端口。

Port Number Protocol Purpose
5058 or 27000 UDP Client to Nameserver (UDP)
5055 or 27001 UDP Client to Master Server (UDP)
5056 or 27002 UDP Client to Game Server (UDP)
4533 TCP Client to Nameserver (TCP)
4530 TCP Client to Master Server (TCP)
4531 TCP Client to Game Server (TCP)
9090 TCP Client to Master Server (WebSockets)
9091 TCP Client to Game Server (WebSockets)
9093 TCP Client to Nameserver (WebSockets)
19090 TCP Client to Master Server (Secure WebSockets)
19091 TCP Client to Game Server (Secure WebSockets)
19093 TCP Client to Nameserver (Secure WebSockets)

鉴权

STEAM

转到DashBoard Multiplayer Game Development Made Easy | Photon Engine DashBoard -》MANAGE-》Authentication-》STEAM

  • apiKeySecret : Steam 发布者 Web API 密钥。

  • appid : Steam 游戏的 ID。

  • verifyOwnership: 验证用户是否真正拥有(购买了游戏并将其保存在他的库中)该游戏

  • verifyVacBan、 **verifyPubBan:**是否验证用户被ban

  • 客户端必须使用Valve’s Steamworks API获取会话ticket用以证明是有效的Steam用户

  • 在项目中支持SteamWork.NET Steamworks.NET - Installation

  • 获取ticket以及发送

//获取session ticket并转换成UTF8
// hAuthTicket should be saved so you can use it to cancel the ticket as soon as you are done with itpublic string GetSteamAuthTicket(out HAuthTicket hAuthTicket){
   
    byte[] ticketByteArray = new byte[1024];
    uint ticketSize;
    hAuthTicket = SteamUser.GetAuthSessionTicket(ticketByteArray, ticketByteArray.Length, out ticketSize);
    System.Array.Resize(ref ticketByteArray, (int)ticketSize);
    StringBuilder sb = new StringBuilder();
    for(int i=0; i < ticketSize; i++)
    {
   
        sb.AppendFormat("{0:x2}", ticketByteArray[i]);
    }
    return sb.ToString();}

//发送ticket进行鉴权
PhotonNetwork.AuthValues = new AuthenticationValues();
PhotonNetwork.AuthValues.UserId = SteamUser.GetSteamID().ToString();
PhotonNetwork.AuthValues.AuthType = CustomAuthenticationType.Steam;
PhotonNetwork.AuthValues.AddAuthParameter("ticket", SteamAuthSessionTicket);// connect
  • 在鉴权完毕后建议撤销ticket,建议在IConnectionCallbacks.OnConnected、IConnectionCallbacks.OnConnectedToMaster、IConnectionCallbacks.OnConnectedToMaster、IConnectionCallbacks.OnDisconnected任一中进行
private void OnConnected(){
   
    SteamUser.CancelAuthTicket(hAuthTicket);}
  • 完整代码
using Photon.Pun;using Photon.Realtime;using Steamworks;


public class MinimalSteamworksPunAuth : MonoBehaviourPunCallbacks{
   
    private HAuthTicket hAuthTicket;


    private void Start()
    {
   
        if (SteamManager.Initialized)
        {
   
            Connect();
        }
    }


    public void Connect()
    {
   
        string SteamAuthSessionTicket = GetSteamAuthTicket(out hAuthTicket);
        PhotonNetwork.AuthValues = new AuthenticationValues();
        PhotonNetwork.AuthValues.UserId = SteamUser.GetSteamID().ToString();
        PhotonNetwork.AuthValues.AuthType = CustomAuthenticationType.Steam;
        PhotonNetwork.AuthValues.AddAuthParameter("ticket", SteamAuthSessionTicket);
        PhotonNetwork.ConnectUsingSettings();
    }


    public override void OnConnectedToMaster()
    {
   
        CancelAuthTicket(hAuthTicket);
    }


    public override void OnCustomAuthenticationFailed(string errorMessage)
    {
   
        CancelAuthTicket(hAuthTicket);
    }


    // ticket should be saved so you can use it to cancel the ticket as soon as you are done with it
    private string GetSteamAuthTicket(out HAuthTicket ticket)
    {
   
        byte[] ticketByteArray = new byte[1024];
        uint ticketSize;
        ticket = SteamUser.GetAuthSessionTicket(ticketByteArray, ticketByteArray.Length, out ticketSize);
        System.Array.Resize(ref ticketByteArray, (int)ticketSize);
        StringBuilder sb = new StringBuilder();
        for(int i=0; i < ticketSize; i++)
        {
   
            sb.AppendFormat("{0:x2}", ticketByteArray[i]);
        }
        return sb.ToString();
    }


  private void CancelAuthTicket(HAuthTicket ticket)
  {
   
      if (ticket != null)
      {
   
          SteamUser.CancelAuthTicket(ticket);
      }
  }}
  • 使用 Facepunch.Steamworks
using Photon.Pun;using Photon.Realtime;using Steamworks;using UnityEngine;


public class MinimalSteamworksPunAuth : MonoBehaviourPunCallbacks{
   
    [SerializeField]
    private int steamAppId;
    private AuthTicket hAuthTicket;


    private void Awake()
    {
   
        try
        {
   
            SteamClient.Init(steamAppId);
        }
        catch (System.Exception e)
        {
   
            // Couldn't init for some reason - it's one of these:
            //     Steam is closed?
            //     Can't find steam_api dll?
            //     Don't have permission to play app?
        }
    }


    private void Start()
    {
   
        Connect();
    }


    public void Connect()
    {
   
        string steamAuthSessionTicket = GetSteamAuthTicket(out hAuthTicket);
        PhotonNetwork.AuthValues = new AuthenticationValues();
        PhotonNetwork.AuthValues.UserId = SteamClient.SteamId.ToString();
        PhotonNetwork.AuthValues.AuthType = CustomAuthenticationType.Steam;
        PhotonNetwork.AuthValues.AddAuthParameter("ticket", steamAuthSessionTicket);
        PhotonNetwork.ConnectUsingSettings();
    }


    public override void OnConnectedToMaster()
    {
   
        CancelAuthTicket(hAuthTicket);
    }


    public override void OnCustomAuthenticationFailed(string errorMessage)
    {
   
        CancelAuthTicket(hAuthTicket);
    }


    // ticket should be saved so you can use it to cancel the ticket as soon as you are done with it
    private string GetSteamAuthTicket(out AuthTicket ticket)
    {
   
        ticket = SteamUser.GetAuthSessionTicket();
        StringBuilder ticketString = new StringBuilder();
        for (int i = 0; i < ticket.Data.Length; i++)
        {
   
            ticketString.AppendFormat("{0:x2}", ticket.Data[i]);
        }
        return ticketString.ToString();
    }


    private void CancelAuthTicket(AuthTicket ticket)
    {
   
        if (ticket != null)
        {
   
            ticket.Cancel();
        }
    }


    private void Update()
    {
   
        SteamClient.RunCallbacks();
    }


    private void OnApplicationQuit()
    {
   
        SteamClient.Shutdown();
    }}

OCULUS

转到DashBoard Multiplayer Game Development Made Easy | Photon Engine DashBoard -》MANAGE-》Authentication-》OCULUS

Oculus通过Oculus ID和客户端生成的nonce值去验证身份,nonce是一个只能使用一次的随机数字。 客户端需要登录 Oculus 然后生成一个随机数。此 nonce 证明客户端是有效的 Oculus 用户。

下载 Oculus Platform SDK for Unity 并将其导入您的项目。从编辑器的菜单栏中,转到“Oculus 平台”->“编辑设置”并输入Oculus AppId。使用以下代码获取登录用户的 Oculus ID 并生成随机数:

using UnityEngine;using Oculus.Platform;using Oculus.Platform.Models;


public class OculusAuth : MonoBehaviour{
   
    private string oculusId;


    private void Start()
    {
   
        Core.AsyncInitialize().OnComplete(OnInitializationCallback);
    }


    private void OnInitializationCallback(Message<PlatformInitialize> msg)
    {
   
        if (msg.IsError)
        {
   
            Debug.LogErrorFormat("Oculus: Error during initialization. Error Message: {0}",
                msg.GetError().Message);
        }
        else
        {
   
            Entitlements.IsUserEntitledToApplication().OnComplete(OnIsEntitledCallback);
        }
    }


    private void OnIsEntitledCallback(Message msg)
    {
   
        if (msg.IsError)
        {
   
            Debug.LogErrorFormat("Oculus: Error verifying the user is entitled to the application. Error Message: {0}",
                msg.GetError().Message);
        }
        else
        {
   
            GetLoggedInUser();
        }
    }


    private void GetLoggedInUser()
    {
   
        Users.GetLoggedInUser().OnComplete(OnLoggedInUserCallback);
    }


    private void OnLoggedInUserCallback(Message<User> msg)
    {
   
        if (msg.IsError)
        {
   
            Debug.LogErrorFormat("Oculus: Error getting logged in user. Error Message: {0}",
                msg.GetError().Message);
        }
        else
        {
   
            oculusId = msg.Data.ID.ToString(); // do not use msg.Data.OculusID;
            GetUserProof();
        }
    }


    private void GetUserProof()
    {
   
        Users.GetUserProof().OnComplete(OnUserProofCallback);
    }


    private void OnUserProofCallback(Message<UserProof> msg)
    {
   
        if (msg.IsError)
        {
   
            Debug.LogErrorFormat("Oculus: Error getting user proof. Error Message: {0}",
                msg.GetError().Message);
        }
        else
        {
   
            string oculusNonce = msg.Data.Value;
            // Photon Authentication can be done here
        }
    }}

客户端需要发送 Oculus ID 和生成的 nonce 作为查询字符串参数,分别带有“userid”和“nonce”键:

PhotonNetwork.AuthValues = new AuthenticationValues();
PhotonNetwork.AuthValues.UserId = oculusId;
PhotonNetwork.AuthValues.AuthType = CustomAuthenticationType.Oculus;
PhotonNetwork.AuthValues.AddAuthParameter("userid", oculusId);
PhotonNetwork.AuthValues.AddAuthParameter("nonce", oculusNonce);// do not set AuthValues.Token or authentication will fail// connect

大厅和房间

用户id以及好友

  • 在 Photon 中,玩家使用唯一的 UserID 来识别。UserID区分大小写。具有相同 UserID 的 Photon 客户端可以连接到同一台服务器,但不能从使用相同 UserID 的两个单独客户端加入同一个 Photon 房间。房间内的每个actor都应该有一个唯一的用户 ID。

  • 使用唯一UID的优点:1.可以跨设备保存数据,以及断线重连等;2.便于构建社交系统;3.可以使用另一服务的id(Facebook等)绑定到PhotonUserID上;4.可以通过保留UserID黑名单并使用身份验证来禁止恶意用户的连接;

  • 在验证后,photon client会持有相同的UID直到disconnected;可以通过以下三个方式进行UID设置:1.在client connecting之前使用AuthenticationValues.UserId去设置;2.或者在验证成功后会返回一个UID;3.匿名用户将返回被分配一个随机GUID

  • 玩家可以在房间内相互分享他们的 UserID。在 C# SDK 中,要启用此功能并使 UserID 对所有人可见,请在创建房间时设置RoomOptions.PublishUserIdtrue然后,服务器将在每次新加入时广播此信息,可以使用 Player.UserId 访问每个玩家的用户 ID。

  • 使用 expectedUsers参数为好友保留房间空位slot( Slot Reservation),可以通过 Room.ExpectedUsers去调整房间的预期玩家列表,要启用Slot Reservation需要开启 房间内UID分享

// create room example
PhotonNetwork.CreateRoom(roomName, roomOptions, typedLobby, expectedUsers);// join room example
PhotonNetwork.JoinRoom(roomName, expectedUsers);// join or create room example
PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, typedLobby, expectedUsers);// join random room example
PhotonNetwork.JoinRandomRoom(expectedProperties, maxPlayers, expectedUsers, matchmakingType, typedLobby, sqlLobbyFilter, expectedUsers);
  • 可以用以下实例进行一个队伍的匹配,队伍的房主进行实际匹配,并为所有成员预留位置
//房主
//尝试找到一个随机房间
PhotonNetwork.JoinRandomRoom(expectedProperties, maxPlayers, expectedUsers, matchmakingType, typedLobby, sqlLobbyFilter, expectedUsers);
//如果没有找到,则创建一个新的
PhotonNetwork.CreateRoom(roomName, roomOptions, typedLobby, teamMembersUserIds);

//成员
//成员不必进行任何匹配,而是轮询房主的房间名字
roomNameWhereTheLeaderIs = PhotonNetwork.FindFriends(new string[1]{
    leaderUserId });
//当房主加入一个房间后成员也加入
PhotonNetwork.JoinRoom(roomNameWhereTheLeaderIs);
  • 好友也通过UID判别,只有连接同一个 AppID、同一个 Photon Cloud 区域、同一个 Photon AppVersion 的朋友,无论使用什么设备或平台,都能找到彼此。通过以下代码更新好友列表。 Photon 不会保留朋友列表。 游戏中的任何非现有用户都将被视为离线。只有当他/她在进行 FindFriends 查询并 连接到 Photon 时,他/她才被认为是在线的。如果用户在线并加入同名房间,则每个用户将返回一个房间名称。
using System.Collections.Generic;using UnityEngine;using Photon.Realtime;using Photon.Pun;

public class FindFriendsExample : MonoBehaviourPunCallbacks{
   
    public bool FindFriends(string[] friendsUserIds)
    {
   
        return PhotonNetwork.FindFriends(friendsUserIds);
    }


    public override void OnFriendListUpdate(List<FriendInfo> friendsInfo)
    {
   
        for(int i=0; i < friendsInfo.Count; i++)
        {
   
            FriendInfo friend = friendsInfo[i];
            Debug.LogFormat("{0}", friend);
        }
    }}

匹配指导

使用 Photon 进入房间与某人一起玩(或对抗)非常容易。基本上有三种方法:告诉服务器找到匹配的房间,跟随朋友进入她的房间,或者获取房间列表让用户选择一个。对于大多数游戏来说,最好使用快速简单的匹配,建议使用随机匹配,并可能使用技能、等级等过滤器。

常见问题:

  • 验证AppId在所有客户端中使用相同。
  • 验证客户端是否连接到相同的Region. 无论他们使用什么设备或平台,只有连接到同一区域的玩家才能互相玩。
  • 确认所有客户端中使用相同的AppVersion。
  • 验证玩家是否有不同的唯一用户UID。具有相同 UserID 的玩家不能加入同一个房间。
  • 在尝试按名称加入房间之前,请确保已创建此房间。或者使用JoinOrCreateRoom.
  • 如果您尝试加入随机房间,请确保选择创建时使用的相同大厅(名称和类型)。
  • 如果您使用房间属性作为过滤器条件进行随机匹配,请确保在创建房间时将这些属性的键设置为从大厅可见。
  • 如果您使用 SQL 过滤器进行随机匹配,请确保将保留的过滤属性键设置为从大厅可见。每次随机匹配尝试时放松 SQL 过滤器或使用链式过滤器或在多次尝试失败后的某个时间点创建新房间也很重要。
  • 如果您正在实现异步匹配,请确保使用配置正确的 webhook(启用“AsyncJoin”)或使用 AsyncRandomLobby。

快速匹配

只需调用JoinRandomOrCreateRoom如果找到一个房间,它将被加入,否则将创建一个新房间。

using Photon.Pun;
using Photon.Realtime;

public class QuickMatchExample : MonoBehaviourPunCallbacks
{
   
    private void QuickMatch()
    {
   
        PhotonNetwork.JoinRandomOrCreateRoom();
    }

    public override void OnJoinedRoom()
    {
   
        // joined a room successfully
    }
}

如果JoinRandomRoom不能立即找到房间,则创建一个。如果从不显示房间名称(以及为什么要显示),请不要自定义名字。让服务器执行此操作。创建房间时将空字符串设置为“房间名称”。房间有一个独特的 GUID。为“最大玩家数”应用一个值。这样,当房间已满时,服务器最终会停止添加玩家。开始游戏后为阻止新玩家加入,请关闭房间,服务器会停止玩家加入。

using Photon.Pun;
using Photon.Realtime;

public class QuickMatchExample : MonoBehaviourPunCallbacks
{
   
    [SerializeField]
    private maxPlayers = 4;

    private void CreateRoom()
    {
   
        RoomOptions roomOptions = 
### 关于 Photon Unity Networking 2 (PUN 2) 的游戏开发教程 Photon Unity Networking 2 (PUN 2) 是一款用于实现多人在线功能的强大工具,广泛应用于基于 Unity 开发的游戏项目中。以下是关于 PUN 2 的一些核心概念及其应用: #### 创建房间逻辑 当尝试加入随机房间失败时,可以创建一个新的房间,并设置最大玩家数量。此过程可以通过 `PhotonNetwork.CreateRoom()` 方法完成[^2]。具体代码示例如下: ```csharp if (!PhotonNetwork.InRoom) { // Attempt to join a random room. PhotonNetwork.JoinRandomRoom(); } else { // Failed to join a random room; create one instead. PhotonNetwork.CreateRoom(null, new ExitGames.Client.Photon.RoomOptions { MaxPlayers = maxPlayersPerRoom }); } ``` #### 自定义 Inspector 面板 为了增强用户体验,在调整团队管理器的功能时,可能需要自定义 Inspector 面板。虽然具体的代码未提供,但可以根据需求扩展 `PhotonTeamManager` 类的相关属性方法[^4]。 #### 实现回调接口 通过继承 `IPunCallbacks` 接口,开发者可以在特定事件发生时执行相应的操作。例如,处理连接状态变化或同步数据更新等场景[^5]。以下是一个简单的例子: ```csharp public class MyPunBehaviour : PunBehaviour, IPunObservable { public void OnPhotonSerializeView(ExitGames.Client.Photon.Stream stream, ExitGames.Client.Photon.SendOptions info) { if (stream.IsWriting) { // Write data when sending it over the network. stream.Serialize(myData); } else { // Read incoming data from other players. myData = stream.Deserialize<MyDataType>(); } } } ``` #### 官方文档与资源推荐 官方提供了详尽的文档支持,涵盖了从基础到高级的各种主题。建议访问 [Photon Unity Documentation](https://doc.photonengine.com/) 获取最新版本的信息。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值