# 处理跨玩家数据

> Access and modify data for multiple players at once with Cloud Code modules.

您可以使用 Cloud Code 模块同时访问和修改多个玩家的数据。

这种功能解锁了多种用例，例如：

* 在 Cloud Save 中一次性为多个玩家更新玩家数据。
* 一次性向多个玩家发送推送通知。
* 一次性更新多个玩家的 Economy 余额。

> **Note:**
>
> **注意：** 为了降低滥用风险，请确保只有特定用户才能调用处理跨玩家数据的模块，而不是所有玩家都具有这样的能力。要限制对特定模块或其终端的访问，可以使用[访问控制](./access-control.md)。

## 获取玩家 ID##retrieve-player-ids

为了处理多个玩家的数据，您需要先调用 UGS 服务来获取玩家 ID。

为了获取玩家 ID，可以使用 `Com.Unity.Services.CloudCode.Apis` NuGet 包中的 Cloud Code C# SDK。

此外，也可以直接使用服务的 REST API。可用于获取玩家 ID 的服务包括 [Leaderboards](/leaderboards.md)、[Lobby](/lobby.md) 和 [Friends](/friends.md)。

### 使用 Leaderboards##use-leaderboards

首先，需要[创建一个排行榜](/leaderboards/get-started.md)。

例如，可以从排行榜中获取前五名玩家并保存他们的 ID。以下模块接受一个排行榜 ID 并返回前五名玩家的分数列表：

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.Leaderboards.Model;

namespace CrossPlayerData;

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

    public CrossPlayerData(ILogger<CrossPlayerData> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("GetTopPlayers")]
    public async Task<List<LeaderboardEntry>> GetTopPlayers(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId)
    {
        ApiResponse<LeaderboardScoresPage> response;
        try
        {
            response = await gameApiClient.Leaderboards.GetLeaderboardScoresAsync(ctx, ctx.ServiceToken, new Guid(ctx.ProjectId),
                    leaderboardId, null, 5);

            return response.Data.Results;

        }
        catch (ApiException e)
        {
            _logger.LogError("Failed to get top players for leaderboard: {LeaderboardId}. Error: {Error}", leaderboardId, e.Message);
            throw new Exception($"Failed to get top players for leaderboard: {leaderboardId}. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}
```

调用 `GetTopPlayers` 函数来获取前 5 名玩家的列表。响应类似如下所示：

```json
{
    "output": [
        {
            "playerId": "wYI5NW5gEVvR3PBmYXEzFS1JvSz3",
            "playerName": "IncredibleGleamingPelican#3",
            "rank": 0,
            "score": 44.0
        },
        {
            "playerId": "ryuAA3ZX23aRHN5ZJClC1Z5BrpVb",
            "playerName": "EffectiveBindingBlackberry#9",
            "rank": 1,
            "score": 1.0
        }
    ]
}
```

### 使用 Lobby##use-lobby

首先，需要[创建一个大厅](/lobby/get-started.md)。

您可以获取当前在大厅中的玩家列表，并使用他们的 ID 来处理他们的数据：

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.Lobby.Model;

namespace CrossPlayerData;

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

    public CrossPlayerData(ILogger<CrossPlayerData> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("GetLobbyPlayers")]
    public async Task<List<Player>> GetLobby(IExecutionContext ctx, IGameApiClient gameApiClient, string lobbyId)
    {
        ApiResponse<Lobby> response;
        try
        {
            response = await gameApiClient.Lobby.GetLobbyAsync(ctx, ctx.ServiceToken, lobbyId, "cloud-code");
            return response.Data.Players;
        }
        catch (ApiException e)
        {
            _logger.LogError("Failed to get players from lobby: {LobbyId}. Error: {Error}", lobbyId, e.Message);
            throw new Exception ($"Failed to get players from lobby: {lobbyId}. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}
```

调用 `GetLobbyPlayers` 函数来获取大厅中的玩家列表。

响应类似如下所示：

```json
{
    "output": [
        {
            "allocationId": null,
            "connectionInfo": null,
            "data": null,
            "id": "Z96pNb4wfgdaMLqMQfWpwXEclaRR",
            "joined": "2023-09-08T11:02:18.13Z",
            "lastUpdated": "2023-09-08T11:02:18.13Z"
        }
    ]
}
```

### 使用 Friends##use-friends

为了使用 Friends，您需要创建一个关系。如需了解更多信息，请参阅 [Friends 文档](/friends.md)。

> **Note:**
>
> 您可以使用辅助方法 `SendFriendRequest` 向玩家发送好友请求。该方法使用 `playerId` 参数将请求发送给正确的用户。

然后，可以获取玩家的好友列表，并使用他们的 ID 来处理他们的数据。以下示例显示了如何获取玩家已将好友请求发送到的玩家 ID 列表：

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.Friends.Model;

namespace CrossPlayerData;
public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

    public CrossPlayerData(IExecutionContext ctx, ILogger<CrossPlayerData> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("SendFriendRequest")]
    public async Task SendFriendRequest(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId)
    {
        try
        {
            await gameApiClient.FriendsRelationshipsApi.CreateRelationshipAsync(
                ctx, ctx.AccessToken, false, false, new AddRelationshipRequest(RelationshipType.FRIENDREQUEST, new List<MemberIdentity>
                {
                    new(id : playerId)
                }));
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to send a friend request to playerId: {playerId}. Error: {Error}", playerId, e.Message);
            throw new Exception("Failed to send a friend request to playerId: " + playerId + ". Error: " + e.Message);
        }
    }

    [CloudCodeFunction("GetRelationships")]
    public async  Task<List<Relationship>> GetRelationships(IExecutionContext ctx, IGameApiClient gameApiClient)
    {
        try
        {
            var res = await gameApiClient.FriendsRelationshipsApi.GetRelationshipsAsync(ctx, ctx.AccessToken, 10, 0, false, false);
            return res.Data;
        }
        catch (Exception e)
        {
            _logger.LogError("Failed to get relationships for playerId: {playerId}. Error: {Error}", ctx.PlayerId, e.Message);
            throw new Exception("Failed to get relationships for playerId: " + ctx.PlayerId + ". Error: " + e.Message);
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}
```

调用 `GetRelationships` 函数可以获取玩家已将好友请求发送到的玩家列表。

响应类似如下所示：

```json
{
    "output": [
        {
            "created": "2023-09-18T14:46:34.74Z",
            "expires": null,
            "id": "5774e898-a078-4f92-9f0a-4c9beeb6d1bb",
            "members": [
                {
                    "id": "0gvQingjjBwpZhkUJfeoKnFUkD4T"
                }
            ],
            "type": "FRIEND_REQUEST"
        }
    ]
}
```

## 处理玩家数据##interact-with-player-data

获得玩家 ID 列表后，您就可以使用它们通过 UGS 服务更新玩家数据。

以下示例显示了如何使用 [Cloud Save](/cloud-save.md) 和 [Economy](/economy.md) 服务来处理玩家数据。

### 以 Cloud Code 身份进行身份验证##authenticate-as-cloud-code

要处理跨玩家数据，您需要以 Cloud Code 身份进行身份验证。要以 Cloud Code 身份进行身份验证，请在模块函数中使用 `ServiceToken` 而不是 `AccessToken`：

```csharp
await gameApiClient.CloudSaveData.SetItemAsync(ctx, ctx.ServiceToken, ctx.ProjectId, player.PlayerId, new SetItemBody("ReachedTop5", true));
```

请参阅[身份验证](./authentication.md)以了解更多信息，并查看[服务和访问令牌支持](./token-support.md)以获取支持 `ServiceToken` 的服务列表。

### 使用 Cloud Save 更新前 5 名玩家数据##update-top-5-player-data-using-cloud-save

处理玩家数据的一种方法是将玩家 ID 列表传递给 Cloud Save。

示例中的模块函数将排行榜 ID 作为参数。

以下示例显示了针对从排行榜获取的前 5 名玩家列表，如何在 Cloud Save 中记录达到前 5 名的值。为了确保示例正常工作，请按照以下步骤操作：

1. 使用 [Leaderboards 服务](/leaderboards/get-started.md)创建一个排行榜。
2. 在排行榜上准备好一些玩家分数。

创建一个新模块并添加以下代码：

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.CloudSave.Model;
using Unity.Services.Leaderboards.Model;

namespace CrossPlayerData;

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

    public CrossPlayerData(ILogger<CrossPlayerData> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("GetTopPlayers")]
    public async Task<List<LeaderboardEntry>> GetTopPlayers(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId)
    {
        ApiResponse<LeaderboardScoresPage> response;
        try
        {
            response = await gameApiClient.Leaderboards.GetLeaderboardScoresAsync(ctx, ctx.ServiceToken, new Guid(ctx.ProjectId),
                    leaderboardId, null, 5);

            return response.Data.Results;

        }
        catch (ApiException e)
        {
            _logger.LogError("Failed to get top players for leaderboard: {LeaderboardId}. Error: {Error}", leaderboardId, e.Message);
            throw new Exception($"Failed to get top players for leaderboard: {leaderboardId}. Error: {e.Message}");
        }
    }

    [CloudCodeFunction("UpdateTopPlayerData")]
    public async Task UpdateTopPlayerData(IExecutionContext ctx, IGameApiClient gameApiClient, string leaderboardId)
    {
        var players = GetTopPlayers(ctx, gameApiClient, leaderboardId);
        foreach (var player in players.Result)
        {
            try
            {
                await gameApiClient.CloudSaveData.SetItemAsync(ctx, ctx.ServiceToken, ctx.ProjectId, player.PlayerId, new SetItemBody("ReachedTop5", true));
                _logger.LogInformation("Updated data for playerId {playerId}", player.PlayerId);

            } catch (Exception e)
            {
                _logger.LogError("Failed to update data for playerId {playerId}. Error: {Error}", player.PlayerId, e.Message);
                throw new Exception ($"Failed to update data for playerId: {player.PlayerId}. Error: {e.Message}");
            }
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}
```

`UpdateTopPlayerData` 函数为排行榜中的所有玩家设置一个键/值对。要验证这一点，您可以导航到 Unity Dashboard 中的 **Player Management（玩家管理）** 服务，并检查排名靠前的玩家的 Cloud Save 数据。

### 对大厅中所有使用 Economy 的玩家进行奖励##reward-all-players-in-the-lobby-using-economy

要处理玩家余额，您可以将玩家 ID 列表传递给 Economy API。

以下示例显示了如何增加从大厅中获取的玩家列表的货币余额。模块函数将大厅 ID、货币 ID 和余额增加金额作为参数。

为了确保示例正常工作，请按照以下步骤操作：

1. 使用 [Lobby 服务](/lobby/create-a-lobby.md)创建大厅。
2. 在大厅中准备好一名玩家。
3. 在 Economy 服务中[创建](/economy/add-currency.md)和[发布](/economy/publication.md)货币。

创建一个新模块并添加以下代码：

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.Economy.Model;
using Unity.Services.Lobby.Model;

public class CrossPlayerData
{
    private readonly ILogger<CrossPlayerData> _logger;

    public CrossPlayerData(ILogger<CrossPlayerData> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("GetLobbyPlayers")]
    public async Task<List<Player>> GetLobby(IExecutionContext ctx, IGameApiClient gameApiClient, string lobbyId)
    {
        ApiResponse<Lobby> response;
        try
        {
            response = await gameApiClient.Lobby.GetLobbyAsync(ctx, ctx.ServiceToken, lobbyId, "cloud-code");
            return response.Data.Players;
        }
        catch (Exception e)
        {
             _logger.LogError("Failed to get players from lobby: {LobbyId}. Error: {Error}", lobbyId, e.Message);
            throw new Exception ($"Failed to get players from lobby: {lobbyId}. Error: {e.Message}");
        }
    }

    [CloudCodeFunction("RewardLobbyPlayers")]
    public async Task RewardLobbyPlayers(IExecutionContext ctx, IGameApiClient gameApiClient, string lobbyId, string currencyId, int amount)
    {
        var players = GetLobby(ctx, gameApiClient, lobbyId);
        foreach (var player in players.Result)
        {
            try
            {
                await gameApiClient.EconomyCurrencies.IncrementPlayerCurrencyBalanceAsync(ctx, ctx.ServiceToken, ctx.ProjectId, player.Id, currencyId, new CurrencyModifyBalanceRequest(currencyId, amount));
                _logger.LogInformation("Incremented balance for playerId {playerId} by {amount}", player.Id, amount);

            } catch (Exception e)
            {
                _logger.LogError("Failed to increment {currencyId} balance for playerId {playerId}. Error: {Error}", currencyId, player.Id, e.Message);
                throw new Exception($"Failed to increment {currencyId} balance for playerId {player.Id}. Error: {e.Message}");
            }
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}
```

`RewardLobbyPlayers` 函数会增加大厅中所有玩家的货币余额。

要进行验证，您可以导航到 [Unity Dashboard](https://cloud.unity.com) 中的 **Player Management（玩家管理）** 服务，并检查玩家的余额。
