> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Photon 快速入门

> 在 Unity 项目中通过 CloudScript 回调将 Photon Realtime 与 PUN 多人游戏功能与 PlayFab 连接,以对玩家进行身份验证并接收房间事件。

本节介绍如何将 PlayFab 与 Photon 多人游戏服务(如 Photon RealTime 和 Photon Unity Networking (PUN))一起使用。

目前,我们提供以下 Photon 集成:

* 使用 PlayFab 对 Photon 玩家进行身份验证。
* 使用 PlayFab CloudScript 回调侦听房间事件。

## Photon 快速入门

本快速入门介绍如何设置 Photon Multiplayer 与 PlayFab 协同工作。目前,PlayFab 提供以下 Photon 集成:

* 使用 PlayFab 对 Photon 玩家进行身份验证。
* 使用 PlayFab CloudScript 回调侦听房间事件。

在本快速入门中,我们使用最新的 [Photon Unity 示例项目](https://assetstore.unity.com/packages/tools/network/photon-unity-networking-classic-free-1786)演示这两项功能的工作方式。

<Note>
  有关 Unity 上完全受支持的 PlayFab 多人游戏和聊天体验,请参阅 [PlayFab Party Unity 插件](/services/playfab/multiplayer/networking/party-unity-overview)获取更多信息。
</Note>

## 先决条件

1. 一个已导入 PlayFab SDK 并已配置游戏的 [Unity](/services/playfab/sdks/unity3d/quickstart) 项目。
2. 已注册的 PlayFab 游戏。

<Note>
  本指南省略了 Chat 应用程序的设置。若要集成 Chat 应用程序,请创建一个新的 Photon Chat 应用程序,并在 Unity 中以及 PlayFab Game Manager - Photon Add-on 页面中相应的框中输入 App ID。
</Note>

## 设置 Photon 应用程序

1. 注册并登录到 Photon 仪表板后,选择 **Create a new App**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-create-app.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=0ec179aacdb319ddcdc3c916895a6286" alt="创建一个新的 Photon 应用程序" width="1032" height="379" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-create-app.png" />

2. 选择 **Photon Type**。
3. 选择 **Name**。
4. 创建 **Description**。
5. 通过选择 **Create** 提交。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-new-app-form.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=2730b10819275943ef4229748f42e0a3" alt="新应用程序表单" width="1054" height="654" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-new-app-form.png" />

## 设置身份验证

1. 在列表中找到新创建的应用程序,然后选择 **Manage**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-manage-app.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=5bae96fef52d530d2cf791ce7a9dc4b0" alt="管理测试应用程序" width="1031" height="425" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-manage-app.png" />

2. 在 **Application** 仪表板中,找到并保存位于页面 **Title** 下的 **App ID**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-save-app-id.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=f77fddbe663fb144bb03fac060d0d654" alt="保存 App ID" width="529" height="144" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-save-app-id.png" />

3. 找到 **Authentication** 部分。
4. 选择 **Custom Server**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-custom-server-authentication.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=3ea9406ca7be43dae469ffb7a926410d" alt="自定义服务器身份验证" width="995" height="366" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-custom-server-authentication.png" />

下一页允许你为 Photon 配置身份验证 URL。

```html theme={null}
https://{PlayFabTitleId}.playfabapi.com/photon/authenticate

```

将 `PlayFabTitleId` 占位符替换为你自己的 Title Id。

```html theme={null}
{PlayFabTitleId}
```

5. 输入 **URL**。
6. 通过选择 **Create** 按钮提交。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-enter-authentication-url.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=25b6eedc308ae2bbd7fb0d3eab6120a5" alt="输入身份验证 URL" width="1010" height="475" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-enter-authentication-url.png" />

## 为 Photon 设置 PlayFab 游戏

导航到游戏的 **Game Manager** 页面,然后:

1. 导航到 **Add-ons** 选项卡。
2. 在 **Add-ons** 列表中,找到并选择 **Photon**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-game-manager-addons-tab.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=4583dedfa9a2b8fe50c9a5470f195328" alt="Game Manager Add-ons 选项卡" width="2560" height="1200" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-game-manager-addons-tab.png" />

3. **Photon Add-on** 页面允许你通过指定 **Photon Realtime Application ID** 来安装 Photon。
   * 或者,你也可以指定 **Chat App ID**。
   * 安装 **Add-on (3)** 后,会生成 **Photon secret Key** **(2)**。
   * 保存它以便将来配置 Webhook 时使用。

<Note>
  请记得将 Photon secret key 保存在安全且易于访问的位置。它在配置 Webhook 时会派上用场。
</Note>

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-copy-realtime-app-id.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=55ebd0eabb1e55905ae85d3c7a722b1a" alt="复制 Realtime App ID" width="483" height="472" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-copy-realtime-app-id.png" />

## 设置 Photon Webhook

作为可选集成,Photon 可以将某些事件传输到你的 PlayFab CloudScript。这是通过 Webhook 完成的:

* 你指定一个 Base URL。
* 然后为每个可用事件指定路径。
* 实际上,路径就是你 CloudScript 中实现的处理程序名称。

Base URL 具有以下格式:

```html theme={null}
https://{PlayFabTitleId}.playfablogic.com/webhook/1/prod/{PhotonSecretKey}
```

请务必将 `Title Id` 占位符替换为你自己的 Title Id。

```html theme={null}
{PlayFabTitleId}
```

请务必将 `PhotonSecretKey` 令牌替换为你在 **Photon Add-on** 页面上生成的密钥。

```html theme={null}
{PhotonSecretKey}
```

1. 填写 **Base URL**。
2. 为每个可用的 **Events** 指定一个 **CloudScript** 处理程序 **Name**。
3. 移除所有 **CustomHttpHeaders**。
4. 通过选择 **Save** 按钮提交。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-assign-cloud-script-event-handlers.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=fe85d7c3f761456f792bbd26aaa244e2" alt="指定 CloudScript 事件处理程序" width="1195" height="776" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-assign-cloud-script-event-handlers.png" />

## 设置 Unity 项目

本指南假设你已导入 PlayFab SDK、完成设置,并配置了游戏设置。

* 开始时导航到 **Asset Store**。
* 找到 [Photon Unity Networking Free 包](https://assetstore.unity.com/packages/tools/network/photon-unity-networking-classic-free-1786)。
* 选择 **Import (1)**。
* 你可以检查导入的文件。
* 然后选择 **Import (2)**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-import-photon-unity.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=0ba88fc1d3ae31903a01e8903734a0d3" alt="导入 Photon Unity" width="900" height="773" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-import-photon-unity.png" />

导入过程完成后,**PUN Setup** 窗口会打开。

* 输入你为所创建的 **Photon App** 在 **Photon Application Dashboard** 中找到的 **Realtime Application ID (1)**。
* 选择 **Setup Project (2)**。
* 设置完成后,选择 **Close (3)**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-pun-setup.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=4bb95f4e4fba469eceab50bf7dc3958e" alt="PUN 设置" width="881" height="717" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-pun-setup.png" />

如果 **AppId** 被接受,**Photon Server Settings** 对象将被选中并可在 **Unity Inspector** 窗口中查看。若要手动访问 **Server Settings** 对象:

* 导航到顶部窗口面板。
* 选择 **Window**。
* 然后选择 **Photon Unity Networking (1)**。
* 最后选择 **Highlight Server Settings (2)**。
* **Standard Unity** 项目窗口会显示 **PhotonServerSettings (3)** 对象。
* 选择该对象以在 **Unity Inspector** 窗口中查看其设置。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-server-settings.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=f30bd8b789ec35b930a7c49ffe397ce7" alt="Photon 服务器设置" width="747" height="567" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-server-settings.png" />

<Note>
  作为 **PhotonServerSettings** 的一部分,你可以选择指定 **Chat Application ID**。
</Note>

此时,项目已配置完毕。

## 实现从 PlayFab 到 Photon 的身份验证

当你在 PlayFab Game Manager 中为游戏启用 **Photon Add-on**,并在 **Photon Application** 仪表板上配置身份验证时,PlayFab 允许你在 Photon 环境中对现有的 PlayFab 玩家进行身份验证。

这意味着你可以完全避免为 Photon 实现任何用户管理。

1. 从 **Photon Unity Networking Free** 包(**Classic** 版本)导航到 **DemoHub-Scene-V2** 场景。
   * 此场景是包中所有示例的中心。
   * 因为它是要加载的*第一个*场景,让我们添加一个小脚本来启用使用 Photon 对 PlayFab 进行身份验证。
2. 创建一个名为 **PlayFabAuthenticator** 的空 **GameObject**。
3. 然后在 **Unity Inspector** 窗口中,向此 **GameObject** 添加一个同名组件。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-add-authentication-component.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=df2cb2df38dbf10957a1f46aefcc3dbf" alt="添加身份验证组件" width="731" height="263" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-add-authentication-component.png" />

选择齿轮图标,然后从下拉菜单中选择编辑脚本。复制并粘贴 `PlayFabAuthenticator` 组件的代码,然后保存并关闭文件。

```csharp theme={null}
using PlayFab;
using PlayFab.ClientModels;
using UnityEngine;

public class PlayFabAuthenticator : MonoBehaviour {

    private string _playFabPlayerIdCache;

    //Run the entire thing on awake
    public void Awake() {
        AuthenticateWithPlayFab();
    }

    /*
     * Step 1
     * We authenticate a current PlayFab user normally.
     * In this case we use the LoginWithCustomID API call for simplicity.
     * You can absolutely use any Login method you want.
     * We use PlayFabSettings.DeviceUniqueIdentifier as our custom ID.
     * We pass RequestPhotonToken as a callback to be our next step, if
     * authentication was successful.
     */
    private void AuthenticateWithPlayFab(){
        LogMessage("PlayFab authenticating using Custom ID...");

        PlayFabClientAPI.LoginWithCustomID(new LoginWithCustomIDRequest()
        {
            CreateAccount = true,
            CustomId = PlayFabSettings.DeviceUniqueIdentifier
        }, RequestPhotonToken, OnPlayFabError);
    }

    /*
    * Step 2
    * We request a Photon authentication token from PlayFab.
    * This is a crucial step, because Photon uses different authentication tokens
    * than PlayFab. Thus, you cannot directly use PlayFab SessionTicket and
    * you need to explicitly request a token. This API call requires you to
    * pass a Photon App ID. The App ID may be hard coded, but in this example,
    * we are accessing it using convenient static field on PhotonNetwork class.
    * We pass in AuthenticateWithPhoton as a callback to be our next step, if
    * we have acquired the token successfully.
    */
    private void RequestPhotonToken(LoginResult obj) {
        LogMessage("PlayFab authenticated. Requesting photon token...");

        //We can player PlayFabId. This will come in handy during next step
        _playFabPlayerIdCache = obj.PlayFabId;

        PlayFabClientAPI.GetPhotonAuthenticationToken(new GetPhotonAuthenticationTokenRequest()
        {
            PhotonApplicationId = PhotonNetwork.PhotonServerSettings.AppSettings.AppIdRealtime
        }, AuthenticateWithPhoton, OnPlayFabError);
    }

    /*
     * Step 3
     * This is the final and the simplest step. We create a new AuthenticationValues instance.
     * This class describes how to authenticate a player inside the Photon environment.
     */
    private void AuthenticateWithPhoton(GetPhotonAuthenticationTokenResult obj) {
        LogMessage("Photon token acquired: " + obj.PhotonCustomAuthenticationToken + "  Authentication complete.");

        //We set AuthType to custom, meaning we bring our own, PlayFab authentication procedure.
        var customAuth = new Photon.Realtime.AuthenticationValues { AuthType = CustomAuthenticationType.Custom };

        //We add "username" parameter. Do not let it confuse you: PlayFab is expecting this parameter to contain player PlayFab ID (!) and not username.
        customAuth.AddAuthParameter("username", _playFabPlayerIdCache);    // expected by PlayFab custom auth service

        //We add "token" parameter. PlayFab expects it to contain Photon Authentication Token issues to your during previous step.
        customAuth.AddAuthParameter("token", obj.PhotonCustomAuthenticationToken);

        //We finally tell Photon to use this authentication parameters throughout the entire application.
        PhotonNetwork.AuthValues = customAuth;
    }

    private void OnPlayFabError(PlayFabError obj) {
        LogMessage(obj.GenerateErrorReport());
    }

    public void LogMessage(string message) {
        Debug.Log("PlayFab + Photon Example: " + message);
    }
}
```

## 测试从 PlayFab 到 Photon 的身份验证

1. 启动场景。
2. 检查控制台消息的流。
   控制并确保:
3. 到 PlayFab 本身的身份验证成功。
4. 已获取 Photon 令牌。

下图说明了正确的流程。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-start-the-scene.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=25a5d78dd81e5bfc218e26fdb8d88deb" alt="启动场景" width="635" height="505" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-start-the-scene.png" />

获取 Photon 令牌并完成身份验证后,我们就可以查看 Photon 演示了。

例如,启动名为 **Demo Boxes** 的演示。

5. 若要确保 PlayFab 身份验证完成,请选择 **Demo Boxes**。
6. 然后如下图所示选择 **Load Demo**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-load-demo.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=258f887d27cf433edf0aa94e85226889" alt="加载演示" width="943" height="364" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-load-demo.png" />

Photon 开始输出调试消息。通过监视你的控制台,可以轻松发现是否有任何身份验证问题。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-debug-console.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=c68b25c7affa4e81024b84b7616d30c1" alt="Photon 调试控制台" width="738" height="959" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-debug-console.png" />

确保控制台中没有 **Authentication Denied** 错误。此时,你已完成最低限度的 PlayFab 和 Photon 集成设置。

## Photon 房间事件 + CloudScript

Photon 匹配系统有一个 *Room*(房间)的概念。如果你不熟悉此概念,请参阅 [Photon 匹配指南](https://doc.photonengine.com/realtime/current/lobby-and-matchmaking/matchmaking-and-lobby)。

PlayFab 允许你拦截各种房间事件。以下房间事件仅需 CloudScript 即可拦截:

* `RoomCreated`
* `RoomJoined`
* `RoomLeft`
* `RoomClosed`

以下事件需要对 Unity 代码进行额外控制才能拦截(细节将在本文档稍后给出):

* `RoomPropertyUpdated`
* `RoomEventRaised`

<Note>
  一旦你为某个房间事件引入了处理程序,它就成为事件处理流程的重要组成部分。因此,你的 CloudScript 在运行时产生的错误可能会对*整个*系统造成问题。例如,如果 `RoomCreated` 处理程序抛出错误,你的客户端也会抛出错误,并且将无法正确连接。
</Note>

让我们通过为每种事件类型单独定义处理程序来构建一个 PlayFab CloudScript。

### Room Created 处理程序

每次创建 Photon 房间时都会调用 `RoomCreated` 处理程序。以下 CloudScript 处理程序拦截此类事件。

```javascript theme={null}
// Triggered automatically when a Photon room is first created
handlers.RoomCreated = function (args) {
    return { ResultCode : 0, Message: 'Success' };
};
```

<Note>
  你可以使用 “args” 参数获取有关该事件的更多数据。
</Note>

```json theme={null}
{
  "ActorNr": 1,
  "AppVersion": "1.2_1.85",
  "AppId": "bfd5f98b-c6a4-4763-80d9-824d20db842b",
  // Options with which the room was created.
  "CreateOptions": {
    "MaxPlayers": 4,
    "LobbyId": null,
    "LobbyType": 0,
    "CustomProperties": {

    },
    "EmptyRoomTTL": 0,
    "PlayerTTL": 0,
    "CheckUserOnJoin": false,
    "DeleteCacheOnLeave": true,
    "SuppressRoomEvents": false,
    "PublishUserId": false,
    "ExpectedUsers": null
  },
  // Unique game identifier
  "GameId": "8b8322de-096d-4481-a2b2-8db8bb45cfef",
  "Region": "EU",
  "Type": "Create",
  // User that caused the room to be created
  "UserId": "834D5AA5BAB1DFB6",
  "Username": ""
}
```

### Room Joined 处理程序

每次有玩家加入房间时都会调用 `RoomJoined` 处理程序。以下 CloudScript 处理程序拦截此类事件。

```javascript theme={null}
// Triggered automatically when a player joins a Photon room
handlers.RoomJoined = function (args) {
    return { ResultCode : 0, Message: 'Success' };
};
```

对于第一个进入房间的用户,不会调用此回调。请使用 `RoomCreated` 来拦截第一个加入的玩家。你可以使用 “args” 参数获取有关该事件的更多数据。

```json theme={null}
{
  "ActorNr": 2,
  "AppVersion": "1.2_1.85",
  "AppId": "bfd5f98b-c6a4-4763-80d9-824d20db842b",
  // Unique Game Identifier
  "GameId": "b0f55a2e-431d-402a-9809-b0240443267e",
  "Region": "EU",
  "Type": "Join",
  // Id of the player that has joined
  "UserId": "AAC7634BF46289DF",
  "Username": ""
}
```

### Room left

每次有玩家离开房间时都会调用 `RoomLeft` 处理程序。以下 CloudScript 处理程序拦截此类事件。

```javascript theme={null}
// Triggered automatically when a player leaves a Photon room
handlers.RoomLeft = function (args) {
    return { ResultCode : 0, Message: 'Success' };
};
```

你可以使用 “args” 参数获取有关该事件的更多数据。

```json theme={null}
{
  "ActorNr": 2,
  "AppVersion": "1.2_1.85",
  "AppId": "bfd5f98b-c6a4-4763-80d9-824d20db842b",
  // Unique Game Identifier
  "GameId": "b0f55a2e-431d-402a-9809-b0240443267e",
  "IsInactive": false,
  "Reason": "0",
  "Region": "EU",
  "Type": "ClientDisconnect",
  // Id of the user that has left the room
  "UserId": "AAC7634BF46289DF",
  "Username": ""
}
```

### Room Closed 处理程序

每次最后一位玩家离开房间导致房间没有玩家时,都会调用 `RoomClosed` 处理程序。以下 CloudScript 处理程序拦截此类事件。

```javascript theme={null}
// Triggered automatically when a Photon room closes
// Note: currentPlayerId is undefined in this function
handlers.RoomClosed = function (args) {
    return { ResultCode : 0, Message: 'Success' };
};
```

你可以使用 “args” 参数获取有关该事件的更多数据。

```json theme={null}
  "ActorCount": 0,
  "ActorNr": 1,
  "AppVersion": "1.2_1.85",
  "AppId": "bfd5f98b-c6a4-4763-80d9-824d20db842b",
  // Unique game identifier
  "GameId": "b0f55a2e-431d-402a-9809-b0240443267e",
  "Region": "EU",
  "State2": {
    "ActorList": [

    ]
  },
  "Type": "Close"
}
```

### Room Property Updated

每次房间属性发生更改时都会调用 `RoomPropertyUpdated` 处理程序。以下 CloudScript 处理程序拦截此类事件。

```javascript theme={null}
// Triggered automatically when a Photon room game property is updated.
// Note: currentPlayerId is undefined in this function
handlers.RoomPropertyUpdated = function (args) {
    return { ResultCode : 0, Message: 'Success' };
};
```

<Note>
  此处理程序中的 `currentPlayerId` 未定义。如果房间属性是从客户端更改的,你可以使用 “args” 参数,并参考 UserId 来获取负责此操作的玩家。
</Note>

你可以使用 “args” 参数获取有关该事件的更多数据。

```json theme={null}
{
  "ActorNr": 1,
  "AppVersion": "1.2_1.85",
  "AppId": "bfd5f98b-c6a4-4763-80d9-824d20db842b",
  // Custom Room Properties
  "Properties": {
    "CustomProperty": "It's Value"
  },
  "GameId": "8b8322de-096d-4481-a2b2-8db8bb45cfef",
  "Region": "EU",
  "State": {
    "ActorCounter": 1,
    "ActorList": [
      {
        "ActorNr": 1,
        "UserId": "834D5AA5BAB1DFB6",
        "Username": "",
        "IsActive": true,
        "Binary": "RGIAAAEBRAAAAAFi\/3MAAA==",
        "DEBUG_BINARY": {
          "1": {
            "255": ""
          }
        }
      }
    ],
    "Binary": {
      "18": "RAAAAANi+nkAAHNzAA1QbGF5ZXJJbmRleGVzRGlpAAEAAAABAAAAAHMADkN1c3RvbVByb3BlcnR5cwAKSXQncyBWYWx1ZQ=="
    },
    "CheckUserOnJoin": false,
    "CustomProperties": {

    },
    "DeleteCacheOnLeave": true,
    "EmptyRoomTTL": 0,
    "IsOpen": true,
    "IsVisible": true,
    "LobbyType": 0,
    "LobbyProperties": [

    ],
    "MaxPlayers": 4,
    "PlayerTTL": 0,
    "SuppressRoomEvents": false,
    "Slice": 0,
    "DebugInfo": {
      "DEBUG_PROPERTIES_18": {
        "250": [

        ],
        "PlayerIndexes": {
          "1": 0
        },
        "CustomProperty": "It's Value"
      }
    },
    "ExcludedActors": [

    ],
    "PublishUserId": false,
    "ExpectedUsers": [

    ]
  },
  "Type": "Game",
  "UserId": "834D5AA5BAB1DFB6",
  "Username": ""
}
```

在使用 Unity Photon 客户端更改自定义房间属性时,*重要的是标记该调用*,以便将事件传递给 Webhook(在此为 PlayFab)。

```csharp theme={null}
// Properties updates ( this hashtable contains the properties to be changed. Properties not mentioned here will stay as is
var properties = new ExitGames.Client.Photon.Hashtable() { { "CustomProperty" , "It's Value" } };

// Control set. Empty in this case, because our property has not existed before.
// Otherwise you would include previous value of the property.
var expectedProperties = new ExitGames.Client.Photon.Hashtable();

// Set Custom Properties call. Notice the last argument set to "true"
// This tells Photon to forward event to the webhook.
PhotonNetwork.room.SetCustomProperties(properties, expectedProperties, true);
```

### Room Event Raised

每次触发自定义房间事件时都会调用 **Room Event Raised**。以下 **CloudScript** 处理程序拦截此类事件。

```javascript theme={null}
// Triggered by calling "OpRaiseEvent" on the Photon client. The "args.Data" property is
// set to the value of the "customEventContent" HashTable parameter, so you can use
// it to pass in arbitrary data.
handlers.RoomEventRaised= function (args) {
    return { ResultCode : 0, Message: 'Success' };
};
```

你可以使用 “args” 参数获取有关该事件的更多数据。

```json theme={null}
{
  "ActorNr": 1,
  "AppVersion": "1.2_1.85",
  "AppId": "bfd5f98b-c6a4-4763-80d9-824d20db842b",
  // Custom event data
  "Data": {
    "Hello": "World"
  },
  "GameId": "8b8322de-096d-4481-a2b2-8db8bb45cfef",
  "Region": "EU",
  "State": {
    "ActorCounter": 1,
    "ActorList": [
      {
        "ActorNr": 1,
        "UserId": "834D5AA5BAB1DFB6",
        "Username": "",
        "IsActive": true,
        "Binary": "RGIAAAEBRAAAAAFi\/3MAAA==",
        "DEBUG_BINARY": {
          "1": {
            "255": ""
          }
        }
      }
    ],
    "Binary": {
      "18": "RAAAAAJi+nkAAHNzAA1QbGF5ZXJJbmRleGVzRGlpAAEAAAABAAAAAA=="
    },
    "CheckUserOnJoin": false,
    "CustomProperties": {

    },
    "DeleteCacheOnLeave": true,
    "EmptyRoomTTL": 0,
    "IsOpen": true,
    "IsVisible": true,
    "LobbyType": 0,
    "LobbyProperties": [

    ],
    "MaxPlayers": 4,
    "PlayerTTL": 0,
    "SuppressRoomEvents": false,
    "Slice": 0,
    "DebugInfo": {
      "DEBUG_PROPERTIES_18": {
        "250": [

        ],
        "PlayerIndexes": {
          "1": 0
        }
      }
    },
    "ExcludedActors": [

    ],
    "PublishUserId": false,
    "ExpectedUsers": [

    ]
  },
  "Type": "Event",
  // PlayFab User Id if he/she was the source of the event
  "UserId": "834D5AA5BAB1DFB6",
  "Username": "",
  // Event code
  "EvCode": 15
}
```

在使用 Unity Photon 客户端触发自定义房间事件时,标记该调用非常重要,以便将事件传递给 Webhook(在此为 PlayFab)。

```csharp theme={null}
var data = new Dictionary<string,object>() {
    { "Hello" , "World" }
};

// Raise custom room event
// Replace 15 with any custom event code of your choice [0..299]
PhotonNetwork.RaiseEvent(15, data, true, new RaiseEventOptions() {
    ForwardToWebhook = true, // Mark call to pass event to Webhook (PlayFab in this case)
});
```

## 测试 Room Event 处理程序

此示例扩展了前面的示例(PlayFab + Photon 身份验证)。

为你的游戏上传以下 CloudScript。

```javascript theme={null}
handlers.RoomCreated = function (args) {
    server.WriteTitleEvent({
        EventName : "room_created"
    });
    return { ResultCode : 0, Message: 'Success' };
};

handlers.RoomJoined = function (args) {
    server.WriteTitleEvent({
        EventName : "room_joined"
    });
    return { ResultCode : 0, Message: 'Success' };
};

handlers.RoomLeft = function (args) {
    server.WriteTitleEvent({
        EventName : "room_left"
    });
    return { ResultCode : 0, Message: 'Success' };
};

handlers.RoomClosed = function (args) {
    server.WriteTitleEvent({
        EventName : "room_closed"
    });
    return { ResultCode : 0, Message: 'Success' };
};

handlers.RoomPropertyUpdated = function (args) {
    server.WriteTitleEvent({
        EventName : "room_property_changed"
    });
    return { ResultCode : 0, Message: 'Success' };
};

handlers.RoomEventRaised = function (args) {
    server.WriteTitleEvent({
        EventName : "room_event_raised"
    });
    return { ResultCode : 0, Message: 'Success' };
};
```

这段代码所做的仅仅是在每次调用 Photon 回调时发布一个新的游戏事件。在生产环境中虽然没有用,但此示例让我们能够清楚地看到回调是如何被调用的。

通过包含新的示例代码来扩展 `PlayFabAuthenticator` 脚本,该示例代码会触发自定义事件并设置自定义房间属性。扩展版本还使用 `Awake` 方法,以便在场景加载之间不销毁对象。

```csharp theme={null}
using System.Collections.Generic;
using PlayFab;
using PlayFab.ClientModels;
using UnityEngine;

public class PlayFabAuthenticator : MonoBehaviour {

    private string _playFabPlayerIdCache;

    //Run the entire thing on awake
    public void Awake() {
        AuthenticateWithPlayFab();
        DontDestroyOnLoad(gameObject);
    }

    /*
     * Step 1
     * We authenticate a current PlayFab user normally.
     * In this case we use the LoginWithCustomID API call for simplicity.
     * You can absolutely use any Login method you want.
     * We use PlayFabSettings.DeviceUniqueIdentifier as our custom ID.
     * We pass RequestPhotonToken as a callback to be our next step, if
     * authentication was successful.
     */
    private void AuthenticateWithPlayFab()  {
        LogMessage("PlayFab authenticating using Custom ID...");

        PlayFabClientAPI.LoginWithCustomID(new LoginWithCustomIDRequest()
        {
            CreateAccount = true,
            CustomId = PlayFabSettings.DeviceUniqueIdentifier+"EDITOR"
        }, RequestPhotonToken, OnPlayFabError);
    }

    /*
    * Step 2
    * We request a Photon authentication token from PlayFab.
    * This is a crucial step, because Photon uses different authentication tokens
    * than PlayFab. Thus, you cannot directly use PlayFab SessionTicket and
    * you need to explicitly request a token. This API call requires you to
    * pass a Photon App ID. The App ID may be hard coded, but in this example,
    * we are accessing it using convenient static field on PhotonNetwork class.
    * We pass in AuthenticateWithPhoton as a callback to be our next step, if
    * we have acquired the token successfully.
    */
    private void RequestPhotonToken(LoginResult obj) {
        LogMessage("PlayFab authenticated. Requesting photon token...");

        //We can player PlayFabId. This will come in handy during next step
        _playFabPlayerIdCache = obj.PlayFabId;

        PlayFabClientAPI.GetPhotonAuthenticationToken(new GetPhotonAuthenticationTokenRequest()
        {
            PhotonApplicationId = PhotonNetwork.PhotonServerSettings.AppSettings.AppIdRealtime
        }, AuthenticateWithPhoton, OnPlayFabError);
    }

    /*
     * Step 3
     * This is the final and the simplest step. We create a new AuthenticationValues instance.
     * This class describes how to authenticate a player inside the Photon environment.
     */
    private void AuthenticateWithPhoton(GetPhotonAuthenticationTokenResult obj) {
        LogMessage("Photon token acquired: " + obj.PhotonCustomAuthenticationToken + "  Authentication complete.");

        //We set AuthType to custom, meaning we bring our own, PlayFab authentication procedure.
        var customAuth = new Photon.Realtime.AuthenticationValues { AuthType = CustomAuthenticationType.Custom };

        //We add "username" parameter. Do not let it confuse you: PlayFab is expecting this parameter to contain player PlayFab ID (!) and not username.
        customAuth.AddAuthParameter("username", _playFabPlayerIdCache);    // expected by PlayFab custom auth service

        //We add "token" parameter. PlayFab expects it to contain Photon Authentication Token issues to your during previous step.
        customAuth.AddAuthParameter("token", obj.PhotonCustomAuthenticationToken);

        //We finally tell Photon to use this authentication parameters throughout the entire application.
        PhotonNetwork.AuthValues = customAuth;
    }

    private void OnPlayFabError(PlayFabError obj) {
        LogMessage(obj.ErrorMessage);
    }

    public void LogMessage(string message) {
        Debug.Log("PlayFab + Photon Example: " + message);
    }

    // Add small button to launch our example code
    public void OnGUI() {
        if (GUILayout.Button("Execute Example ")) ExecuteExample();
    }

    // Example code which raises custom room event, then sets custom room property
    private void ExecuteExample() {

        // Raise custom room event
        var data = new Dictionary<string, object>() { {"Hello","World"} };
        var result = PhotonNetwork.RaiseEvent(15, data, true, new RaiseEventOptions()
        {
            ForwardToWebhook = true,
        });
        LogMessage("New Room Event Post: "+result);

        // Set custom room property
        var properties = new ExitGames.Client.Photon.Hashtable() { { "CustomProperty", "It's Value" } };
        var expectedProperties = new ExitGames.Client.Photon.Hashtable();
        PhotonNetwork.room.SetCustomProperties(properties, expectedProperties, true);
        LogMessage("New Room Properties Set");
    }

}
```

* 运行 hub 场景并等待 PlayFab 身份验证完成 **(1)**。
* 然后加载 **Boxes Demo** 场景 **(2)**。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-load-demo-boxes-scene.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=8afc52b7f693bf5a99dbca3b75f8afeb" alt="加载 Demo Boxes 场景" width="893" height="820" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-load-demo-boxes-scene.png" />

* 场景加载完成后,等待对等方连接到新创建的房间 **(1)**。
* 然后在左上角选择 **Execute Example** **(2)**。
* 观察控制台输出 **(3)**。
* 确保没有发生错误。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-execute-example.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=f9eecb8d73012c83679a60318858de79" alt="Execute Example" width="893" height="778" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-execute-example.png" />

不要忘记停止 Unity 的运行。这是为了确保我们也能收到 **RoomLeft** 和 **RoomClosed** 事件。

导航到游戏的 Game Manager 页面,并查看 **PlayStream** 面板。你应该能够看到我们的 CloudScript 代码在处理 Photon 事件时生成的事件。

1. 最初,我们的 Photon 实例没有打开的房间。当我们启动示例时,Photon 为 Boxes Demo 创建了房间。
2. 第一个加入的玩家就是请求创建房间的玩家。因此没有记录 **RoomJoined Event**。然后我们执行了示例代码:
   * 首先,我们触发了一个自定义 **Room Event**。
   * 然后,我们设置了一个自定义 **Room Property**。
   * 然后我们停止了 Unity 播放模式。这导致我们的客户端离开了房间。
3. 由于我们断开连接的客户端是最后一个,没有更多客户端,Photon 关闭了房间。

所有事件都应该记录到 PlayStream 事件流中,如下图所示。

<img src="https://mintcdn.com/microsoft-4404708b/dqv53299jA1M-fNi/images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-playstream-event-flow.png?fit=max&auto=format&n=dqv53299jA1M-fNi&q=85&s=7023a3734cea23eaf1664528bdb20404" alt="PlayStream 事件流" width="408" height="333" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-playstream-event-flow.png" />

此时,你已将 Photon 事件支持完全集成到你的 PlayFab 游戏中。


## Related topics

- [快速入门](/zh-CN/services/playfab/economy-monetization/economy-v2/quickstart.md)
- [Android 快速入门](/zh-CN/services/playfab/sdks/c/quickstart-android.md)
- [iOS 快速入门](/zh-CN/services/playfab/sdks/c/quickstart-ios.md)
- [Linux 快速入门](/zh-CN/services/playfab/sdks/c/quickstart-linux.md)
- [Win32 快速入门](/zh-CN/services/playfab/sdks/c/quickstart-win32.md)
