> ## 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.

# PlayFab Game Saves용 계정 연결 전략

> 기본 연결 ID 및 필수 대 선택적 플랫폼 계정 연결을 포함한 PlayFab Game Saves용 교차 진행 전략을 비교합니다.

## 기본 연결 ID

교차 진행 전략의 첫 번째 요구 사항은 게임 플랫폼에 걸쳐 사용될 플레이어 ID를 결정하는 것입니다. 플레이어는 게임을 사용할 수 있는 모든 디바이스에서 이 ID로 로그인할 수 있어야 합니다. 1인칭 타이틀의 경우 이는 종종 Microsoft 계정(MSA)/XBOX 계정입니다. 많은 타사의 경우 이는 OpenID Connect 구현을 통해 노출된 게시자 ID일 가능성이 높습니다. 이 플레이어 ID에 대한 두 가지 요구 사항은 다음과 같습니다.

1. 플레이어가 게임을 플레이할 수 있는 모든 플랫폼에서 사용 가능
2. `LoginWithXbox` 또는 `LoginWithOpenIdConnect`와 같은 기존 PlayFab 로그인 호출에서 지원

이 두 가지 요구 사항 외에도 ID가 일종의 디바이스 기반 캐싱 및 후속 게임 실행 시 자동 로그인을 지원한다면 유용합니다. 이는 필수 요구 사항은 아니지만 워크플로 복잡성 및/또는 플레이어 마찰을 줄일 수 있습니다.

## 플랫폼 기본 ID

플랫폼 기본 ID는 플레이어가 게임을 실행하는 플랫폼에서 제공하는 계정 시스템입니다. 예는 다음과 같습니다.

* Steam: Steam 계정 및 인증 티켓(`ISteamUser::GetAuthTicketForWebApi`)
* PC/콘솔의 XBOX: MSA/XBOX `XUserHandle` 및 XSTS 토큰
* PlayStation: PSN Online ID/계정
* Nintendo: Nintendo Service Account/디바이스 ID

이러한 ID는 일반적으로 지정된 디바이스의 활성 플레이어에 대해 `PFLocalUserHandle`을 만들고 재사용하는 데 사용됩니다(예: `PFLocalUserCreateHandleWithSteamUser` 또는 `XUserHandle` 래핑). 또한 적절한 경우 공급자별 PlayFab 로그인 API(예: `LoginWithSteam`, `LoginWithXbox`)를 통해 인증하는 데 사용할 수도 있습니다.

교차 진행 시나리오에서 플랫폼 기본 ID는 기본 연결 ID에 연결되므로 진행 및 자격이 플랫폼 전반에서 플레이어를 따라갑니다. 이 문서에서 권장하는 워크플로는 플랫폼 기본 ID를 로컬 사용자 컨텍스트로 사용하고 기본 연결 ID를 교차 플랫폼 앵커로 사용합니다.

## 연결 전략 옵션

이 문서에서는 두 가지 관련 전략을 살펴봅니다.

1. 모든 플레이어는 플레이하기 전에 플랫폼 기본 ID를 기본 연결 ID와 연결하는 것이 **필수**입니다.
2. 플랫폼 기본 ID와 기본 연결 ID 간의 연결은 **선택 사항**이지만 플레이하기 전에 매우 권장됩니다.

첫 번째 전략은 구현하기가 더 쉽고 나중에 잠재적으로 손실될 진행과 관련하여 플레이어의 어려운 결정을 방지합니다. 게임 시작 시 초기 마찰을 증가시키고 플레이어가 사전 연결 요구 사항에 충분한 가치를 보지 못하는 경우 부정적인 감정을 초래할 수 있습니다. 특히 가능한 한 많은 플레이어를 유치하기 위해 매우 낮은 장벽에 의존하는 많은 무료 및 모바일 게임에는 적합하지 않습니다.

두 번째 전략은 잠재적인 충돌과 향후 어려운 결정, 그리고 개발자의 코딩 복잡성 증가를 대가로 초기 플레이어 경험을 개선합니다. 플레이어는 추가 로그인 요구 사항 없이 시작할 수 있습니다. 게임이 지원하는 경우 완전히 오프라인 상태에서 시작할 수도 있습니다. 이러한 유연성은 플레이어가 나중에 연결하기로 결정할 때 문제가 될 수 있습니다. 연결하려는 ID가 다른 플랫폼에 이미 존재하는 진행 상황이 있을 수 있습니다. 이 경우 연결 설정은 포기된 진행 또는 게임이 처리해야 하는 지저분한 병합을 포함할 수 있습니다. 게임이 이 전략을 선택하더라도 가능한 한 빨리 연결을 홍보하고, 연결의 이점을 설명하고, 연결하지 않을 경우 잠재적인 위험에 대해 경고하는 것이 좋습니다.

궁극적으로 이러한 전략 간의 선택은 비즈니스 관점에서 이루어져야 합니다. 하나가 다른 것보다 명확하게 우수하지 않습니다. 출시 후 옵션 2에서 옵션 1로 전환하면 플레이어 불만족이 발생한다는 명확한 시장 증거가 있습니다. 이는 이 선택을 조기에 올바르게 하고 처음부터 이를 중심으로 디자인하는 것의 중요성을 나타냅니다.

## 원하는 상태

전략에 관계없이 궁극적인 목표는 모든 플레이어를 동일한 상태로 만드는 것입니다. 기본 연결 ID는 플레이하는 모든 디바이스에서 플랫폼 기본 ID와 연결되어야 합니다. 이 상태에서는 진행 상황이 기본 연결 ID에 일관되게 연결되고, 플랫폼 기본 ID는 모든 플랫폼에서 기본 ID에 대한 효과적인 프록시 ID로도 사용될 수 있습니다.

일부 플랫폼에서는 기본 연결 ID가 플랫폼 기본 ID일 수 있습니다(XBOX에서 실행되는 1인칭 게임). 이러한 경우에는 원하는 상태를 얻는 것이 쉽습니다. 이 문서에서는 기존 로그인 및 연결 지침이 적절하므로 이러한 내용을 자세히 다루지 않습니다.

## LocalUser 대 login

PlayFab SDK 내에 존재하는 관련되어 있지만 별개인 두 가지 개념을 구분하는 것이 중요합니다.

LocalUserCreate 호출은 로컬 사용자 개체를 생성하고 인증을 수행하지 않고 PFLocalUserHandle을 반환합니다. 이는 플랫폼별 또는 지속형 로컬 ID(예: XUserHandle 래핑)로 사용자를 식별하고 캐시하므로 작업 및 게임 인스턴스 간에 동일한 로컬 컨텍스트를 재사용할 수 있습니다. 이 작업은 순전히 로컬입니다. 네트워크 요청을 하지 않고, 엔터티 토큰을 얻거나 PlayFab 계정을 만들지 않습니다.

반대로, Login 호출은 PlayFab으로 로컬 사용자를 인증하고 토큰과 ID를 포함한 인증된 엔터티를 설정합니다. Login 호출은 네트워크 요청(예: /Client/LoginWithXbox)을 수행하고 createAccount와 같은 플래그를 준수하며, 성공하면 결과가 캐시되어 후속 호출에서 인증된 상태를 재사용할 수 있습니다.

간단히 말해서, 로컬 사용자를 만드는 것은 PlayFab Game Saves에 필요한 로컬 ID 및 핸들 관리를 설정하는 반면, 로그인은 엔터티가 필요한 API를 활성화하기 위해 PlayFab에 접속하여 인증하는 단계입니다.

## 전략 1 - 필수 기본 ID 연결

이 전략을 설명하기 위해 Steam에 출시되는 XBOX 1인칭 게임에 대해 논의하겠습니다. 게임은 XBOX/MSA를 기본 연결 ID로 사용합니다. 모든 플레이어가 게임을 시작하기 전에 XBOX/MSA에 로그인해야 합니다. Steam ID 기반의 LocalUserHandle을 여전히 사용하지만, 플레이어가 XBOX ID를 만들고 연결할 때까지 실제 로그인을 차단합니다. 이후 실행 시에는 Steam ID가 연결된 것으로 확인되었으므로 바로 사용할 수 있습니다.

### 개괄적인 요약

* 플레이어는 플레이하기 전에 교차 플랫폼 ID(XBOX/MSA)로 로그인해야 합니다.
* 플랫폼 계정(Steam)에서 로컬 플레이어를 만들지만 XBOX/MSA 로그인이 완료될 때까지 온라인 플레이를 보류합니다.
* XBOX/MSA 로그인 후 플랫폼 계정을 교차 플랫폼 계정에 연결합니다.
* 교차 플랫폼 계정에 연결되도록 로컬 플랫폼 프로필을 새로 고칩니다.
* 이후 실행은 원활합니다. 연결이 설정되어 있으므로 플레이어는 바로 플레이할 수 있습니다.

### 자세한 연습

**Steam 로컬 사용자 핸들 만들기**

* `PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, customContext, outLocalUserHandle)`을 호출합니다.
* 결과: `PFLocalUserHandle`, 아직 인증되지 않음.

**온라인으로 시도**

* `PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async)`를 호출합니다.
* 완료 시, `PFLocalUserLoginGetResult`가 `E_PF_ACCOUNT_NOT_FOUND`로 실패하면 계정을 부트스트랩하기 위해 XBOX 로그인을 트리거합니다.
* `PFLocalUserLoginGetResult`가 성공하면 원하는 상태의 계정이 이미 있고 온라인 상태입니다. 워크플로 완료.

<Note>
  타이틀이 현재 사용자에 연결된 상태로 교차 플랫폼 ID를 유지해야 하는 경우(예: 게임이 XBOX 로그인을 요구하고 엔터티의 XBOX 링크가 현재 로그인된 XBOX 계정과 일치할 것으로 예상하는 경우), 성공적인 플랫폼 로그인만으로는 충분하지 않습니다. 로그인 후 `PFAccountManagementClientGetAccountInfoAsync`를 호출하여 교차 플랫폼 링크가 현재 사용자와 일치하는지 확인합니다. 일치하지 않으면 엔터티가 오래된 교차 플랫폼 ID에 연결되어 있을 수 있습니다. 아래의 **XBOX를 통해 로그인** 및 **Steam 연결** 단계를 따라 재정렬합니다.
</Note>

**XBOX를 통해 로그인**

* `PFAuthenticationLoginWithXUserRequest`를 빌드합니다.
  * 필요한 경우 PlayFab 계정을 만들려면 `createAccount=true`로 설정합니다.
  * PC에서 로그인한 XBOX 사용자로부터 `XUserHandle`을 제공합니다.
* `PFAuthenticationLoginWithXUserAsync(serviceConfigHandle, request, async)`를 호출합니다.
* `PFAuthenticationLoginWithXUserGetResultSize(...)` 및 `PFAuthenticationLoginWithXUserGetResult(...)`로 완료하여 `PFEntityHandle`을 얻습니다.
* 참고: LoginWithXUser 변형을 사용할 수 없는 경우 `PFAuthenticationLoginWithXbox`를 사용합니다. 이 경우 `XUserHandle`에서 XSTS 토큰을 직접 추출해야 합니다.

**Steam을 인증된(XBOX 기반) PlayFab 계정에 연결**

* 현재 Steam 인증 티켓(`ISteamUser::GetAuthTicketForWebApi` 또는 통합의 동등한 기능; 코드에서 정확한 함수 이름 확인)으로 클라이언트 링크 요청을 빌드합니다.
* `PFAccountManagementClientLinkSteamAccountAsync(entityHandle, linkRequest, async)`를 호출합니다.
* 참고: "LinkSteamAccount"는 인증이 아닌 Account Management 아래에 있습니다.
* 성공 후, 플레이어의 Steam ID가 XBOX 기반 PlayFab 계정에 연결됩니다.

**Steam 로컬 사용자를 엔터티와 연결(연결 후)**

* `PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async)`를 다시 호출합니다.
* 이제 `PFLocalUserLoginGetResult`가 성공합니다. 결과인 `PFEntityHandle`은 Steam 로컬 사용자에 바인딩됩니다.

**앞으로의 정상 작동**

* Game Save 및 기타 온라인 API는 인증된 `PFEntityHandle`을 사용합니다.
* 이후 게임 실행은 모든 연결이 설정되어 있기 때문에 바로 플레이할 수 있습니다.

참고

* `PFLocalUserCreateHandleWithSteamUser`는 단독으로 로그인하지 않지만, `PFGameSaveFilesAddUserWithUiAsync`를 호출하려고 하면 로그인 시도가 발생합니다. 해당 호출 전에 계정 연결 흐름을 진행했는지 확인하세요.

### 연결 충돌 처리

Steam을 XBOX 기반 엔터티에 연결할 때 두 가지 다른 충돌이 발생할 수 있습니다. 각각은 다른 해결책이 필요합니다.

| 오류                                    | 의미                                          | 해결책                                                                                                                                                                                    |
| ------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED` | 현재 Steam 계정이 **다른** PlayFab 엔터티에 연결되어 있습니다. | 동의 후 `forceLink=true`로 `PFAccountManagementClientLinkSteamAccountAsync`를 다시 호출합니다. 이렇게 하면 Steam 링크가 현재 엔터티로 이동합니다.                                                                     |
| `E_PF_ACCOUNT_ALREADY_LINKED`         | 현재 엔터티에 이미 **다른** Steam 계정이 연결되어 있습니다.      | 이전 Steam 링크를 먼저 제거하려면 `PFAccountManagementClientUnlinkSteamAccountAsync`를 호출한 다음 새 Steam 계정에 대해 `PFAccountManagementClientLinkSteamAccountAsync`를 호출합니다. `forceLink`는 이 오류를 해결하지 않습니다. |

**권장 흐름:**

* XBOX 로그인 후, `PFAccountManagementClientGetAccountInfoAsync(xboxEntity, getInfoRequest, async)`를 호출하여 계정 정보를 가져오고 연결된 ID를 검사합니다.
* 엔터티에 이미 **다른 Steam 계정**이 연결되어 있는 경우:
  * 진행하면 이 엔터티에서 이전 Steam 연결이 제거된다고 플레이어에게 경고합니다. 동의를 얻습니다.
  * 기존 Steam 링크를 제거하려면 `PFAccountManagementClientUnlinkSteamAccountAsync(xboxEntity, unlinkRequest, async)`를 호출합니다.
  * 그런 다음 현재 Steam 티켓으로 `PFAccountManagementClientLinkSteamAccountAsync`를 호출합니다.
* 링크 호출이 `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED`를 반환하는 경우(Steam 계정이 다른 엔터티에 속함):
  * 이 Steam 계정이 다른 PlayFab 계정과 연결되어 있으며 여기에 연결하면 해당 연결이 제거된다고 플레이어에게 경고합니다.
  * 동의 후 `forceLink=true`로 재시도합니다.

이는 플레이어의 주도권을 유지하면서 링크를 의도치 않게 재정의하는 것을 방지하고, ID를 조정하기 위한 명확한 경로를 제공합니다.

### 샘플 C++ 흐름(Steam 우선 게이트, XBOX 부트스트랩, Steam 연결, Steam으로 다시 로그인):

<Info>
  이 샘플은 수명 관리를 단순화하기 위해 `static XAsyncBlock` 변수를 사용합니다. 프로덕션 코드는 동시 또는 재진입 호출을 지원하기 위해 비동기 블록을 동적으로 할당해야 합니다(예: 컨텍스트 구조체의 일부로).
</Info>

```cpp theme={null}
// Assumes: serviceConfigHandle, taskQueue, and a signed-in XUserHandle (xUserHandle) are available
//
// NOTE: RETURN_IF_FAILED is used here for brevity. Because XAsyncBlock callbacks return void,
// production code should replace it with proper error handling (for example, log and return).

// Single context block used by multiple nested callbacks
struct Strategy1AsyncCtx
{
    PFServiceConfigHandle serviceConfig{};
    XTaskQueueHandle queue{};
    PFLocalUserHandle steamUser{};
    XUserHandle xUserHandle{};
    PFEntityHandle xboxEntity{};
};

// 1) Create Steam local user
PFLocalUserHandle steamUser{};
RETURN_IF_FAILED(PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, /*customContext*/ nullptr, &steamUser));

// 2) Attempt Steam login with createAccount=false
XAsyncBlock steamLoginAsync{};
steamLoginAsync.queue = taskQueue;
// Provide context for nested callbacks
// NOTE: Caller is responsible for deleting s1ctx in the terminal callback.
Strategy1AsyncCtx* s1ctx = new Strategy1AsyncCtx{ serviceConfigHandle, taskQueue, steamUser, xUserHandle };
steamLoginAsync.context = s1ctx;
steamLoginAsync.callback = [](XAsyncBlock* async)
{
    auto* ctx = static_cast<Strategy1AsyncCtx*>(async->context);
    // Try to get result-size; failure means login failed
    size_t bufferSize{};
    HRESULT hrSize = PFLocalUserLoginGetResultSize(async, &bufferSize);
    if (FAILED(hrSize))
    {
        if (hrSize == E_PF_ACCOUNT_NOT_FOUND)
        {
            // 3) Bootstrap via Xbox (provider API), then link Steam
            PFAuthenticationLoginWithXUserRequest xreq{};
            xreq.createAccount = true;
            xreq.user = ctx->xUserHandle; // supply your signed-in XUserHandle

            static XAsyncBlock xboxLoginAsync{}; // ensure lifetime until callback
            xboxLoginAsync.queue = async->queue;
            xboxLoginAsync.context = ctx;
            xboxLoginAsync.callback = [](XAsyncBlock* xAsync)
            {
                auto* ctx = static_cast<Strategy1AsyncCtx*>(xAsync->context);
                // Obtain Xbox login result
                PFEntityHandle xboxEntity{};
                size_t xSize{};
                RETURN_IF_FAILED(PFAuthenticationLoginWithXUserGetResultSize(xAsync, &xSize));
                std::vector<uint8_t> xbuf(xSize);
                PFAuthenticationLoginResult const* xres{};
                RETURN_IF_FAILED(PFAuthenticationLoginWithXUserGetResult(xAsync, &xboxEntity, xbuf.size(), xbuf.data(), &xres, nullptr));
                ctx->xboxEntity = xboxEntity;

                // Detect existing Steam linkage before linking
                // Build a minimal GetAccountInfo request
                // Then call PFAccountManagementClientGetAccountInfoAsync(xboxEntity, &getInfoReq, &getInfoAsync)
                // and inspect the linked accounts section of the result.
                //
                // If the entity already has a DIFFERENT Steam account linked:
                //   Call PFAccountManagementClientUnlinkSteamAccountAsync(xboxEntity, &unlinkReq, &unlinkAsync)
                //   to remove the old Steam link, then proceed to link the current Steam account below.
                //   forceLink does NOT resolve this case (E_PF_ACCOUNT_ALREADY_LINKED error).
                //
                // If a DIFFERENT entity has this Steam account claimed:
                //   The link call below will fail with E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED.
                //   Retry with forceLink=true after obtaining player consent.

                // Link Steam to Xbox-backed account
                PFAccountManagementClientLinkSteamAccountRequest linkReq{};
                linkReq.steamTicket = GetCurrentSteamTicket(); // your helper wrapping ISteamUser::GetAuthTicketForWebApi
                bool isServiceSpecific = true;
                linkReq.ticketIsServiceSpecific = &isServiceSpecific;
                // To resolve E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED after consent:
                // bool forceLink = true;
                // linkReq.forceLink = &forceLink;

                static XAsyncBlock linkAsync{};
                linkAsync.queue = xAsync->queue;
                linkAsync.context = ctx;
                linkAsync.callback = [](XAsyncBlock* linkXAsync)
                {
                    auto* ctx = static_cast<Strategy1AsyncCtx*>(linkXAsync->context);
                    RETURN_IF_FAILED(XAsyncGetStatus(linkXAsync, false));

                    // 4) Re-login Steam local user to bind entity handle
                    static XAsyncBlock steamReloginAsync{};
                    steamReloginAsync.queue = linkXAsync->queue;
                    steamReloginAsync.context = ctx;
                    steamReloginAsync.callback = [](XAsyncBlock* reloginAsync)
                    {
                        auto* ctx = static_cast<Strategy1AsyncCtx*>(reloginAsync->context);
                        PFEntityHandle entity{};
                        RETURN_IF_FAILED(PFLocalUserLoginGetResult(reloginAsync, &entity, 0, nullptr, nullptr, nullptr));
                        // Now the Steam local user has an associated entity
                    };

                    RETURN_IF_FAILED(PFLocalUserLoginAsync(ctx->steamUser, /*createAccount*/ false, &steamReloginAsync));
                };

                RETURN_IF_FAILED(PFAccountManagementClientLinkSteamAccountAsync(ctx->xboxEntity, &linkReq, &linkAsync));
            };

            // NOTE: On platforms that don't support LoginWithXUser, replace this with PFAuthenticationLoginWithXboxAsync
            RETURN_IF_FAILED(PFAuthenticationLoginWithXUserAsync(ctx->serviceConfig, &xreq, &xboxLoginAsync));
        }
        // Other failures: handle/log as needed
        return;
    }

    // Steam login succeeded: get entity
    PFEntityHandle entity{};
    RETURN_IF_FAILED(PFLocalUserLoginGetResult(async, &entity, 0, nullptr, nullptr, nullptr));
    // Use entity as needed; PFServices/GameSave APIs, etc.
};

RETURN_IF_FAILED(PFLocalUserLoginAsync(steamUser, /*createAccount*/ false, &steamLoginAsync));
```

구현 참고 사항:

* XBOX 부트스트랩 경로를 결정하려면 `E_PF_ACCOUNT_NOT_FOUND`를 사용합니다. 기타 오류는 별도로 처리합니다.
* Steam `PFLocalUserHandle`을 계속 사용합니다. 공급자별 XBOX 로그인은 Steam을 연결하는 데만 사용되는 `entityHandle`을 반환합니다.
* 연결 후, 엔터티를 LocalUser에 바인딩하기 위해 Steam으로 다시 로그인합니다.
* 연결을 위해 새 Steam 티켓을 가져와야 합니다.
* 더 이상 필요하지 않은 경우 핸들을 닫습니다(`PFLocalUserCloseHandle`, `PFEntityCloseHandle`).

## 전략 2 - 선택적 기본 ID 연결

이 시나리오에서는 게임이 Steam에서 실행되고 XBOX/MSA를 기본 연결 ID로 사용합니다. 플레이어는 XBOX/MSA에 즉시 로그인하지 않고 플레이를 시작할 수 있습니다. 게임은 명확한 이점과 경고와 함께 조기 연결을 권장하지만 초기 게임 플레이는 차단되지 않습니다.

### 개괄적인 요약

* 플랫폼 계정(Steam)을 사용하여 로컬 플레이어를 만들고 새 계정을 만들지 않고 온라인으로 플레이해 봅니다.
* 온라인 작업이 잘 되면 계속합니다. 그렇지 않으면 다음 중 선택 사항을 제공합니다.
  * 통합 계정을 만들기 위해 교차 플랫폼 ID(XBOX/MSA)로 로그인하거나,
  * 지금 플랫폼 전용 계정을 만들고 즉시 플레이를 시작합니다.
* 이점과 위험을 설명하여 XBOX/MSA에 대한 조기 연결을 권장합니다.
* 플레이어가 연결하기로 선택하면:
  * 연결이 직접 성공하면 통합 계정으로 계속 플레이합니다.
  * XBOX/MSA 계정이 다른 곳에 이미 진행 상황이 있는 경우 일시 중지하고 어느 진행 상황을 유지할지 묻습니다(조정):
    * XBOX/MSA 진행 상황을 유지하고 현재 플랫폼 계정을 연결합니다.
    * 현재 플랫폼 진행 상황을 유지하고 연결을 연기합니다.
* 연결 후 통합 계정에 연결되도록 로컬 플랫폼 프로필을 새로 고칩니다.
* 이후 실행은 원활합니다. 플레이어는 (연결이 연기되지 않은 경우) 바로 플레이할 수 있습니다.

### 자세한 연습

**Steam 로컬 사용자 핸들 만들기**

* `PFLocalUserCreateHandleWithSteamUser(serviceConfigHandle, customContext, outLocalUserHandle)`을 호출합니다.
* 결과: `PFLocalUserHandle`, 아직 인증되지 않음.

**온라인으로 시도(자동 Steam 경로)**

* `PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ false, async)`를 호출합니다.
* 완료 시:
  * `PFLocalUserLoginGetResult`가 성공하면 결과인 `PFEntityHandle`을 사용하고 온라인으로 진행합니다.
    * Steam 기반 계정이 이미 XBOX/MSA에 연결되었는지 확인합니다. `PFAccountManagementClientGetAccountInfoAsync(entityHandle, getInfoRequest, async)`를 호출하고 연결된 ID를 검사합니다. 연결되어 있지 않으면 아래 흐름을 사용하여 XBOX를 연결하도록 플레이어에게 요청합니다(비차단). 그러면 향후 교차 진행이 원활하게 작동합니다.
  * `PFLocalUserLoginGetResult`가 `E_PF_ACCOUNT_NOT_FOUND`로 실패하면 계정이 생성될 때까지 플레이어는 오프라인 상태로 유지됩니다. 두 가지 선택 사항을 제공합니다.
    * 지금 XBOX 기반 PlayFab 계정 만들기: XBOX/MSA로 로그인합니다(예: `PFAuthenticationLoginWithXUserAsync`).
    * 지금 Steam으로 로그인하고 플레이 시작: `PFLocalUserLoginAsync(localUserHandle, /*createAccount*/ true, async)`를 호출하여 로컬 핸들에 바인딩된 Steam 기반 PlayFab 계정을 만듭니다. 성공 시 결과인 `PFEntityHandle`을 사용하여 즉시 온라인으로 진행합니다.
  * 기타 오류의 경우 정상적으로 처리합니다(재시도/백오프).

**XBOX 연결 요청(비차단)**

* 연결의 이점(교차 진행, 자격 이식성)을 제시합니다.
* XBOX/MSA 로그인을 제공합니다.

**XBOX를 통해 로그인(부트스트랩 또는 조정 감지가 포함된 후기 연결)**

* 플레이어가 나중에 XBOX 연결을 시작하는 경우(현재 세션에서 이미 Steam 기반 `PFEntityHandle`이 있음):
  * 새 XBOX 기반 계정을 만들지 마세요.
  * 적절한 링크 API(예: `PFAccountManagementClientLinkXboxAccountAsync(currentSteamEntity, linkRequest, async)`)를 호출하여 XBOX ID를 현재 Steam 기반 계정에 직접 연결하려고 시도합니다.
  * 연결이 성공하면 XBOX/MSA ID가 이제 연결됩니다. 정상 작동을 계속합니다.
  * 기존 링크/충돌 오류로 인해 연결이 실패하는 경우(XBOX ID가 다른 곳에 이미 연결됨) 계정 조정 모드로 들어갑니다. 즉시 `createAccount=false`(예: `PFAuthenticationLoginWithXUserAsync`)로 XBOX/MSA 로그인을 수행하여 XBOX 기반 `PFEntityHandle`을 얻은 다음, 플레이어에게 프롬프트를 표시하기 전에 컨텍스트를 위한 프로필 메타데이터를 가져옵니다. 동의 후 연결 결정으로 진행합니다.
* 플레이어에게 XBOX에 대한 기존 계정이 없는 경우(부트스트랩 경로):
  * 먼저 `PFAuthenticationLoginWithXUserAsync`(또는 `LoginWithXUser`를 사용할 수 없는 경우 `PFAuthenticationLoginWithXboxAsync`)를 사용하여 `createAccount=false`로 XBOX/MSA 로그인을 시도합니다.
    * 로그인이 성공하면: XBOX/MSA 계정에 이미 PlayFab 계정이 있고 기존 진행 상황이 있을 가능성이 높습니다. 유지할 진행 상황을 선택하려면 계정 조정 모드로 들어갑니다.
    * 로그인이 `E_PF_ACCOUNT_NOT_FOUND`로 실패하면: `createAccount=true`로 다시 로그인하여 PlayFab 계정을 만들도록 진행합니다.
  * `PFAuthenticationLoginWithXUserGetResultSize(...)` 및 `PFAuthenticationLoginWithXUserGetResult(...)`로 완료하여 `PFEntityHandle`을 얻습니다.

**Steam을 인증된(XBOX 기반) PlayFab 계정에 연결**

* 방금 새 XBOX 기반 PlayFab 계정을 만든 경우에 해당(기존 Steam 연결 없음).
* 이전에 계정 조정에 들어간 경우 연결 결정 및 필요한 `forceLink` 작업이 거기에서 처리됩니다. 이 단계를 건너뛰세요.
* 현재 Steam 인증 티켓(`ISteamUser::GetAuthTicketForWebApi` 또는 사용자 통합)으로 클라이언트 링크 요청을 빌드합니다.
* `forceLink=false`(기본값)로 `PFAccountManagementClientLinkSteamAccountAsync(entityHandle, linkRequest, async)`를 호출합니다. 새 계정의 경우 성공할 것으로 예상됩니다.
* 연결이 실패하는 경우 오류 조건(예상치 못한 충돌 또는 인증 실패)으로 처리합니다. 플레이어에게 오류를 표시하고 재시도하기 전에 다시 로그인하도록 요청하는 것을 고려하세요.
* 성공 후, 플레이어의 Steam ID가 XBOX 기반 PlayFab 계정에 연결됩니다.

**Steam 로컬 사용자를 엔터티와 연결(연결 후)**

* `PFLocalUserLoginAsync(steamLocalUserHandle, /*createAccount*/ false, async)`를 다시 호출합니다.
* 이제 `PFLocalUserLoginGetResult`가 성공합니다. 결과인 `PFEntityHandle`은 Steam 로컬 사용자에 바인딩됩니다.

**앞으로의 정상 작동**

* Game Save 및 기타 온라인 API는 인증된 `PFEntityHandle`을 사용합니다.
* 이후 게임 실행은 모든 연결이 설정되어 있기 때문에 바로 플레이할 수 있습니다.

참고

* XBOX/MSA 로그인으로 초기 게임 플레이를 차단하지 마세요. 조기에 요청하되 플레이어가 연기할 수 있도록 허용하세요.
* `PFLocalUserCreateHandleWithSteamUser`는 단독으로 로그인하지 않습니다. `PFGameSaveFilesAddUserWithUiAsync`와 같이 로그인을 트리거하는 API를 호출하기 전에 연결 흐름을 완료했는지 확인하세요.

### 계정 조정(XBOX/MSA에 기존 진행 상황이 있는 경우)

플레이어가 이미 진행 상황이 있는 XBOX/MSA 계정으로 로그인하는 경우(예: 콘솔 또는 다른 플랫폼에서), 새 계정을 무작정 만들거나 기존 연결을 재정의하는 것을 피해야 합니다. 감지 및 조정을 위한 신중한 2단계 접근 방식을 사용하세요.

<Tip>
  **플레이어를 대상으로 한 문구:** 개발자 문서에서는 "조정"을 사용합니다. 플레이어 UI의 경우 혼동을 줄이고 플레이어가 하나의 프로필을 선택하여 계속 진행하고 있음(병합이 아님)을 강조하기 위해 "게임 프로필 선택", "진행 상황 선택" 또는 "기존 진행 상황 유지"와 같은 더 명확한 용어를 사용하는 것이 좋습니다.
</Tip>

#### 감지 단계

* 조정은 두 가지 경로를 통해 진입할 수 있습니다.
  1. 기존 XBOX/MSA 계정 감지: `createAccount=false`로 XBOX/MSA 로그인을 시도합니다.
     * 로그인이 성공하면 XBOX/MSA ID에 이미 PlayFab 계정이 있고 기존 진행 상황이 있을 가능성이 높습니다. 조정으로 들어갑니다.
     * 로그인이 `E_PF_ACCOUNT_NOT_FOUND`로 실패하면 부트스트랩(`createAccount=true`)으로 진행합니다. 조정에 들어갈 필요가 없습니다.
  2. 후기 연결 충돌 감지: XBOX/MSA를 현재 Steam 기반 계정에 연결할 때 링크가 기존 연결/충돌 오류(다른 Steam ID와 이미 연결된 XBOX ID)로 실패합니다.
     * 즉시 `createAccount=false`(예: `PFAuthenticationLoginWithXUserAsync`)로 XBOX/MSA 로그인을 수행하여 XBOX 기반 `PFEntityHandle`을 얻습니다.
     * 컨텍스트를 표시하기 위해 프로필 메타데이터(예: 최근 진행 상황, 저장 슬롯, 타임스탬프)를 가져옵니다.
     * 유지할 진행 상황을 결정하기 위해 조정으로 들어갑니다.

#### 조정 단계

* 간단한 옵션을 권장합니다: XBOX/MSA(기본 연결 ID) 진행 상황을 유지하고 로컬 Steam 진행 상황을 포기합니다. 이는 교차 플랫폼 앵커를 보존하고 복잡한 병합을 방지합니다.
* 플레이어는 사실상 세 가지 선택이 있습니다(게임은 이 중 1-2개만 제공하도록 선택할 수 있습니다).
  1. XBOX/MSA 진행 상황 유지(권장): 현재 Steam 계정을 기존 XBOX 기반 PlayFab 계정에 연결하도록 진행합니다.
  2. 현재 Steam 진행 상황 유지 및 연결 취소: 지금은 Steam 기반 계정을 계속 사용합니다. XBOX/MSA 데이터를 연결하거나 재정의하지 마세요. 다음에 다시 연결을 제공합니다.
  3. 현재 Steam 진행 상황 유지 및 XBOX 진행 상황 포기: Steam 기반 계정으로 전환하고 의도적으로 XBOX/MSA 진행 상황을 삭제합니다. 이 경로는 특히 다른 플랫폼 기본 ID가 이미 XBOX 계정에 연결된 경우 명시적인 게임 디자인 고려가 필요하며 가볍게 시도해서는 안 됩니다. 이 문서에서는 이 시나리오에 대한 구현 세부 사항을 다루지 않습니다.
* 커밋하기 전:
  * XBOX 엔터티에 대해 `PFAccountManagementClientGetAccountInfoAsync`를 호출하여 기존 공급자 링크를 검사하고 Steam 링크가 이미 존재하는지 확인합니다.
  * 엔터티에 **다른 Steam 계정**이 연결되어 있는 경우, 새 계정을 추가하기 전에 이 기존 Steam 연결이 엔터티에서 제거된다고 플레이어에게 경고합니다. 이를 위해서는 현재 Steam 계정을 연결하기 전에 `PFAccountManagementClientUnlinkSteamAccountAsync`를 호출해야 합니다. `forceLink`는 이 시나리오를 해결하지 않습니다.
  * **현재 Steam 계정**이 **다른 엔터티**에 연결된 경우, `PFAccountManagementClientLinkSteamAccountAsync`를 호출하면 `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED`가 반환됩니다. 연결하면 Steam ID가 다른 엔터티에서 이동된다고 경고한 다음 동의 후 `forceLink=true`로 재시도합니다.

#### 커밋 작업(플레이어 선택 기반)

* 플레이어가 XBOX/MSA 진행 상황을 선택합니다:
  * XBOX 로그인 엔터티가 얻어졌는지 확인합니다(감지 중에 아직 수행되지 않은 경우 `createAccount=false`로 `PFAuthenticationLoginWithXUserAsync`를 수행).
  * XBOX 엔터티에 이미 다른 Steam 계정이 연결되어 있는 경우, 먼저 `PFAccountManagementClientUnlinkSteamAccountAsync`를 호출하여 제거합니다.
  * 현재 Steam을 XBOX 기반 계정에 연결합니다. 호출이 `E_PF_LINKED_ACCOUNT_ALREADY_CLAIMED`를 반환하는 경우(Steam이 다른 엔터티에 있음), 동의 후 `forceLink=true`로 재시도합니다.
  * 엔터티를 바인딩하기 위해 Steam 로컬 사용자로 다시 로그인합니다(`PFLocalUserLoginAsync(..., /*createAccount*/ false, ...)`).
* 플레이어가 Steam 진행 상황을 선택합니다:
  * Steam에 대한 PlayFab 계정이 아직 없으면 `PFLocalUserLoginAsync(..., /*createAccount*/ true, ...)`를 통해 만들거나 바인딩합니다.
  * XBOX 연결을 연기합니다. 즉시 게임 플레이를 허용합니다. 다음에 다시 연결을 제공합니다.

#### 참고 사항

* 플레이어의 결정을 알리기 위해 항상 충분한 컨텍스트를 가져와 표시하세요.
* 시간이 지남에 따라 프롬프트와 기본값을 개선하기 위해 조정 결과에 대한 원격 분석을 기록합니다.

### 전략 2 흐름 다이어그램

다음 다이어그램은 전체 결정 트리를 보여줍니다. 모든 경로는 플레이어가 플레이 가능한 상태에 도달하는 단일 터미널 노드로 수렴됩니다.

<img src="https://mintcdn.com/microsoft-4404708b/U1LR64ZWxo45eXwl/images/playfab/player-progression/game-saves/strategy2-flow.svg?fit=max&auto=format&n=U1LR64ZWxo45eXwl&q=85&s=a4c6001009c7729b9a68014f478b1a20" alt="Steam 로컬 사용자 생성부터 로그인, 계정 연결 및 조정 경로에 이르는 전체 결정 트리를 보여주는 전략 2 흐름 다이어그램." width="1424" height="1968" data-path="images/playfab/player-progression/game-saves/strategy2-flow.svg" />

## 참고 항목

* [PlayFab Game Saves 개요](/services/playfab/player-progression/game-saves/overview)
* [Game Saves 빠른 시작](/services/playfab/player-progression/game-saves/quickstart)
* [Game Saves 충돌](/services/playfab/player-progression/game-saves/conflicts)
* [토큰 만료 및 다시 로그인](/services/playfab/sdks/c/relogin)


## Related topics

- [PlayFab Game Saves용 Steam Deck 구현 가이드](/ko/services/playfab/player-progression/game-saves/steam-deck-implementation.md)
- [PlayFab의 Microsoft 계정 인증](/ko/services/playfab/identity/dev-identity/authentication/aad-authentication.md)
- [Unity에서 Google 로그인에서 Google Play Games로 마이그레이션하기](/ko/services/playfab/identity/player-identity/platform-specific-authentication/google-play-games-sign-in-migration-details.md)
- [Game Saves 개요](/ko/services/playfab/player-progression/game-saves/overview.md)
- [Grafana를 Insights에 연결](/ko/services/playfab/data-analytics/legacy/connectivity/connecting-grafana-to-insights.md)
