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

# Economy v2 のドロップ テーブルとランダム化されたルート

> Title Data、Azure Functions、および ExecuteInventoryOperations を使用して PlayFab Economy v2 で重み付けランダム ルート ドロップを構築し、アトミックにアイテムを付与します。

<Info>
  Economy v2 が一般提供 (GA) されました。サポートとフィードバックについては、[PlayFab フォーラム](https://community.playfab.com) を参照してください。
</Info>

Economy v1 (レガシー) には、Game Manager で直接、重み付けされたランダム アイテム分配を定義できる組み込みの [ドロップ テーブル](/services/playfab/economy-monetization/economy/tutorials/drop-tables) 機能がありました。Economy v2 はドロップ テーブルをネイティブ機能として含みませんが、[Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview) を [ExecuteInventoryOperations](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations) API と組み合わせて、同じ動作を、より柔軟に実装できます。

このチュートリアルでは、次のようなランダム化されたルート システム全体を構築する方法を説明します。

* [Title Data](/services/playfab/live-service-management/game-configuration/titledata) に、重み付けされたアイテム プールを持つルート テーブルを定義します。
* Azure Function を使用して、ルート テーブルからランダムなアイテムをロールします。
* 単一の呼び出しで、コンテナ トークンをアトミックに減算し、ランダムに選択されたアイテムを付与します。

## 仕組み

1. **Title Data にルート テーブルを定義**: ドロップ テーブル構成 (アイテム ID、重み、数量) を JSON として Title Data に格納すると、コードを再デプロイせずに更新できます。
2. **コンテナを仮想通貨として表す**: [固定コンテンツを持つコンテナ](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents) と同じコンテナ トークン パターンを使用します: 未開封のルート ボックスとして機能する仮想通貨です。
3. **Azure Function を呼び出してコンテナを開く**: 関数はルート テーブルを読み取り、重み付けランダム選択を実行し、`ExecuteInventoryOperations` を呼び出してアトミックに 1 個のコンテナ トークンを減算し、ランダムに選択されたアイテムを追加します。

## 前提条件

* [PlayFab 開発者アカウント](https://developer.playfab.com/en-us/sign-up)。
* [Game Manager](https://developer.playfab.com/) で作成されたタイトル。
* ルート報酬として付与できる公開済みのカタログ アイテム。
* コンテナを表す仮想通貨 (セットアップ手順については [固定コンテンツを持つコンテナ](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-1-create-a-virtual-currency-for-the-container) を参照)。
* PlayFab タイトルに接続された [Azure Functions](/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart) プロジェクト。

## ステップ 1: Title Data にルート テーブルを定義する

デザイナーがコードの変更なしにドロップ率を更新できるように、ルート テーブル定義を [Title Data](/services/playfab/live-service-management/game-configuration/titledata) に格納します。

### ルート テーブル構造の例

`LootTables` という Title Data キーを JSON 値で作成します。

```json theme={null}
{
  "TreasureChest": {
    "Rolls": 1,
    "Items": [
      { "ItemId": "{{CommonSwordID}}", "Weight": 1000, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{UncommonSwordID}}", "Weight": 100, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{RareSwordID}}", "Weight": 10, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{LegendarySwordID}}", "Weight": 1, "MinQuantity": 1, "MaxQuantity": 1 }
    ]
  },
  "SupplyCrate": {
    "Rolls": 3,
    "Items": [
      { "ItemId": "{{HealthPotionID}}", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 5 },
      { "ItemId": "{{ManaPotionID}}", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 3 },
      { "ItemId": "{{GoldCurrencyID}}", "Weight": 80, "MinQuantity": 10, "MaxQuantity": 100 },
      { "ItemId": "{{RareGemID}}", "Weight": 5, "MinQuantity": 1, "MaxQuantity": 1 }
    ]
  }
}
```

各ルート テーブルには次のプロパティが含まれます。

| プロパティ                         | 説明                                    |
| ----------------------------- | ------------------------------------- |
| **Rolls**                     | コンテナが開かれた際にテーブルでロールする回数。              |
| **Items**                     | 可能な報酬のプール。それぞれに重みと数量範囲があります。          |
| **Weight**                    | 相対的な確率。重みが高いほど、アイテムがドロップする可能性が高くなります。 |
| **MinQuantity / MaxQuantity** | ロールごとに付与するそのアイテムの数量の範囲。               |

### Title Data の設定

#### Game Manager

1. **Content** > **Title Data** に移動します。
2. `LootTables` というキーを作成します。
3. 値として JSON ルート テーブル構成を貼り付けます。
4. **Save** を選択します。

#### API

[SetTitleData](https://learn.microsoft.com/en-us/rest/api/playfab/server/title-wide-data-management/set-title-data) を使用します。

```json theme={null}
{
  "Key": "LootTables",
  "Value": "{\"TreasureChest\":{\"Rolls\":1,\"Items\":[...]}}"
}
```

***

## ステップ 2: Azure Function を作成する

プレイヤーがコンテナを開きたい場合にゲーム クライアントが呼び出す Azure Function を作成します。この関数は次のようになります。

1. プレイヤーが少なくとも 1 個のコンテナ トークンを持っていることを確認します。
2. Title Data からルート テーブルを読み取ります。
3. 重み付けランダム選択を実行します。
4. `ExecuteInventoryOperations` を呼び出して、アトミックにトークンを減算し、報酬を付与します。

### データ モデル

```csharp theme={null}
public class OpenContainerRequest
{
    public string LootTableId { get; set; }  // 例: "TreasureChest"
}

public class LootTable
{
    public int Rolls { get; set; }
    public List<LootTableEntry> Items { get; set; }
}

public class LootTableEntry
{
    public string ItemId { get; set; }
    public int Weight { get; set; }
    public int MinQuantity { get; set; }
    public int MaxQuantity { get; set; }
}
```

### 重み付けランダム選択

```csharp theme={null}
public static LootTableEntry RollOnTable(LootTable table, Random rng)
{
    int totalWeight = table.Items.Sum(e => e.Weight);
    int roll = rng.Next(0, totalWeight);

    int cumulative = 0;
    foreach (var entry in table.Items)
    {
        cumulative += entry.Weight;
        if (roll < cumulative)
            return entry;
    }

    // フォールバック (ここには到達しないはず)
    return table.Items.Last();
}
```

### 完全な Azure Function

```csharp theme={null}
[FunctionName("OpenLootContainer")]
public static async Task<dynamic> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
    ILogger log)
{
    // 1. PlayFab コンテキストとリクエストを解析する
    var context = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(
        await req.ReadAsStringAsync());
    var args = context.FunctionArgument != null
        ? JsonConvert.DeserializeObject<OpenContainerRequest>(
            context.FunctionArgument.ToString())
        : null;

    if (args == null || string.IsNullOrEmpty(args.LootTableId))
        return new { Error = "LootTableId is required." };

    var playerEntity = context.CallerEntityProfile.Entity;
    var settings = new PlayFabApiSettings
    {
        TitleId = context.TitleAuthenticationContext.Id,
        DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY")
    };
    var authContext = new PlayFabAuthenticationContext
    {
        EntityToken = context.TitleAuthenticationContext.EntityToken
    };

    var serverApi = new PlayFabServerInstanceAPI(settings);
    var economyApi = new PlayFabEconomyInstanceAPI(settings, authContext);

    // 2. Title Data からルート テーブルを読み取る
    var titleDataResult = await serverApi.GetTitleDataAsync(new GetTitleDataRequest
    {
        Keys = new List<string> { "LootTables" }
    });

    if (!titleDataResult.Result.Data.ContainsKey("LootTables"))
        return new { Error = "LootTables not found in Title Data." };

    var allTables = JsonConvert.DeserializeObject<Dictionary<string, LootTable>>(
        titleDataResult.Result.Data["LootTables"]);

    if (!allTables.ContainsKey(args.LootTableId))
        return new { Error = $"Loot table '{args.LootTableId}' not found." };

    var lootTable = allTables[args.LootTableId];

    // 3. 報酬のためロールする
    var rng = new Random();
    var rewards = new Dictionary<string, int>(); // ItemId -> 合計数量

    for (int i = 0; i < lootTable.Rolls; i++)
    {
        var entry = RollOnTable(lootTable, rng);
        int quantity = rng.Next(entry.MinQuantity, entry.MaxQuantity + 1);

        if (rewards.ContainsKey(entry.ItemId))
            rewards[entry.ItemId] += quantity;
        else
            rewards[entry.ItemId] = quantity;
    }

    // 4. アトミック操作を構築: 1 個のコンテナ トークンを減算 + 報酬を追加
    var operations = new List<InventoryOperation>();

    // コンテナ トークンを減算
    // コンテナ通貨 ID は LootTableId のマッピングと一致する必要があります
    // このマッピングは Title Data に格納するか、命名規則を使用してください
    operations.Add(new InventoryOperation
    {
        Subtract = new SubtractInventoryItemsOperation
        {
            Item = new InventoryItemReference
            {
                Id = GetContainerCurrencyId(args.LootTableId) // マッピング ロジック
            },
            Amount = 1
        }
    });

    // 各報酬を追加
    foreach (var reward in rewards)
    {
        operations.Add(new InventoryOperation
        {
            Add = new AddInventoryItemsOperation
            {
                Item = new InventoryItemReference { Id = reward.Key },
                Amount = reward.Value
            }
        });
    }

    // 5. アトミックに実行
    var executeResult = await economyApi.ExecuteInventoryOperationsAsync(
        new ExecuteInventoryOperationsRequest
        {
            Entity = new EntityKey
            {
                Id = playerEntity.Id,
                Type = playerEntity.Type
            },
            Operations = operations,
            IdempotencyId = Guid.NewGuid().ToString()
        });

    if (executeResult.Error != null)
    {
        log.LogError($"Failed to execute operations: {executeResult.Error.ErrorMessage}");
        return new { Error = executeResult.Error.ErrorMessage };
    }

    log.LogInformation($"Player {playerEntity.Id} opened {args.LootTableId}, received: " +
        string.Join(", ", rewards.Select(r => $"{r.Value}x {r.Key}")));

    return new { Rewards = rewards, TransactionIds = executeResult.Result.TransactionIds };
}
```

<Note>
  `GetContainerCurrencyId` ヘルパー関数は、ルート テーブル名を対応するコンテナ通貨 ID にマッピングします。このマッピングはルート テーブルと一緒に Title Data に格納するか、命名規則を使用できます。
</Note>

## ステップ 3: コンテナ トークンをプレイヤーに付与する

ゲームプレイ、ストア購入、または報酬を通じてプレイヤーにコンテナ トークンを付与します。[固定コンテンツを持つコンテナ](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents#step-3-grant-container-tokens-to-players) で説明されているのと同じアプローチを使用します。

## ステップ 4: ゲーム クライアントから関数を呼び出す

プレイヤーがコンテナを開きたい場合、[ExecuteFunction](https://learn.microsoft.com/en-us/rest/api/playfab/cloudscript/server-side-cloud-script/execute-function) を使用して Azure Function を呼び出します。

```json theme={null}
{
    "FunctionName": "OpenLootContainer",
    "FunctionParameter": {
        "LootTableId": "TreasureChest"
    },
    "GeneratePlayStreamEvent": true
}
```

レスポンスには、ランダムに選択された報酬が含まれます。

```json theme={null}
{
    "FunctionResult": {
        "Rewards": {
            "{{UncommonSwordID}}": 1
        },
        "TransactionIds": ["txn-abc-123"]
    }
}
```

## ルート テーブルの設計パターン

### レアリティ層

指数関数的に減少する重みを持つクラシックなレアリティ システム:

```json theme={null}
{
  "Items": [
    { "ItemId": "CommonItem",     "Weight": 1000, "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "UncommonItem",   "Weight": 100,  "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "RareItem",       "Weight": 10,   "MinQuantity": 1, "MaxQuantity": 1 },
    { "ItemId": "LegendaryItem",  "Weight": 1,    "MinQuantity": 1, "MaxQuantity": 1 }
  ]
}
```

ドロップ確率:

* Common: 約 90.0%
* Uncommon: 約 9.0%
* Rare: 約 0.9%
* Legendary: 約 0.09%

### 混合報酬でのマルチロール

混合プールから 3 個のランダム アイテムを与える supply crate:

```json theme={null}
{
  "Rolls": 3,
  "Items": [
    { "ItemId": "{{HealthPotionID}}", "Weight": 40, "MinQuantity": 1, "MaxQuantity": 3 },
    { "ItemId": "{{ManaPotionID}}",   "Weight": 40, "MinQuantity": 1, "MaxQuantity": 3 },
    { "ItemId": "{{GoldCurrencyID}}", "Weight": 30, "MinQuantity": 10, "MaxQuantity": 50 },
    { "ItemId": "{{RareGemID}}",      "Weight": 5,  "MinQuantity": 1, "MaxQuantity": 1 }
  ]
}
```

### 保証付き + ランダム (ピティ システム)

"10 回開くごとに Rare 以上を保証" というパターンには、プレイヤーの開封回数を [表示プロパティ](/services/playfab/economy-monetization/economy-v2/inventory#display-properties) または [プレイヤー データ](/services/playfab/player-progression/player-data/quickstart) で追跡し、しきい値に達した際に Azure Function 内でランダム選択を上書きします。

### ネストされたドロップ テーブル

ルート テーブルは ID で別のルート テーブルを参照できます。Azure Function は参照を再帰的に解決します。

```json theme={null}
{
  "BossDropTable": {
    "Rolls": 2,
    "Items": [
      { "ItemId": "TABLE:WeaponDropTable", "Weight": 50, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "TABLE:ArmorDropTable",  "Weight": 30, "MinQuantity": 1, "MaxQuantity": 1 },
      { "ItemId": "{{GoldCurrencyID}}",    "Weight": 80, "MinQuantity": 50, "MaxQuantity": 200 }
    ]
  }
}
```

関数は `ItemId` が `TABLE:` で始まるかどうかを確認し、そうであればアイテムを直接付与するのではなく、参照されたテーブルでロールします。

## セキュリティに関する考慮事項

* **ルート ロジックは常に Azure Function 内でサーバー側で実行してください。** ロールされたアイテムを決定するためにクライアントを信頼しないでください。このアプローチにより、操作を防止します。
* **`ExecuteInventoryOperations` の呼び出しで `IdempotencyId` を使用します。** 関数が再試行された場合の重複付与を防ぎます。詳細については、[べき等な取引と再試行](/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries) を参照してください。
* **減算する前にプレイヤーのインベントリを確認して、コンテナの所有権を検証してください。** `ExecuteInventoryOperations` の `Subtract` 操作は、プレイヤーが十分なトークンを持っていない場合に失敗し、バッチ全体がロールバックされます。
* **監査のためにすべてのロールをログに記録してください。** 関数の戻り値と PlayStream イベントにより、各プレイヤーが受け取ったものの証跡が得られます。

## 関連項目

* [固定コンテンツを持つコンテナ](/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents)
* [ExecuteInventoryOperations API リファレンス](https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/execute-inventory-operations)
* [べき等な取引と再試行](/services/playfab/economy-monetization/economy-v2/tutorials/idempotent-transactions-and-retries)
* [Title Data](/services/playfab/live-service-management/game-configuration/titledata)
* [Azure Functions のクイックスタート](/services/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart)
* [ドロップ テーブル (レガシー Economy)](/services/playfab/economy-monetization/economy/tutorials/drop-tables)
* [Economy v2 の概要](/services/playfab/economy-monetization/economy-v2/overview)


## Related topics

- [Economy v2 の固定コンテンツのコンテナ](/ja-jp/services/playfab/economy-monetization/economy-v2/tutorials/containers-with-fixed-contents.md)
- [Economy v2 の概要](/ja-jp/services/playfab/economy-monetization/economy-v2/overview.md)
- [Economy (Legacy) のドロップ テーブル](/ja-jp/services/playfab/economy-monetization/economy/tutorials/drop-tables.md)
- [カタログ (レガシー Economy)](/ja-jp/services/playfab/economy-monetization/economy/items/catalogs.md)
- [プライズ テーブルの使用](/ja-jp/services/playfab/community/leaderboards/tournaments-leaderboards/using-prize-tables.md)
