Game Server Services で所持品を管理

# 前回までのあらすじ

Game Server Services の登録の手順、SDKのセットアップ手順、アカウントの作成手順は [こちら](https://qiita.com/kazutomo/items/2a0091ad5ce187ebb528)

# アイテムのマスターデータについて考える

GS2 では厳密にはマスターデータではなく、モデルデータと呼びます。
2種類のモデルデータを用意する必要があります。

## InventoryModel

| パラメータ |	データ型 |	必須 |	説明 |
|---|---|---|---|
| name |	string |	true |	名前 |
| metadata |	string | 	false | 	メタデータ |
| initialCapacity | 	integer | 	true | 	初期サイズ |
| maxCapacity | 	integer | 	true | 	最大サイズ |

GS2全体でのルールになりますが、名前には英数字しか指定できませんし、後から変更することもできません。
GS2におけるIDはURLのような形式になっており、これは名前がパスを構成する要素として使用されるためです。

インベントリモデルはアイテムの種類をざっくりとまとめた単位です。
『キャラクター』『強化素材』『消費アイテム』『通貨』といった単位で用意するのがいいでしょう。

インベントリには容量があります。キャラクターであれば50体までしか所有できない。とかそういうのです。
ただ、容量は拡張できるようになっています。そのため、モデルデータの設定値としては『初期値』と『最大値』が設定できます。

メタデータには任意の文字列を記録できます。
どんな形式でも構いませんがJSON形式で使うと、項目の追加ができて便利でしょう。

## ItemModel

| パラメータ |	データ型 |	必須 |	説明 |
|---|---|---|---|
| name |	string |	true |	アイテムモデルの種類名 |
| metadata |	string |	false |	アイテムモデルの種類のメタデータ |
| stackingLimit |	long |	true |	スタック可能な最大数量 |
| allowMultipleStacks |	boolean |	true |	スタック可能な最大数量を超えた時複数枠にアイテムを保管することを許すか |
| sortValue |	integer |	true |	表示順番 |

アイテムモデルはアイテムについて具体的に記述できます。

スタックというのは同種のアイテムを1枠で複数持てるようにする仕組みです。
『やくそう×99』みたいなのを想像してもらえればOKです。

表示順番は小さい値ほどリスト取得時に先に応答されます。

メタデータには任意の文字列を記録できます。
例えば、アイコンのファイル名などを入れておくと便利かもしれませんし、GS2-Enhance と組み合わせるときには強化素材として使用したときの経験値量を記録したりもします。
どんな形式でも構いませんがJSON形式で使うと、項目の追加ができて便利でしょう。

# ユースケースっぽくモデリングしてみる

## InventoryModel

| name |	metadata |	initialCapacity |	maxCapacity |
|---|---|---|---|
| Character |	{"displayName": "キャラクター"} |	50 |	100 |
| Material |	{"displayName": "強化素材"}| 	3 | 	3 |

## ItemModel

### character 

| name |	metadata |	stackingLimit |	allowMultipleStacks | sortValue|
|---|---|---|---| ---| 
| Taro |	{"displayName": "太郎", "rarity": 1, "element": "fire"} | 1 |	true |	1 |
| Hanako |	{"displayName": "花子", "rarity": 1, "element": "water"}| 1 | 	true | 2 |
| Jiro |	{"displayName": "次郎", "rarity": 1, "element": "leaf"}| 1 | 	true | 3 |
| Shiori |	{"displayName": "詩織", "rarity": 2, "element": "fire"}| 1 | 	true | 4 |
| Saburo |	{"displayName": "三郎", "rarity": 2, "element": "water"}| 1 | 	true | 5 |
| Shinobu |	{"displayName": "しのぶ", "rarity": 2, "element": "leaf"} | 1 |	true |	6 |

### material

| name |	metadata |	stackingLimit |	allowMultipleStacks | sortValue|
|---|---|---|---| ---| 
| Fire |	{"displayName": "炎のかけら", "element": "fire"} | 9999 |	false |	1 |
| Water |	{"displayName": "水のかけら", "element": "water"} | 9999 |	false |	2 |
| Leaf |	{"displayName": "葉のかけら", "element": "leaf"} | 9999 |	false |	3 |

# モデルデータを登録してみよう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/66b0b0d2-7f58-590a-9918-006b89b1ff62.png)

ゲームプレイヤー > Inventory を選択します。

## ネームスペースの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/72df0eee-bcd9-50b1-0910-7f194e3ee646.png)

ネームスペースを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/47c74943-a67c-7b69-cc5b-b707dc0c1aba.png)

## インベントリモデルの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/444af2ab-b35f-4263-bfd0-3fdb8e435c6a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/33a69ab2-2c82-4443-d4cc-d1ae8c552290.png)

## アイテムモデルの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/2de267cd-adb8-1bde-2845-b627d6ef8a5b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/ffc7a203-2496-a54b-11dd-d1f99c3df626.png)

# モデルデータの反映

実はここでポチポチしても直ちには設定が反映されません。
これには色々な理由がありますが、わかりやすい理由としては複数の要素を変更する必要があるときに、慌てなくてもいいようにです。
もう一つの理由は、現実問題として管理画面からポチポチアイテムを登録したくないからです。

この問題への解決方法として、GS2では実際にゲーム向けの挙動に使用されるモデルデータは、JSON ファイルをアップロードして反映する。というアプローチをとっています。
単一のJSONファイルには複数のインベントリモデルやアイテムモデルが含まれているので、まとめて結果を反映できますし、最終的にJSONファイルさえ作れれば手段は問いません。

方法の一つとしてマネージメントコンソールでポチポチ登録してエクスポートする。というものがありますが、スプレッドシートから出力したほうが実際にゲームを運営するときには便利でしょう。

## エクスポート

せっかく登録してしまったので、試しに現在の状態でエクスポートしてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/d82ae0eb-1d16-b7f6-46be-a6ca5108f349.png)

```json
{
  "version": "2019-02-05",
  "inventoryModels": [
    {
      "name": "Character",
      "metadata": "{}",
      "initialCapacity": 50,
      "maxCapacity": 100,
      "protectReferencedItem": false,
      "itemModels": [
        {
          "name": "Taro",
          "metadata": "{\"displayName\": \"\u592a\u90ce\", \"rarity\": 1, \"element\": \"fire\"}",
          "stackingLimit": 1,
          "allowMultipleStacks": true,
          "sortValue": 1
        }
      ]
    }
  ]
}
```

それらしいデータが出力されています。
ちなみに、マスターデータのフォーマットは [こちら](https://app.gs2.io/docs/sdk/inventory.html#currentitemmodelmaster) で確認できます。

## マスターデータをアップロード

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/209b22e0-d3ed-6502-1aa1-77fdf00cb65c.png)

このボタンをクリックすると、JSONファイルの選択ダイアログが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100723/253d93d0-f416-20b4-bfe1-febea7d6983b.png)

アップロードが完了すると、現在有効なマスターデータに結果が反映されています。
これでゲーム内から呼び出す準備が整いました。

# 待って、アイテムの種類が少ない

JSONをそのまま書き換えてマスターデータを準備してもいいのですが、人間にはJSONよりふさわしい言語があります！そうYAMLです！
あれ、YAMLってどこかで出てきましたね？ そうです！ GS2-Deploy です。

GS2-Deploy を使ってアイテムマスターを作成するところまでババーっと進めてみます。

# GS2-Deploy テンプレート

```yaml
GS2TemplateFormatVersion: "2019-05-01"

Resources:
  InventoryNamespace:
    Type: GS2::Inventory::Namespace
    Properties:
      Name: ItemBox

  InventorySettings:
    Type: GS2::Inventory::CurrentItemModelMaster
    Properties:
      NamespaceName: ItemBox
      Settings:
        version: "2019-02-05"
        inventoryModels:
          - name: Character
            initialCapacity: 50
            maxCapacity: 100
            protectReferencedItem: false
            itemModels:
              - name: Taro
                metadata:
                  displayName: 太郎
                  rarity: 1
                  element: fire
                stackingLimit: 1
                allowMultipleStacks: true
                sortValue: 1
              - name: Hanako
                metadata:
                  displayName: 花子
                  rarity: 1
                  element: water
                stackingLimit: 1
                allowMultipleStacks: true
                sortValue: 2
              - name: Jiro
                metadata:
                  displayName: 次郎
                  rarity: 1
                  element: leaf
                stackingLimit: 1
                allowMultipleStacks: true
                sortValue: 3
              - name: Shiori
                metadata:
                  displayName: 詩織
                  rarity: 2
                  element: fire
                stackingLimit: 1
                allowMultipleStacks: true
                sortValue: 4
              - name: Saburo
                metadata:
                  displayName: 三郎
                  rarity: 2
                  element: water
                stackingLimit: 1
                allowMultipleStacks: true
                sortValue: 5
              - name: Shinobu
                metadata:
                  displayName: しのぶ
                  rarity: 2
                  element: leaf
                stackingLimit: 1
                allowMultipleStacks: true
                sortValue: 6
          - name: Material
            initialCapacity: 3
            maxCapacity: 3
            protectReferencedItem: false
            itemModels:
              - name: Fire
                metadata:
                  displayName: 炎のかけら
                  element: fire
                stackingLimit: 9999
                allowMultipleStacks: false
                sortValue: 1
              - name: Water
                metadata:
                  displayName: 水のかけら
                  element: water
                stackingLimit: 9999
                allowMultipleStacks: false
                sortValue: 2
              - name: Leaf
                metadata:
                  displayName: 葉のかけら
                  element: leaf
                stackingLimit: 9999
                allowMultipleStacks: false
                sortValue: 3
    DependsOn:
      - InventoryNamespace
```

Settings 以下や、メタデータ以下は気にせず構造化データを書くと最終的にJSONとして処理されます。便利ですね。

長くなりましたが、このテンプレートで GS2-Deploy のスタックを作成すると、ネームスペースの作成から、現在有効なマスターデータの反映までが一発で出来上がりです。

# プログラムから操作してみる

## インベントリモデルの一覧を取得

```csharp

yield return gs2.Inventory.ListInventoryModels(
    r => {
        if (r.Error != null)
        {
            // エラーが発生した場合に到達
            // r.Error は発生した例外オブジェクトが格納されている
        }
        else
        {
            Debug.Log(r.Result.Items); // list[InventoryModel] インベントリモデルのリスト
        }
    },
    gameSession,    // GameSession ログイン状態を表すセッションオブジェクト
    "ItemBox"   //  ネームスペース名
);
```

## アイテムモデルの一覧を取得

```csharp

yield return gs2.Inventory.ListItemModels(
    r => {
        if (r.Error != null)
        {
            // エラーが発生した場合に到達
            // r.Error は発生した例外オブジェクトが格納されている
        }
        else
        {
            Debug.Log(r.Result.Items); // list[ItemModel] アイテムモデルのリスト
        }
    },
    gameSession,    // GameSession ログイン状態を表すセッションオブジェクト
    "ItemBox",   //  ネームスペース名
    "Character"   //  インベントリの種類名
);
```

## 自分のインベントリを取得

```csharp
yield return gs2.Inventory.GetInventory(
    r => {
        if (r.Error != null)
        {
            // エラーが発生した場合に到達
            // r.Error は発生した例外オブジェクトが格納されている
        }
        else
        {
            Debug.Log(r.Result.Item.InventoryId); // string インベントリ
            Debug.Log(r.Result.Item.InventoryName); // string インベントリモデル名
            Debug.Log(r.Result.Item.CurrentInventoryCapacityUsage); // integer 現在のインベントリのキャパシティ使用量
            Debug.Log(r.Result.Item.CurrentInventoryMaxCapacity); // integer 現在のインベントリの最大キャパシティ
        }
    },
    gameSession,    // GameSession ログイン状態を表すセッションオブジェクト
    namespaceName,   //  ネームスペース名
    "ItemBox"   //  インベントリの種類名
);
```

キャパシティーの状態が取得できます。

## インベントリ内のアイテム一覧を取得

```cs
yield return gs2.Inventory.ListItems(
    r => {
        if (r.Error != null)
        {
            // エラーが発生した場合に到達
            // r.Error は発生した例外オブジェクトが格納されている
        }
        else
        {
            Debug.Log(r.Result.Items); // list[ItemSet] 有効期限ごとのアイテム所持数量のリスト
            Debug.Log(r.Result.NextPageToken); // string リストの続きを取得するためのページトークン
        }
    },
    gameSession,    // GameSession ログイン状態を表すセッションオブジェクト
    "ItemBox",   //  ネームスペース名
    "Character",   //  インベントリの種類名
    pageToken,   //  データの取得を開始する位置を指定するトークン(オプション値)
    limit   //  データの取得件数(オプション値)
);
```


## アイテムを入手

なんと、アイテムを入手するAPIは（GS2-SDK for Unity には）用意されていません。
理由は単純で、アイテムを無限に増やせるようになってしまうからです。

では、どうするか？一番簡単な方法はマネージメントコンソールを使用することです。
しかし、ゲームのメカニズムに組み込むには適していません。
ゲームのメカニズムに組み込む『アイテムを入手』する操作は『クエストのクリア報酬』や『ショップでのアイテム購入』となります。

GS2では必ず何か対価を払った報酬としてメリットを得るようにすることでチート行為が行えないように設計します。

この後の消費に備えて、マネージメントコンソールでアイテムを追加してみましょう。

## アイテムを消費

```cs
yield return gs2.Inventory.Consume(
    r => {
        if (r.Error != null)
        {
            // エラーが発生した場合に到達
            // r.Error は発生した例外オブジェクトが格納されている
        }
        else
        {
            Debug.Log(r.Result.Items); // list[ItemSet] 消費後の有効期限ごとのアイテム所持数量のリスト
            Debug.Log(r.Result.ItemModel.Name); // string アイテムモデルの種類名
            Debug.Log(r.Result.ItemModel.Metadata); // string アイテムモデルの種類のメタデータ
            Debug.Log(r.Result.ItemModel.StackingLimit); // long スタック可能な最大数量
            Debug.Log(r.Result.ItemModel.AllowMultipleStacks); // boolean スタック可能な最大数量を超えた時複数枠にアイテムを保管することを許すか
            Debug.Log(r.Result.ItemModel.SortValue); // integer 表示順番
            Debug.Log(r.Result.Inventory.InventoryId); // string インベントリ
            Debug.Log(r.Result.Inventory.InventoryName); // string インベントリモデル名
            Debug.Log(r.Result.Inventory.CurrentInventoryCapacityUsage); // integer 現在のインベントリのキャパシティ使用量
            Debug.Log(r.Result.Inventory.CurrentInventoryMaxCapacity); // integer 現在のインベントリの最大キャパシティ
        }
    },
    gameSession,    // GameSession ログイン状態を表すセッションオブジェクト
    "ItemBox",   //  ネームスペース名
    "Character",   //  インベントリの名前
    "Taro",   //  アイテムマスターの名前
    consumeCount   //  消費する量
);
```

## 有効期限のあるアイテム

アイテムには有効期限が設定できます。
有効期限の異なるアイテムは同一アイテムでもスタックが強制的に分けられます。

消費するとき、明示的にスタックのIDを指定しなければ、有効期限の近いアイテムから消費されます。

# 次回予告

次回はキャラクターを組み合わせてパーティを作ってみます