> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Exchange

ゲーム内リソースの交換機能




GS2 が提供するマイクロサービスの中でも特に重用される機能です。
あらゆるマイクロサービスのリソースを、全く異なるマイクロサービスのリソースに変換する役割を担います。

ゲームの仕様にはリソースの交換に関するものが多数存在し、その度に GS2-Exchange の出番が発生します。

## リソース交換の例

### 強化素材の変換

GS2-Inventory で管理する ★1 の強化素材 10個を、同じく GS2-Inventory で管理する ★2 の強化素材 1個と交換

### 6時間に1回アイテムを入手できる

GS2-Stamina で管理する6時間で1回復するスタミナ1を、GS2-Inventory で管理するアイテムと交換

### アイテムを売却

GS2-Inventory で管理するアイテムを、同じく GS2-Inventory で管理するゲーム内通貨と交換

## 交換の種類

GS2-Exchange はマスターデータで定められた交換レートで直ちに交換できるダイレクト交換と、
交換を実行した後、現実時間で一定時間経過したのちに交換結果を得られる非同期交換、
購入する度にコストが上昇するコスト上昇型交換の3つのモードが存在します。

```mermaid
flowchart LR
  Verify["対価検証<br/>(verifyActions)"] --> Consume["対価消費<br/>(consumeActions)"]
  Consume -->|timingType=direct| Acquire["報酬付与<br/>(acquireActions)"]
  Consume -->|timingType=await| Await["Await オブジェクト作成"]
  Await -. lockTime 経過 .-> Acquire2["AcquireAsync で報酬受取"]
```

交換の挙動はネームスペース設定の `enableDirectExchange` / `enableAwaitExchange` でモードごとに有効化できます。
レートモデルの `timingType` を `direct` または `await` に切り替えることで個別のレートが即時交換と非同期交換のいずれで動作するかを選択できます。

### 非同期交換の挙動

非同期交換を使用した場合、交換を実行したタイミングで対価は消費され、 報酬を得る代わりに Await オブジェクトが作成されます。
Await オブジェクトの作成時刻からマスターデータで定義された交換待機時間（`lockTime` 秒）が経過すると報酬を受け取ることができます。

| 状態 | フィールド |
| --- | --- |
| 交換実行時刻 | `exchangedAt` |
| 報酬受取可能時刻 | `acquirableAt` |
| 対価として消費される数量 | `count` |
| スキップで短縮された秒数 | `skipSeconds` |

#### 拠点の強化

街づくり系のゲームで、拠点を成長させるために資源を消費した後
8時間経過することで実際に拠点の経験値を加算する

#### 遠征

パーティを編成し、冒険にでたのち3時間後に冒険の結果として報酬を受け取れる

### 非同期交換のスキップ

非同期交換の時間経過待ちを、更なる対価を支払うことでスキップできるようにすることができます。
一般的にこのような仕様を入れる場合は、マネタイズのための機能として実装されることが多いですが、
現金で購入した GS2-Money で管理するゲーム内通貨を消費することで、待ち時間を時短可能な仕様を実現できます。

### 待機報酬の強制取得と削除

管理画面や API（トランザクションアクション）を通じて、待機時間を無視して報酬を強制的に取得したり、実行中の非同期交換（Await オブジェクト）を削除してキャンセルしたりすることが可能です。

### コスト上昇型交換の例

#### 強化(インフレゲーム)

強化する度に強化に必要なゴールドの消費量が増加する

#### スタミナ回復コストが増加

スタミナを購入する度に購入するために必要な課金通貨の消費量が上昇する。
購入回数は毎日リセットされる

#### コスト上昇量の計算

コストの上昇量には3つのモードが存在し、`IncrementalRateModel` の `calculateType` で指定します。

#### linear

`baseValue + (coefficientValue * 交換回数)`

例

baseValue = 100, coefficientValue = 50

交換回数 | コスト
-- | --
0 | 100
1 | 150
2 | 200
3 | 250
4 | 300

#### power

`coefficientValue * (交換回数 + 1) ^ 2`

例

coefficientValue = 50

交換回数 | コスト
-- | --
0 | 50
1 | 200
2 | 450
3 | 800
4 | 1250

#### gs2_script

GS2-Script の実行結果をもとに算出します。
複雑な条件を元にコストを計算したい場合に使用できます。

例

```lua
currentExchangeCount = args.currentExchangeCount
quantity = quantity

cost = 100
for i = 1 , quantity do
	cost = cost + (i + currentExchangeCount - 1) * 50
end

result = {
    cost=cost
}
```

交換回数 | コスト
-- | --
0 | 100
1 | 150
2 | 200
3 | 250
4 | 300

#### 交換回数の管理

`IncrementalRateModel` では `exchangeCountId` と `maximumExchangeCount` を指定して、GS2-Limit の回数制限モデルと連携し交換実行回数を追跡したり、特定期間内の交換上限を設けることができます。

`exchangeCountId` に GS2-Limit の `Counter` モデルを指定すると、交換回数が GS2-Limit でカウントされ、日次・週次など GS2-Limit のリセット仕様に従って交換回数を初期化できます。
`maximumExchangeCount` を超える回数の交換は拒否されるため、期間内の購入上限を設けたガチャや、1日に N 回まで購入可能なアイテムショップなどを実現できます。

## マスターデータ運用
マスターデータを登録することでマイクロサービスで利用可能なデータや振る舞いを設定できます。

マスターデータの種類には以下があります。

- `RateModel`: 即時または非同期交換のレート定義
- `IncrementalRateModel`: 交換回数に応じてコストが増加するレート定義

### RateModel の主なフィールド

| フィールド | 説明 |
| --- | --- |
| `name` | レート名（一意） |
| `verifyActions` | 交換実行前に行う検証アクション |
| `consumeActions` | 対価として消費するアクション |
| `acquireActions` | 報酬として付与するアクション |
| `timingType` | `direct`（即時交換）/ `await`（非同期交換） |
| `lockTime` | 非同期交換の待機秒数 |

### IncrementalRateModel の主なフィールド

| フィールド | 説明 |
| --- | --- |
| `name` | レート名（一意） |
| `consumeAction` | 対価として消費するアクション（数量はコスト計算で決定） |
| `acquireActions` | 報酬として付与するアクション |
| `calculateType` | コスト計算方式（`linear` / `power` / `gs2_script`） |
| `baseValue` / `coefficientValue` | コスト計算で使用する係数 |
| `calculateScriptId` | `gs2_script` 時に呼び出すスクリプト |
| `exchangeCountId` | 交換回数を管理する GS2-Limit のカウンタ |
| `maximumExchangeCount` | 交換回数の上限 |

### マスターデータの JSON 例

```json
{
  "version": "2019-08-19",
  "rateModels": [
    {
      "name": "material_n_to_r",
      "metadata": "N -> R 強化素材交換",
      "consumeActions": [
        {
          "action": "Gs2Inventory:ConsumeItemSetByUserId",
          "request": "{\"namespaceName\":\"inventory-0001\",\"inventoryName\":\"material\",\"itemName\":\"n-material\",\"userId\":\"#{userId}\",\"consumeCount\":10}"
        }
      ],
      "timingType": "await",
      "lockTime": 3600,
      "acquireActions": [
        {
          "action": "Gs2Inventory:AcquireItemSetByUserId",
          "request": "{\"namespaceName\":\"inventory-0001\",\"inventoryName\":\"material\",\"itemName\":\"r-material\",\"userId\":\"#{userId}\",\"acquireCount\":1}"
        }
      ]
    }
  ]
}
```

マスターデータの登録はマネージメントコンソールから登録する他、GitHubからデータを反映したり、GS2-Deployを使ってCIから登録するようなワークフローを組むことが可能です。

## スクリプトトリガー

ネームスペースに以下のスクリプト設定を追加すると、交換処理や待機報酬受取の前後でカスタムスクリプトを実行できます。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

- `exchangeScript`（完了通知: `exchangeDone`）: 交換処理の前後
- `incrementalExchangeScript`（完了通知: `incrementalExchangeDone`）: コスト上昇型交換の前後
- `acquireAwaitScript`（完了通知: `acquireAwaitDone`）: 待機報酬受取の前後

これらのスクリプトは同期・非同期の実行方式を選択でき、非同期では GS2-Script や Amazon EventBridge を介した外部連携にも対応します。

なお、`IncrementalRateModel` でコスト計算方式 `gs2_script` を選択した場合は、レート単位で設定した `calculateScriptId` を呼び出してコストを算出します。

## バフによる補正

GS2-Buff と連携すると、`RateModel` の `lockTime` や `acquireActions`・`verifyActions`・`consumeActions`、`IncrementalRateModel` の `acquireActions`・`consumeAction`・`maximumExchangeCount` をコンテキストスタック経由で動的に上書きでき、イベントやキャンペーンに応じて報酬や待機時間、交換可能回数を柔軟に調整できます。

たとえば、期間限定で「強化素材交換の待機時間を半減」「ガチャの1日購入上限を増加」といった施策を、マスターデータを書き換えずにバフの適用だけで実現できます。

## 実装例

### 交換の実行（ダイレクト）

`config` パラメータには、交換に紐づく対価・報酬アクションのトランザクションで使用するコンテキスト値を渡すことができます。



**Unity**
```csharp

    var result = await gs2.Exchange.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Exchange(
    ).ExchangeAsync(
        rateName: "rate-0001",
        count: 1
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Exchange->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Exchange(
    );
    const auto Future = Domain->Exchange(
        "rate-0001", // rateName
        1, // count
        nullptr // config
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### 非同期交換の開始

`timingType=await` のレートに対して `ExchangeAsync` を呼び出すと、対価は即座に消費され、報酬は Await オブジェクトに保留されます。



**Unity**
```csharp

    var result = await gs2.Exchange.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Exchange(
    ).ExchangeAsync(
        rateName: "material_n_to_r",
        count: 1
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Exchange->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Exchange(
    );
    const auto Future = Domain->Exchange(
        "material_n_to_r", // rateName
        1, // count
        nullptr // config
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### Await の一覧取得



**Unity**
```csharp

    var items = await gs2.Exchange.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).AwaitsAsync(
    ).ToListAsync();
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Exchange->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto It = Domain->Awaits(
    );
    TArray<Gs2::UE5::Exchange::Model::FEzAwaitPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
```


### Await 待機時間経過後の報酬受け取り

待機時間が経過した Await から報酬を受け取ります。
受け取り処理ではトランザクションが発行され、`acquireActions` に定義された報酬付与処理が他のマイクロサービスに対して実行されます。



**Unity**
```csharp

    var result = await gs2.Exchange.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Await(
        rateName: "material_n_to_r",
        awaitName: "await-0001"
    ).AcquireAsync(
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Exchange->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Await(
        "await-0001", // awaitName
        "material_n_to_r" // rateName
    );
    const auto Future = Domain->Acquire(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### Await の削除（キャンセル）

待機中の Await を破棄して交換をキャンセルします。
対価として消費したリソースは返却されない点に注意してください。返却が必要な場合はトランザクションアクションを利用したサーバー側スクリプトで補填してください。



**Unity**
```csharp

    var result = await gs2.Exchange.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Await(
        rateName: "material_n_to_r",
        awaitName: "await-0001"
    ).DeleteAwaitAsync(
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Exchange->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Await(
        "await-0001", // awaitName
        "material_n_to_r" // rateName
    );
    const auto Future = Domain->DeleteAwait(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


### 待機時間のスキップ

待機時間を追加コストの支払いでスキップする場合は、`skipByConfig` を用いてスキップに必要なコストを設定したうえで、サーバーサイド（GS2-JobQueue や GS2-Script 経由）の API を呼び出します。
スキップ処理はトランザクションアクションとしても利用できるため、ショップでのスキップアイテムやスキップチケットといった商品設計と組み合わせることができます。

### コスト上昇型交換の実行



**Unity**
```csharp

    var result = await gs2.Exchange.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Exchange(
    ).IncrementalExchangeAsync(
        rateName: "rate-0001",
        count: 1
    );
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Exchange->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Exchange(
    );
    const auto Future = Domain->IncrementalExchange(
        "rate-0001", // rateName
        1, // count
        nullptr // config
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```


## トランザクションアクション

GS2-Exchange では以下のトランザクションアクションを提供しています。

- 消費アクション: 実行中の交換待機（Await）の削除
- 入手アクション: 直ちに交換（Exchange）を実行、コスト上昇型交換（IncrementalExchange）を実行、交換待機（Await）の作成、待機中報酬の強制取得、待機時間のスキップ

「報酬の強制取得」を入手アクションとして利用することで、特定のアイテムを入手した際やミッション達成時の報酬として、現在進行中の建築や遠征（非同期交換）を即座に完了させるといった処理が可能になります。また、「交換待機の削除」を消費アクションとして利用することで、進行中のプロセスを中断（キャンセル）させるといった運用も可能です。

## 詳細なリファレンス

[GS2-Exchange API リファレンス](../../api_reference/exchange)



