# クロスプレイヤーデータの操作

> 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) する必要があります。

例えば、リーダーボードから上位 5 人のプレイヤーを取得し、その ID を保存できます。以下のモジュールは、リーダーボード ID を取り込み、上位 5 人のプレイヤーのスコアのリストを返します。

```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 として認証するには、モジュール関数内で `AccessToken` の代わりに `ServiceToken` を使用します。

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

[認証](./authentication.md) で詳細情報を参照し、`ServiceToken` をサポートするサービスのリストについては [サービスとアクセストークンのサポート](./token-support.md) の説明を確認してください。

### Cloud Save を使用した上位 5 人のプレイヤーデータの更新##update-top-5-player-data-using-cloud-save

プレイヤーデータを操作する 1 つの方法は、プレイヤー ID のリストを Cloud Save に渡すことです。

サンプル内のモジュール関数は、リーダーボード ID をパラメーターとして受け取ります。

以下のサンプルは、リーダーボードから取得した上位 5 人のプレイヤーのリストについて上位 5 人に到達するための値を Cloud Save に記録する方法を示します。サンプルが動作するためには、以下のステップに従います。

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** サービスに移動し、プレイヤーの残高を調べることができます。
