> ## 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 프로젝트에서 Photon Realtime 및 PUN 멀티플레이어를 PlayFab과 연결하여 플레이어를 인증하고 CloudScript 콜백을 통해 룸 이벤트를 수신합니다.

이 섹션에서는 Photon RealTime 및 Photon Unity Networking(PUN)과 같은 Photon 멀티플레이어 서비스와 PlayFab을 함께 사용하는 방법에 대한 정보를 제공합니다.

현재, 다음과 같은 Photon 통합을 제공합니다.

* PlayFab으로 Photon 플레이어 인증.
* PlayFab CloudScript 콜백을 사용하여 룸 이벤트 수신.

## Photon 빠른 시작

이 빠른 시작에서는 PlayFab과 함께 작동하도록 Photon Multiplayer를 설정하는 방법을 보여줍니다. 현재, PlayFab은 다음과 같은 Photon 통합을 제공합니다.

* PlayFab으로 Photon 플레이어 인증.
* PlayFab CloudScript 콜백을 사용하여 룸 이벤트 수신.

이 빠른 시작에서는 최신 [Photon Unity 예제 프로젝트](https://assetstore.unity.com/packages/tools/network/photon-unity-networking-classic-free-1786)를 사용하여 두 기능이 어떻게 작동하는지 설명합니다.

<Note>
  Unity에서 완전히 지원되는 PlayFab Multiplayer 및 Chat Experience의 경우, 자세한 내용은 [PlayFab Party Unity Plugin](/services/playfab/multiplayer/networking/party-unity-overview)을 참조하세요.
</Note>

## 사전 요구 사항

1. PlayFab SDK를 가져오고 구성된 타이틀이 있는 [Unity](/services/playfab/sdks/unity3d/quickstart) 프로젝트.
2. 등록된 PlayFab 타이틀.

<Note>
  이 가이드는 Chat Application 설정을 생략합니다. Chat Application을 통합하려면, 새 Photon Chat Application을 만들고 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="Create a new Photon application" 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="New application form" 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="Manage the Test application" 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="Save the 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="Custom server authentication" width="995" height="366" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-custom-server-authentication.png" />

다음 페이지에서 Photon에 대한 Authentication 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="Enter authentication 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 tab" 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)**.
   * 웹 후크를 구성할 때 향후 사용을 위해 저장합니다.

<Note>
  Photon 비밀 키를 안전하고 쉽게 접근할 수 있는 장소에 저장하는 것을 잊지 마세요. Webhooks를 구성할 때 유용합니다.
</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="Copy 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 webhooks 설정

선택적 통합으로, Photon은 특정 이벤트를 PlayFab CloudScript로 전송할 수 있습니다. 이는 webhooks로 수행됩니다.

* 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="Assign CloudScript event handlers" 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="Import 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 setup" 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 server settings" 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** 씬으로 이동합니다.
   * 이 씬은 패키지의 모든 예제에 대한 허브입니다.
   * 이 씬이 로드되는 *첫 번째* 씬이므로, PlayFab을 Photon으로 인증할 수 있게 하는 작은 스크립트를 추가하겠습니다.
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="Add authentication component" 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="Start the scene" 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="Load demo" 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 debug console" 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 Matchmaking Guide](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 핸들러

`RoomCreated` 핸들러는 Photon 룸이 생성될 때마다 호출됩니다. 다음 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 클라이언트를 사용하여 사용자 지정 Room 속성을 변경할 때, *호출을 표시하는 것이 중요합니다*. 그래야 이벤트를 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 클라이언트를 사용하여 사용자 지정 룸 이벤트를 발생시킬 때, 호출을 표시하여 이벤트가 Webhooks(이 경우 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");
    }

}
```

* 허브 씬을 실행하고 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="Load Demo Boxes scene" 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** 패널을 관찰합니다. Photon 이벤트를 처리하는 CloudScript 코드의 결과로 생성된 이벤트를 볼 수 있어야 합니다.

1. 처음에, Photon 인스턴스에는 열린 룸이 없었습니다. 예제를 시작하면 Photon이 Boxes Demo를 위한 룸을 만들었습니다.
2. 참가하는 첫 번째 플레이어는 룸을 요청한 플레이어입니다. 따라서 **RoomJoined 이벤트**는 기록되지 않았습니다. 그런 다음 예제 코드를 실행했습니다.
   * 먼저, 사용자 지정 **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 event flow" width="408" height="333" data-path="images/playfab/live-service-management/service-gateway/add-ons/photon/tutorials/photon-playstream-event-flow.png" />

이 시점에서, PlayFab 타이틀에 Photon 이벤트 지원을 완전히 통합했습니다.
