ScriptableObjectでデータを作ろう:アイテムのデータをアセットにする【Unityスタートガイド12-8】

アイテムの名前や値段、敵のHPや攻撃力・・・。こういった「ゲームのデータ」をスクリプトの中に直接書いていくと、数が増えたときに大変ですし、値を少し変えるだけでもスクリプトを開くことになります。今回は、データだけを1つのファイル(アセット)にして、Inspectorで編集出来るようにするScriptableObject(スクリプタブルオブジェクト)をご紹介します!

目次

この記事で学べること

  • ScriptableObjectとは何か(MonoBehaviourとの違い)
  • ScriptableObjectのクラスを作って、アイテムのデータのアセットを作る方法
  • スクリプトからScriptableObjectのデータを使う方法
  • セーブデータには「データそのもの」ではなく「ID」を保存する理由
  • 再生中にScriptableObjectの値を書き換えるときの注意

前提知識

ScriptableObjectとは

ScriptableObjectは、データを入れておくためのアセットです。スクリプトで「どんなデータを持つか」を決めておくと、Projectウインドで右クリックして、そのデータのファイルをいくつでも作れます。

MonoBehaviourScriptableObject
どこにある?シーンのGameObjectに付けるProjectウインドのアセット(ファイル)
主な役割動き・処理(Update、当たり判定など)データ(名前、値段、HPなど)
いくつ作る?付けたGameObjectの数だけアセットの数だけ(1つのアセットを、いろんなところから使える)
例プレイヤーの移動、ボタンの処理アイテムの一覧、敵の強さ、ステージの設定

「アイテム1つ1つをファイルにして、Inspectorで値を入れる」イメージです。

ScriptableObjectのクラスを作る

アイテムのデータを持つScriptableObjectを作ってみましょう。

  • Projectウインドで右クリック>Create>MonoBehaviour Script で「ItemData」を作り、中身を次のように書き換える
using UnityEngine;

// アイテム1つ分のデータ。Project ウインドの Create>Game>Item Data でアセットを作れる
[CreateAssetMenu(fileName = "NewItem", menuName = "Game/Item Data")]
public class ItemData : ScriptableObject
{
    public string id;           // セーブデータに保存する名前(英数字で、ほかのアイテムと重ならないように)
    public string itemName;     // 画面に出す名前
    public Sprite icon;         // アイコン
    public int price;           // 値段
    [TextArea] public string description; // 説明文
}
  • : ScriptableObject
    • MonoBehaviourの代わりに、ScriptableObjectを継承する
    • Update・Startなどは使わない。データを入れておくだけのクラス
  • [CreateAssetMenu]
    • Projectウインドの右クリック>Createのメニューに、このアセットを作る項目が出る
    • fileName:作ったときの最初のファイル名
    • menuName:メニューのどこに出すか(「Game/Item Data」なら、Create>Game>Item Data)
  • [TextArea]
    • Inspectorで、何行も書ける大きな入力欄になる
  • ファイル名とクラス名をそろえる
    • ItemData.csの中はItemDataクラスにする。違うと、アセットが作れない

アセットを作る

  • Projectウインドで右クリック>Create>Game>Item Data
  • 名前を「Potion」にする
  • Inspectorで値を入れる
    • Id:potion
    • Item Name:やくそう
    • Icon:好きなSprite
    • Price:10
    • Description:HPを少し回復する
  • 同じように「Antidote」(どくけしそう)や「Bomb」(ばくだん)も作ってみる

アイテムを増やしたいときは、スクリプトを触らずにアセットを増やすだけでOK。値段を変えたいときも、Inspectorで数字を変えるだけです。

スクリプトからデータを使う

作ったアセットは、[SerializeField]の変数にドラッグ&ドロップして使います。

using System.Collections.Generic;
using UnityEngine;

// ショップの棚。並べるアイテムのデータを Inspector で設定して、Console に一覧を出す
public class ItemShelf : MonoBehaviour
{
    [SerializeField] private List<ItemData> items = new List<ItemData>();

    private void Start()
    {
        foreach (ItemData item in items)
        {
            Debug.Log($"{item.itemName}:{item.price}コイン({item.description})");
        }
    }
}
  • 空のGameObjectにItemShelfを付けて、InspectorのItemsの「+」で欄を増やし、作ったアセットをドラッグ&ドロップする
  • 再生すると、Consoleにアイテムの名前と値段が並ぶ
  • List
  • 同じアセットを、いろんなところから使える
    • ショップの棚でも、持ち物の画面でも、同じPotionのアセットを参照すればOK。値段を変えると、全部に反映される

セーブデータには「ID」を保存する

ScriptableObjectのデータを、そのままJsonUtilityで保存しようとすると、うまくいきません。

  • JsonUtilityでScriptableObjectの変数を保存すると、中身ではなく「そのときの番号(インスタンスID)」が保存される
  • インスタンスIDは、Unityを起動し直すと変わってしまう。次に読み込んだとき、どのアイテムか分からなくなる

そこで、セーブデータにはアイテムのID(文字)を保存して、読み込むときにIDからアイテムのデータを探します。ItemDataにidを用意したのはそのためです。

全部のアイテムを登録しておく「図鑑」のようなScriptableObjectを作っておくと、IDから探すのが簡単になります。

using System.Collections.Generic;
using UnityEngine;

// ゲームに出てくるアイテムを全部登録しておく図鑑。ID からアイテムのデータを探せる
[CreateAssetMenu(fileName = "ItemDatabase", menuName = "Game/Item Database")]
public class ItemDatabase : ScriptableObject
{
    [SerializeField] private List<ItemData> items = new List<ItemData>();

    public IReadOnlyList<ItemData> Items => items;

    // ID が同じアイテムを探す(なければ null)
    public ItemData FindById(string id)
    {
        foreach (ItemData item in items)
        {
            if (item.id == id)
            {
                return item;
            }
        }
        return null;
    }
}
  • 保存するとき:item.id(”potion”など)を保存する
  • 読み込むとき:database.FindById("potion")で、Potionのアセットが返ってくる
  • IReadOnlyList
    • ほかのスクリプトからは一覧を読むだけで、足したり消したり出来ないようにしている
スライム

IDはあとから変えると、保存したデータが読めなくなります・・・一度決めたら変えないようにしましょう

再生中に値を書き換えるときの注意

ScriptableObjectはアセット(ファイル)なので、再生中にスクリプトで値を書き換えると、エディタでは再生を止めても元に戻りません。

  • エディタで遊んでいるときにitem.price = 0;のように書き換えると、Potionのアセットそのものが0になってしまう
  • ビルドしたゲームでは、アプリを終了すると元に戻る(ファイルには保存されない)

なので、ScriptableObjectには変わらないデータ(名前、値段、最大HPなど)だけを入れて、変わるデータ(持っている数、今のHPなど)は、MonoBehaviourやセーブデータの方に持たせるのがオススメです。

ScriptableObjectに入れる(変わらない)別に持つ(変わる)
アイテムの名前・値段・アイコン持っている数
敵の最大HP・攻撃力戦闘中の今のHP
ステージの制限時間残り時間
フェアリー

ScriptableObjectは「設計図のデータ」、変わる値は「セーブデータ」。分けておけば安心だね!

よくあるエラーと対処法

エラー:Create>Game>Item Dataがメニューに出てこない

  • 原因:スクリプトにコンパイルエラーがある、または[CreateAssetMenu]を付け忘れている
  • 対処法:Consoleの赤いエラーを直す。[CreateAssetMenu]がクラスのすぐ上にあるか確認する

警告:The associated script can not be loaded.(アセットのInspectorに出る)

  • 原因:ファイル名とクラス名が違う、またはスクリプトの名前を変えた
  • 対処法:ItemData.csの中のクラス名をItemDataにそろえる

エラー:NullReferenceException

  • 原因:[SerializeField]の変数にアセットを入れていない。またはFindByIdで見つからずnullが返った
  • 対処法:Inspectorで設定されているか確認する。IDのつづり(大文字・小文字)がアセットと同じか確認する

エラー:再生を止めても値が元に戻らない

  • 原因:再生中にスクリプトでScriptableObjectの値を書き換えた
  • 対処法:変わる値は、ScriptableObjectではなくMonoBehaviourやセーブデータに持たせる

まとめ

  • ScriptableObjectはデータを入れておくアセット。MonoBehaviourの代わりにScriptableObjectを継承する
  • [CreateAssetMenu]を付けると、Projectウインドの右クリックでアセットを作れる
  • アセットは[SerializeField]の変数にドラッグ&ドロップして使う。同じアセットをいろんなところから使える
  • セーブデータには、データそのものではなくIDを保存して、読み込むときにIDから探す
  • 再生中に書き換えた値は、エディタでは元に戻らない。変わる値は別に持つ

ScriptableObjectの使い方をもっと知りたい人は、ScriptableObjectの基礎!シーン間でデータ受け渡しなども参考にしてください。

次のステップ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次