UnityでSteamworksのInventory Serviceのアイテム取得を実装する

# ■ はじめに
前回のSteamworksのInventoryServiceの設定とUnityでのアイテムの付与に続いて、アイテムの取得に関して書いてみる。
(もし間違っている内容があればコメントで教えていただけると助かります。。)


https://qiita.com/wanwancat2467/items/ae81a269101928a5d932

# ■環境
Unity2022.3.53f1


# ■ 今回の記事でやること
:::note info
- Unityでユーザーのインベントリのアイテムの取得を実装する。
- UnityでSteamのユーザーのインベントリに関して複数の理由でトリガーされたコールバック関数の中で、トリガー原因を特定する。
:::

# ■ 前提
:::note info
- Inventory Service周りの設定が一通り済んでいる。
- アイテムを付与できるところまで実装し、Steamのインベントリに作成中のゲームのアイテムを追加することが可能
:::


# ■ 公式ドキュメント

https://partner.steamgames.com/doc/api/ISteamInventory#TriggerItemDrop


# ■ UnityでSteamのユーザーのインベントリのアイテムを取得する



:::note warn
注意
Steamのユーザーのインベントリのアイテムを取得すると言っても、全てのゲームのアイテムが取得できる訳ではありません。
取得したり、アイテムに対して何かしらの処理を実行できるのは、皆さんが作っているアプリケーションで付与したアイテムのみです。
なので、他のゲームのアイテムを取得して何かしらの処理をする、ということはできないはずです。
:::

```
public void RequestAllInventoryItems()
{
    if (!SteamInventory.GetAllItems(out var handle))
    {
        Debug.Log("アイテム一覧取得時にアイテム取得リクエストに失敗した");
        return;
    }
    //todo:handleを覚えておく
}
```
    
これはまずシンプルにユーザーのインベントリに入っているアイテム一覧の取得を要求するコードです。
要求というのも、ユーザーのインベントリのアイテム一覧はここのresultHandleには入ってきません。
ユーザーのアイテム一覧はSteamInventoryResultReady_tのコールバックで指定した関数で帰ってきます。

```
private Callback<SteamInventoryResultReady_t> steamInventoryResultReady;

//awake等の初期化で登録しておく
steamInventoryResultReady = Callback<SteamInventoryResultReady_t>.Create(OnSteamInventoryResultReady);

void OnSteamInventoryResultReady(SteamInventoryResultReady_t pCallback)
{            
    if (pCallback.m_result != EResult.k_EResultOK)
    {
        Debug.Log($"アイテムの取得関連の結果がOK以外でした/handle => {pCallback.m_handle}/result => {pCallback.m_result}");
        return;
    }

    Debug.Log("アイテムの取得結果が正常に受け取れた");
}

//終了処理等で書いておく
steamInventoryResultReady.Dispose();
```

これでユーザーの所持しているアイテムの取得の要求と、インベントリに関する情報の取得に成功/失敗したタイミングは取れるようになりました。

ここから取得したユーザーのアイテムの情報を確認していきます。

```
void GetItemDetailsFromHandle(SteamInventoryResult_t _handle)
{
    //ハンドルが無効
    if (_handle == SteamInventoryResult_t.Invalid)
    {
        Debug.Log("取得したアイテム一覧の内容を確認する際にハンドルが無効だった");
        return;
    }
    
    //ユーザーのインベントリのアイテムの数を取得する
    uint itemCount = 0;
    if (!SteamInventory.GetResultItems(_handle, null, ref itemCount))
    {
        Debug.Log($"ハンドルからユーザーのインベントリのアイテムの数が取得できなかった => {_handle}");
        return;
    }

    //取得したユーザーのインベントリ内にアイテムが存在しない場合
    if (itemCount <= 0)
    {
        //アイテムを付与したはずなのにここに入ってくる場合は、
        //generator系のアイテムを付与したが、そのアイテムのbundleが定義されておらず、
        //generatorは付与されたが、排出されたアイテムが無い場合が多い。
        
        Debug.Log($"アイテム一覧は取得できたが、アイテムは1つもなかった => {_handle}");
        return;
    }

    //ハンドルのアイテムの情報を取得する
    SteamItemDetails_t[] itemDetails = new SteamItemDetails_t[itemCount];
    if (!SteamInventory.GetResultItems(_handle, itemDetails, ref itemCount))
    {
        Debug.Log($"アイテム一覧取得時に結果が取得できなかった => {_handle}");
        return;
    }

    //取得したアイテムの情報を全てログに出してみる
    foreach (var item in itemDetails)
    {
        Debug.Log($"アイテム一覧 /def {item.m_iDefinition}　/handle => {_handle}");
    }
}
```
この関数でハンドルの持つアイテムの情報一覧が取得できるようになりました。
ただ、アイテムの情報といっても、この関数で得られる情報はアイテムの定義ID、アイテムのユニークインスタンスIDくらいです。

皆さんのゲーム内では、アイテムの画像を表示、アイテムのレア度の取得等、諸々の処理をするにあたって、アイテム定義のプロパティの取得は必須だと思うのでやってみましょう。

```
public string GetItemDefinitionProperty(SteamItemDef_t itemDefinitionId,string _propertyKey,uint _bufferSize = 1024)
{
    string result = string.Empty;

    // アイテム定義IDから指定のプロパティを取得
    bool success = SteamInventory.GetItemDefinitionProperty(
        itemDefinitionId,
        _propertyKey,
        out result,
        ref _bufferSize
    );
    
    if (!success)
    {
        Debug.Log($"アイテム定義から指定のプロパティが取得できなかった/ => {itemDefinitionId} / => {_propertyKey}");
        return null;
    }

    if (String.IsNullOrEmpty(result))
    {
        Debug.Log($"アイテム定義から取得したプロパティの内容がNULLでした/ => {itemDefinitionId} / => {_propertyKey}");
        return null;
    }

    return result;
}
```
これでアイテムのicon_urlを取得して、画像をダウンロードし、ユーザーのインベントリのアイテム一覧を取得して全てUIで表示したり、表示するにあたって、レア度等何かしらのプロパティを取得しておけばソートしたりできたりるようになります。

<br>
ただ、この辺りまで実装したところで問題が出てきます。

:::note warn
OnSteamInventoryResultReady関数は複数の原因で実行されます。
:::

なので、OnSteamInventoryResultReady関数でhandleの結果がEResult.K_EResultOKだった場合でも、そのhandleがユーザーの全ての所持アイテム一覧を示しているものではない可能性があります。

そこで、OnSteamInventoryResultReady関数を発動させるなにかしらを要求する関数で発行されたhandleを覚えておいて、OnSteamInventoryResultReady関数で帰ってきたhandleと比較する、ということをしないといけません。


# ■ 何が原因でSteamInventoryResultReady_tのコールバックが発火したか特定する #

SteamInventoryResultReady_tのコールバックが発火する要求は複数あります。

:::note info
- プロモアイテムの付与 (AddPromoItem , AddPromoItems)
- プレイ時間に応じたアイテムの付与 (TriggerItemDrop)
- テスト用アイテムの付与 (GenerateItems)
- アイテムの変換 (ExchangeItems)
- ユーザーのアイテム一覧取得 (GetResultItems)
- アイテムの消費 (ConsumeItem)
- (他にもあるかも)
:::
これらの関数を呼ぶと、結果は全てSteamInventoryResultReady_tのコールバックで帰ってきます。
これらのどれで関数が呼ばれたのかはhandleの数値で判別できます。

先ほどのユーザーのインベントリのアイテム一覧取得関数を例とすると、アイテム一覧要求時のhandle.m_SteamInventoryResultと、コールバックのpCallback.m_handle.m_SteamInventoryResultを比較して同じであれば、ユーザーの所持アイテム一覧取得によるコールバックの発火だったと知ることができます。
```
public void RequestAllInventoryItems()
{
    SteamInventory.GetAllItems(out var handle);
    //handle.m_SteamInventoryResult <= これ！
}

void OnSteamInventoryResultReady(SteamInventoryResultReady_t pCallback)
{
    //pCallback.m_handle.m_SteamInventoryResult <= これ！
}
```

このように、要求と結果のハンドルを確認すれば、OnSteamInventoryResultReady関数内で、新規にユーザーのインベントリに入ってきたアイテムの情報、ユーザーが所持しているアイテム一覧等を分けて知ることができるわけです。

# ■ 最後に
アイテムの取得できたでしょうか。
もし上記でうまく行かないようであれば、一度前回の記事のアイテム付与周りに関する条件を確認してみてください。


<img width="150" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951067/cecee291-142e-cbbe-0c3d-f818ef33db2b.gif">