様々なPrefabをまたいで値を設定していると、いくつかのPrefabで同じ値を使いまわしたい!と感じることがあります。

そのため、Unityには、データを保存して使いまわすためのScriptableObjectという仕組みが存在します。

Unity - マニュアル: ScriptableObject

これ自体はとても良い仕組みではあるのですが、毎回下記のようなコードを書く必要があります。

流れ

  1. SerializeObjectを継承した、データを持つクラスを作る
  2. MenuItem属性を使い、生成用のメソッドを作成する
  3. 保存先のパスを生成する
  4. パスが被っていたら適当に名前をズラす
  5. AssetDatabaseからアセットを作成
  6. AssetDatabaseを保存

コード

using System.IO;
using UnityEditor;
using UnityEngine;

public class TileSetScriptableObject : ScriptableObject
{
    [SerializeField] private TileSet _tileSet;

    public TileSet TileSet
    {
        get { return _tileSet; }
        set { _tileSet = value; }
    }

    public static void Create()
    {
        var scriptableObject = new TileSetScriptableObject();

        const string fileName = "TileSet.asset";

        var path = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(GetSelectingAssetDirectory(), fileName));

        AssetDatabase.CreateAsset(scriptableObject, path);
    }

    private static string GetSelectingAssetDirectory()
    {
        if (Selection.assetGUIDs.Length == 0) return "Assets";

        var directoryPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);

        return Directory.Exists(directoryPath)
            ? directoryPath
            : Path.GetDirectoryName(directoryPath);
    }
}

毎回この処理を書くのは面倒なので、楽をするためのクラスを作ってみました!

楽をするためのクラス

下記のコードをAsset内へ放り込みましょう。

using System.IO;
using UnityEditor;
using UnityEngine;

public class ScriptableObject<T> : ScriptableObject
{
    [SerializeField] private T _data;

    public T Data
    {
        get { return _data; }
        set { _data = value; }
    }
    
    public static void Create<T2>() where T2 : ScriptableObject<T>, new()
    {
        var scriptableObject = new T2();

        Debug.Log(typeof(T).Name);
        var fileName = typeof(T).Name + ".asset";

        var path = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(GetSelectingAssetDirectory(), fileName));

        AssetDatabase.CreateAsset(scriptableObject, path);
        AssetDatabase.SaveAssets();
    }

    private static string GetSelectingAssetDirectory()
    {
        if (Selection.assetGUIDs.Length == 0) return "Assets";

        var directoryPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);

        return Directory.Exists(directoryPath)
            ? directoryPath
            : Path.GetDirectoryName(directoryPath);
    }
}

使い方

まず、ScriptableObjectにしたいクラスを作成します。

クラスにSerializable属性がついていて、外から設定したい値にSerializableField属性が付いていれば、あとは適当で大丈夫です!

using System;
using UnityEngine;

[Serializable]
public class TileSet
{
    [SerializeField] private Texture2D _texture;
    [SerializeField] private int _xCount = 1;
    [SerializeField] private int _yCount = 1;

    public Texture2D Texture
    {
        get { return _texture; }
        set { _texture = value; }
    }

    public int XCount
    {
        get { return _xCount; }
        set { _xCount = value; }
    }

    public int YCount
    {
        get { return _yCount; }
        set { _yCount = value; }
    }
}

ScriptableObject<>を継承したクラスを作り、クラス名と同じ名前のcsファイルとして保存します。

using UnityEditor;

public class TileSetScriptableObject : ScriptableObject<TileSet> // ScriptableObject<> の中にはさっき定義したクラスの名前を書く
{
    [MenuItem("Assets/Create/TileSet")] // MenuItemの名前は適当でいいが、Assets/Create/内に入れておくと便利
    public static void Create()
    {
        Create<TileSetScriptableObject>(); // Create<>の中にはこのクラスの名前を書く
    }
}

スクリプトを更新すると、プロジェクトビューの右クリックメニュー内にさっきのScriptableObjectが追加されています。

ScriptableObject

既に同じ名前があったら、自動で連番をつけてくれます。

ScriptableObject

便利!!