インベントリ / 設計


はじめに

これからインベントリシステムの作成方法を解説していきますが、どのような方針で組み立てていくのかを詳しく説明します。

まずゲームでいうインベントリシステムとは、キャラクターやプレイヤー自身が所持するアイテムを管理するモジュールのことです。色々なゲームのインベントリを見比べてみると、機能はそれぞれ違ったように見えるかもしれません。あるゲームではアイテムの合成機能があるかもしれませんし、あるゲームでは捨てる(換金する)機能があるかもしれません。クリックで効果を発揮できるものもあれば、アイテムの説明が見られるだけで何も出来ない場合もあります。ただし、基本となる考え方は共通しており、それは「アイテムの収支を矛盾なく行うシステムであること」です。アイテムが1個入ってきた(出ていった)際に間違いなく通帳に+1 (-1) と記入するといった基本的な管理はもちろん、捨てたらいけないアイテムは出ていかないように管理する、といったアイテムの性質も考慮に入れます。

この一連の記事ではインベントリシステムのパッケージ化を目標に解説をしていくため、完成した場合は様々なゲームに取り込まれることを想定します。そのためどんなアイテムが入ってくるのかは未知であり、アイテムの性質を考慮する処理をパッケージに含めることはできません。しかしこのシステムの使用者 (未来の自分かもしれません) が、性質依存の処理を付け加えやすいような設計を考えます。



設計

まず、インベントリシステムの核となるデータの構造に関してです。しかしこれは非常に単純で、下図のExcelシートのようなイメージとなります。


実際にコードを書く際は2つのList<int>型で表現します。1つはItem Idを並べたコレクションで、他方は個数を並べたコレクションになります。平たく言うと、上図の表を左右に分割したようなイメージです。ここで整数配列(int[])を使用し、Item Id番目の要素に個数を入れても管理できそうに感じますが、この場合インベントリの整列順が必ずItem Id順になる欠点があります。そのためここでは2つのListを使用することにします。

このデータを中心に、いくつかの機能を実装していきます。全体像は次の図の通りです。


まずItemBagと書かれた中央の領域を見てください。ここには2つの機能があり、核となる情報の記憶とその情報をUIとして表示する機能です。前者は上で説明した通りで、後者はインベントリのスロット達のことを意味しています。このインベントリシステムの使用者の立場を考えると、データの参照、そして書き換え機能は必須です。そこでAddItem() , RemoveItem() , GetItem()の3つの関数を公開します。

次にスロットをクリックした際の挙動です。上の図では例としてアイテム情報の詳細を表示する場合を示しています。ここで必要になるのが全てのアイテム情報がまとめられたアイテム図鑑(に相当する)機能です。これがあればItem Idからアイテムの情報を検索することができるようになります。先ほどは説明しませんでしたが、スロットにアイコンを表示させるのにもアイテム図鑑が必要になってきます。というのも核となるデータにはitem idと個数の情報しか載っておらず、アイコンが分からないからです。


以上の戦略からどのようにプログラムを組むかを考えます。


緑の字はクラス名、青字はメソッド、黒字は(基本的に)変数名になります。例えばItemBagと書かれた領域内はItemBagクラス内ということです。

ItemBag : GameObject
システムのユーザー向けに三つのメソッドを用意することは先に説明した通りです。次に核となるデータに関しては、インナークラスItemBagDataを用意し、そのインスタンスをItemBagで保持します。インナークラスにはList<int>型の変数が二つあり、ItemIdと個数を表します。最後にインベントリのスロットに関して、ItemBagクラスはスロットオブジェクトの生成を行います。具体的には、予めスロットオブジェクトのプレハブを用意し、指定した個数だけインスタンス化します。

ItemSlot : GameObject
まずスロットに入っているアイテムを示す変数Itemと、その個数Numberです。しかし所持アイテムや個数はゲームが進むにつれ変化します。核となるデータに変更があった場合ItemBagクラスからお知らせが、、、正しくはUpdateItem()が呼ばれます。このメソッド内でItemNumberが更新されます。最後にスロットがクリックされたときの挙動についてです。この時ItemDetailクラスのOnClickCallback()抽象メソッドを呼びます。

ItemDetail : GameObject
スロットがクリックされた際の挙動を決めるクラスです。インベントリ1つに対し、これを継承したクラスのインスタンスを必ず1つ作る必要があります。作成したインスタンスは、インスペクターを通してItemBagに渡します。

ItemBase : ScriptableObject
アイテム図鑑の1ページです。アイテムId、アイテム名、アイコン、アイテム説明を設定します。図には載せていませんが、ItemBaseクラスのインスタンス(=図鑑のページ)を束ねる役割を持つクラスItemUtilityを作成する予定です。この束ねた結果が本当の意味で図鑑となります。



最後に、このパッケージを使用する立場で見たデザインです。


中央の箱で表した部分がこのパッケージです。機能のほとんどを覆い、使用者が考慮すべき箇所を極力減らしています。一見するとカスタマイズ性の乏しい(使いづらい)パッケージに見えますが、OnClickCallback()は抽象メソッドですので中身を自由に記述することができます。記事の最後に、ショップ機能も含め様々なインベントリを表現できることを、デモを通して紹介します。

次回以降具体的な作業に入りますが、1点注意があります。紹介するコードの中に、頻繁にinternalというアクセス修飾子が出てきます。これは上図でいう「箱」の内部からのみ参照可能な変数(など)を定義する際に用います。パッケージ利用者からいじられる心配がないという利点があるため、覚えておいて下さい。



以上で説明が終了です。次回以降もよろしくお願いします。