> ## 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 门户的 shell 中运行以下命令：

`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]);
```

该游戏提供了 3 种可恢复的玩家认证方式：邮件、Google 和 Facebook，从而确保玩家账号永远不会丢失。更多信息请参阅[登录最佳实践](/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;
```

随后在 [use-store.ts](https://github.com/PlayFab/winter-starfall/blob/main/website/src/hooks/use-store.ts) 的第 161 - 187 行调用了该 CloudScript 函数。

```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，你需要一个 Azure 账号以支持 CloudScript 函数。你可以[注册一个免费的 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 演示游戏概览](/zh-CN/services/playfab/demo-game/overview.md)
- [Vanguard Outrider（旧版）](/zh-CN/services/playfab/demo-game/legacy-vanguard-outrider.md)
- [最佳实践](/zh-CN/services/xbox-services/develop/best-practices/index.md)
- [Insights 最佳实践](/zh-CN/services/playfab/data-analytics/legacy/insights/best-practices.md)
- [XBOX services 配置摘要页面](/zh-CN/services/xbox-services/fundamentals/portal-config/live-portal-summary-tab.md)
