【Unity】コンポーネントパターンの ScriptableObject

コンポーネントパターンを適用した ScriptableObject のベースクラスです。

ソースコード

public class ComponentScriptableObject : ScriptableObject
{
    [SerializeField]
    protected List<ScriptableObject> m_Components = new();

    protected ScriptableObject Add(Type type)
    {
        if (type.IsClass && !type.IsAbstract && type.IsSubclassOf(typeof(ScriptableObject)))
        {
            var component = CreateInstance(type) as ScriptableObject;
            AssetDatabase.AddObjectToAsset(component, this);
            m_Components.Add(component);

            if (component is ComponentScriptableObject componentScriptableObject)
            {
                componentScriptableObject.AddRequirements();
            }

            return component;
        }

        throw new ArgumentException(type.GetTypeInfo().Name);
    }

    protected virtual IEnumerable<Type> Requirements()
    {
        yield break;
    }

    private void AddRequirements()
    {
        Requirements().ToList().ForEach(type => Add(type));
    }

    protected List<T> GetAllInChildren<T>() where T : ScriptableObject
    {
        var list = m_Components.Where(x => x is T).Cast<T>().ToList();
        list.AddRange(list.Where(x => x is ComponentScriptableObject).Cast<ComponentScriptableObject>().SelectMany(x => x.GetAllInChildren<T>()).ToList());
        return list;
    }

    protected static T Create<T>(string path) where T : ScriptableObject
    {
        var data = CreateInstance<T>();
        AssetDatabase.CreateAsset(data, path);
        if (data is ComponentScriptableObject componentScriptableObject)
        {
            componentScriptableObject.AddRequirements();
        }

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        return data;
    }

    protected static T Load<T>(string path) where T : ScriptableObject
    {
        var data = AssetDatabase.LoadAssetAtPath<T>(path);
        if (data == null)
        {
            return null;
        }

        if (data is ComponentScriptableObject componentScriptableObject)
        {
            var settings = componentScriptableObject.m_Components.Select(x => x.GetType()).ToList();
            componentScriptableObject.Requirements().Where(type => !settings.Contains(type)).ToList().ForEach(type => componentScriptableObject.Add(type));
        }

        return data;
    }
}

public abstract class ComponentScriptableObject<T1> : ComponentScriptableObject where T1 : ScriptableObject
{
    public T Add<T>() where T : T1
    {
        return Add(typeof(T)) as T;
    }

    public void RemoveAll<T>() where T : T1
    {
        var components = GetAllInChildren<T>();
        foreach (var component in components)
        {
            m_Components.Remove(component);
            DestroyImmediate(component, true);
        }

        m_Components.RemoveAll(x => x is T);
    }

    public bool TryGet<T>(out T component) where T : T1
    {
        component = Get<T>();
        return component != null;
    }

    public T Get<T>() where T : T1
    {
        return m_Components.FirstOrDefault(x => x is T) as T;
    }

    public List<T> GetAllInChildren<T>() where T : T1
    {
        return base.GetAllInChildren<T>();
    }

    public static T Create<T>(string path) where T : T1
    {
        return ComponentScriptableObject.Create<T>(path);
    }

    public static T Load<T>(string path) where T : T1
    {
        return ComponentScriptableObject.Load<T>(path);
    }
}