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

# 소스 코드 및 모범 사례 - Winter Starfall

> Winter Starfall 소스 코드 다운로드 방법과 로그인 및 구매 시나리오에 대한 모범 사례 튜토리얼입니다.

# Winter Starfall의 소스 코드와 시나리오

이 튜토리얼에서는 Winter Starfall의 소스 코드를 다운로드하고 실행하는 방법과, 로그인 및 구매 시나리오에서 모범 사례를 보여주는 코드 샘플을 자세히 살펴봅니다.

## 소스 코드 다운로드

### 사전 요구 사항

* Node JS 설치
* Visual Studio Code 설치

### 로컬 개발 환경 설정

1. [GitHub](https://github.com/PlayFab/winter-starfall)에서 소스 코드를 다운로드합니다.
2. VS Code에서 `/website` 폴더를 열고 권장하는 모든 확장을 설치합니다.
3. `/website` 디렉터리에서 npm install을 실행하여 모든 종속성을 설치합니다.
4. `npm run dev`를 사용하여 사이트를 시작합니다. 사이트를 보려면 제공된 링크를 선택합니다.

### Azure 자격 증명

AZURE\_CREDENTIALS 시크릿을 업데이트하려면 Azure Portal 셸에서 다음 명령을 실행합니다.

`az ad sp create-for-rbac --name "VanguardOutrider2" --role contributor --scopes /subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/YOUR_RESOURCE_GROUP_NAME --json-auth`

## 로그인 흐름

Winter Starfall의 플레이어 인증 흐름은 두 단계로 구성됩니다. 로그인 호출 단계와, 서비스에서 플레이어 정보를 가져와 스토리의 올바른 위치로 로드하는 로그인 후 함수 단계입니다.

### 플레이어 인증

로그인 부분의 경우, [use-login.tsx](https://github.com/PlayFab/winter-starfall/blob/main/website/src/hooks/use-login.ts) 파일은 플레이어에게 제공되는 여러 인증 방식을 처리하는 TypeScript 래퍼 함수를 정의합니다.

144–169행은 이메일 주소로 로그인하는 콜백 함수를 정의하며, 로그인이 성공하면 로그인 후 함수를 호출합니다.

```typescript theme={null}
const onLogin = useCallback(() => {
    setIsLoading(true);
    setLoginMethodInProgress("email");

    dispatch(siteSlice.actions.loginSteps(loginEventCount));

    ClientLoginWithEmailAddress({ Email: data.email, Password: data.password })
        .then(result => {
            dispatch(siteSlice.actions.login(result));
            dispatch(siteSlice.actions.loginStepsAdvance());
            // Track logins from returning users
            trackEvent({ name: "Returning User", properties: {} });
        })
        .then(() => {
            return postLoginFunctions();
        })
        .then(() => {
            setIsLoading(false);
            navigate(routes.Explore());
        })
        .catch(problem => {
            dispatch(siteSlice.actions.loginStepsReset());
            onError(problem);
        })
        .finally(() => dispatch(siteSlice.actions.loginStepsReset()));
}, [ClientLoginWithEmailAddress, data.email, data.password, dispatch, navigate, onError, postLoginFunctions]);
```

이 게임은 이메일, Google, Facebook 등 3가지 복구 가능한 플레이어 인증 방식을 제공하여 플레이어 계정이 절대로 손실되지 않도록 합니다. 자세한 내용은 [로그인 모범 사례](/services/playfab/identity/player-identity/login/login-basics-best-practices)를 참조하세요.

### 로그인 후: 플레이어 데이터 가져오기

보안 토큰을 가져온 후에는 이를 사용하여 저장된 게임 상태를 얻는 로그인 후 함수를 실행합니다.

340–378행에는 **postLoginFunctions**가 정의되어 있으며, 이 함수는 다양한 Economy 및 PlayFab Services API를 호출하여 플레이어에 따라 올바른 스토리 위치, 인벤토리 아이템, 통화 등을 로드합니다.

```typescript theme={null}
return new Promise<void>((resolve, reject) => {
    ClientGetTitleData({ Keys: TITLE_DATA_KEYS_ALL })
        .then(result => {
            dispatch(siteSlice.actions.titleData(result));
            dispatch(siteSlice.actions.loginStepsAdvance());
            loadScripts();
        })
        .then(() =>
            EconomySearchItems({
                Count: SEARCH_ITEMS_MAX_COUNT,
                Filter: "type eq 'currency' or type eq 'catalogItem'",
            })
        )
        .then(result => {
            dispatch(siteSlice.actions.catalog(result.Items));
            dispatch(siteSlice.actions.loginStepsAdvance());
        })
        .then(() => EconomyGetInventoryItems({ Count: SEARCH_ITEMS_MAX_COUNT }))
        .then(result => {
            dispatch(siteSlice.actions.inventory(result.Items));
            dispatch(siteSlice.actions.loginStepsAdvance());
        })
        .then(() => ClientGetUserData({ Keys: USER_DATA_KEYS_PLAYER_ALL }))
        .then(result => {
            dispatch(siteSlice.actions.userDataPlayer(result));
            dispatch(siteSlice.actions.loginStepsAdvance());
        })
        .then(() => ClientGetUserReadOnlyData({ Keys: USER_DATA_KEYS_READONLY_ALL }))
        .then(result => {
            dispatch(siteSlice.actions.userDataReadOnly(result));
            dispatch(siteSlice.actions.loginStepsAdvance());
        })
        .then(() => {
            resolve();
        })
        .catch(problem => {
            dispatch(siteSlice.actions.loginStepsReset());
            reject(problem);
        });
});
```

* 먼저 `ClientGetTitleData`가 모든 API 호출에 필요한 비밀 키를 가져옵니다.
* 그런 다음 `EconomySearchItems`가 카탈로그에서 통화 및 아이템 유형을 검색합니다.
* 이 값은 `EconomyGetInventoryItems`의 입력으로 사용되어 플레이어의 인벤토리 아이템을 반환합니다.
* 그리고 `ClientGetUserData`가 플레이어 데이터의 일부로 저장된 스토리 위치 값을 검색합니다.

## 구매 흐름

게임의 특정 지점에서 플레이어는 스토어에서 인벤토리 아이템을 구매하고 판매할 수 있는 옵션을 갖습니다. 구매 흐름은 또 다른 래퍼 함수로 구현됩니다. [use-store.tx](https://github.com/PlayFab/winter-starfall/blob/main/website/src/hooks/use-store.ts) 파일의 40–69행은 플레이어가 특정 위치에서 만나는 올바른 스토어를 표시하고, 카탈로그를 검색하여 판매 중인 아이템을 채웁니다.

```typescript theme={null}
export function useEconomyStoreSingle(storeName: string): IEconomyStoreSingleResults {
    const dispatch = useDispatch();
    const store = useSelector((state: AppState) => state.site.stores).find(store =>
        store.AlternateIds?.find(friendlyId => friendlyId.Type === FRIENDLYID && friendlyId.Value === storeName)
    );
    const { isLoading, error, setError, EconomyGetItem } = usePlayFab();

    useEffect(() => {
        if (!is.null(store) || isStoreLoading) {
            return;
        }

        isStoreLoading = true;

        EconomyGetItem({
            AlternateId: { Type: FRIENDLYID, Value: storeName },
        })
            .then(results => {
                dispatch(siteSlice.actions.storeAdd(results.Item as PlayFabEconomyModels.CatalogItem));
            })
            .catch(setError)
            .finally(() => {
                isStoreLoading = false;
            });
    }, [EconomyGetItem, dispatch, setError, store, storeName]);

    return {
        error,
        isLoading,
        store,
    };
}
```

`EconomyGetItem`은 [use-playfab.tsx](https://github.com/PlayFab/winter-starfall/blob/main/website/src/hooks/use-playfab.ts)의 423–446행에 정의되어 있으며, 이 안에서 PlayFab Economy API `GetItems`를 사용해 카탈로그를 검색하고 아이템을 반환합니다.

```typescript theme={null}
const EconomyGetItems = useCallback(
        (request: PlayFabEconomyModels.GetItemsRequest): Promise<PlayFabEconomyModels.GetItemsResponse> => {
            const date = startRequest("EconomyApi", "GetItems", request);

            return new Promise((resolve, reject) => {
                PlayFab.EconomyApi.GetItems(request, (result, problem) => {
                    endRequest(date, problem, result);

                    if (!is.null(problem)) {
                        return reject(problem);
                    }

                    if (result.code !== 200) {
                        return reject(formatPlayFabNon200Error(result));
                    }

                    return resolve(result.data);
                }).catch(reason => {
                    catchRequest(reject, reason);
                });
            });
        },
        [catchRequest, endRequest, startRequest]
    );
```

구매가 이루어질 때는 `PurchaseInventoryItems` API에 대한 호출이 이루어집니다. 이는 [use-playfab.tsx](https://github.com/PlayFab/winter-starfall/blob/main/website/src/hooks/use-playfab.ts)의 540–565행에 정의되어 있습니다.

### 아이템 판매

판매 흐름은 Azure Functions 기반의 CloudScript를 사용해 Economy 시스템의 기능을 확장하는 방법의 예시입니다.

[SellItem.cs](https://github.com/PlayFab/winter-starfall/blob/main/azure-functions/SellItem.cs)는 아이템을 판매할 때 실행되는 CloudScript 함수를 정의합니다.

49–61행은 PlayFab의 `GetTitleData API`를 사용해 스토어 배수를 반환하고 판매 가격을 계산하는 부분입니다.

```csharp theme={null}
 // Get your sell multiplier
var titleData = await PlayFabFunctions.GetTitleDataAsync(player, new List<string> { TitleDataKeys.Multipliers }, log);
var sellMultiplier = JsonConvert.DeserializeObject<Multipliers>(titleData[TitleDataKeys.Multipliers])?.sell ?? 1;
```

그런 다음 이 CloudScript 함수는 [use-store.ts](https://github.com/PlayFab/winter-starfall/blob/main/website/src/hooks/use-store.ts)의 161–187행에서 호출됩니다.

```typescript theme={null}
export function useEconomyStoreSell(): IEconomyStoreSellItemResults {
    const { isLoading, error, setError, CloudScriptExecuteFunction, EconomyGetInventoryItems } = usePlayFab();
    const dispatch = useDispatch();

    const onSell = useCallback(
        (itemId: string, amount: number) => {
            return new Promise<void>((resolve, reject) => {
                CloudScriptExecuteFunction({
                    FunctionName: "SellItem",
                    FunctionParameter: {
                        ItemId: itemId,
                        Amount: amount,
                    },
                })
                    .then(() => EconomyGetInventoryItems({ Count: SEARCH_ITEMS_MAX_COUNT }))
                    .then(data => {
                        dispatch(siteSlice.actions.inventory(data.Items));
                        resolve();
                    })
                    .catch(issue => {
                        setError(issue);
                        reject(issue);
                    });
            });
        },
        [CloudScriptExecuteFunction, EconomyGetInventoryItems, dispatch, setError]
    );
```

<Note>
  Winter Starfall을 로컬에서 실행하려면 CloudScript 함수를 지원할 Azure 계정이 필요합니다. [무료 Azure 계정에 가입](https://azure.microsoft.com/pricing/purchase-options/azure-account?msockid=1dec68fa155462cb2baa7ca6147963d7)한 다음, 위의 지침에 따라 Azure 자격 증명을 올바르게 재설정할 수 있습니다.
</Note>

## 참고 항목

* 로그인 흐름
  * [플레이어 로그인 문서](/services/playfab/identity/player-identity/login)
* 구매 흐름
  * [Economy V2 문서](/services/playfab/economy-monetization/economy-v2/overview)
  * Economy V2에 대해 더 자세히 알아보기 좋은 다음 단계는 [크래프팅 게임 튜토리얼](/services/playfab/economy-monetization/economy-v2/tutorials/craftingGame/game-context)을 시도해 보는 것입니다. 이 튜토리얼은 스토어와 인벤토리 함수를 사용하여 샘플 게임을 만드는 데 중점을 둡니다.


## Related topics

- [Winter Starfall PlayFab 데모 게임 개요](/ko/services/playfab/demo-game/overview.md)
- [Vanguard Outrider (레거시)](/ko/services/playfab/demo-game/legacy-vanguard-outrider.md)
- [실험 모범 사례 및 권장 사항](/ko/services/playfab/live-service-management/game-configuration/experiments/experimentation-keys.md)
- [PlayFab 소비 모범 사례](/ko/services/playfab/pricing/consumption-best-practices.md)
- [Insights 모범 사례](/ko/services/playfab/data-analytics/legacy/insights/best-practices.md)
