Unity ScriptableObject Factory

时间:2019-11-09 18:55:16

标签: unity3d factory-pattern

我的工厂基于ScriptableObject(“ SO”)属性创建实例。

我不想将我创建的每个新SO追加到Factory。因此,理想情况下,像每个SO一样醒着地在工厂中进行注册的事情都会很棒。

我研究了两种方法,一种是让工厂成为MonoBehaviour:


public class ThingFactory
{
    private static ThingFactory instance = null;

    public static ThingFactory Instance
    {
        get
        {
            if (instance != null)
            {
                return instance;
            }
            else
            {
                instance = new ThingFactory();

                return instance;
            }
        }

        private set
        {
            instance = value;
        }
    }

    public ThingFactory()
    {
    }

    public void RegisterThing(ThingType thingType)
    {
        GameObject newThing = MonoBehaviour.Instantiate(thingType.prefab, Camera.main.transform);

        newThing.SetActive(true);

        newThing.GetComponentInChildren<Text>().text = thingType.name;

        newThing.GetComponent<Image>().sprite = thingType.sprite;
    }
}

public abstract class ThingType: ScriptableObject
{
    public GameObject prefab;

    public Sprite sprite;


#if UNITY_EDITOR
#else
    public void OnEnable()
    {
        Debug.Log("Enabling ThingType of type: " + this.name);

        ThingFactory.Instance.RegisterThing(this);
    }
#endif
}


[CreateAssetMenu(menuName = "Things/ThingA")]
public class ThingAType : ThingType
{       
}

此方法存在三个问题。

  1. 首先是从编辑器调用了OnEnable,我已经使用if UNITY_EDITOR解决了这个问题。

  2. 第二个问题是,仅当场景具有引用该ScriptableObject资产的MonoBehaviour(并非总是如此)时,才调用OnEnable。 甚至现在,当我在一个小项目中重现行为时,我也无法完全理解调用OnEnable的确切要求。

  3. 第三个问题是我收到此异常:

关闭场景时,没有清除某些对象。 (您是否从OnDestroy生成了新的GameObject?) 图片 prefabOfThing(clone)

我不理解这个异常,对我来说有意义的是,如果在场景加载之前调用OnEnable,然后在场景加载时销毁了SO。


第二种方法是让工厂成为MonoBehaviour,并进行反射魔术。

醒着的时候,像这样:

var thingTypes = Assembly.GetAssembly(typeof(ThingType)).GetTypes().Where(thingType => thingType.IsClass && !thingType.IsAbstract && thingType.IsSubclassOf(typeof(ThingType)));

但是现在我被困住了-我有ThingTypes的类,例如ThingAType,但没有单独的SO。

另一个问题是,OnEnable在我的工厂的Awake之前被调用,因此尚未初始化。脚本执行顺序不能改善这种情况。

我不知道实例化工厂预唤醒的好方法,我考虑过使用类似以下的方法。但是这种方法的问题在于,MonoBehaviourFactory的实例不会从检查器中插入诸如someInspectorText的引用。

public static MonoBehaviourFactory Instance
    {
        get
        {
            if (instance != null)
            {
                return instance;
            }
            else
            {
                instance = /*SomeRandomGameObject*/.AddComponent<MonoBehaviourFactory>();

                return instance;
            }
        }

        private set
        {
            instance = value;
        }
    }

    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(this.gameObject);
        }
        else
        {
            instance = this;
        }
    }

    public Text someInspectorText = null;

如何在ScriptableObjects上使用OnEnable为其工厂分配所需的类型?

1 个答案:

答案 0 :(得分:1)

与其让可编写脚本的对象添加自己,不如考虑进行工厂调用Resources.LoadAll<T>,这将在您的项目中加载所有T类型的对象。

如果您真的想在游戏开始时调用ScriptableObject上的方法,请考虑使用[RuntimeInitializeOnLoadMethod]属性[RuntimeInitializeOnLoadMethod]-允许在运行时加载游戏时初始化运行时类方法而无需运行用户的操作。 (脚本不必在现场) 该代码看起来像这样:

// The name of the method does not matter. The magic is in the attribue.
//Add this method to any class to see the log print when the game starts.
[RuntimeInitializeOnLoadMethod]
private static void Init()
{

 Debug.Log("The game has started and this class is Initialized");
}

正如derHugo所指出的:该属性会初始化类本身,因此它无法用作将实例添加到列表的方式

另一种可能的其他解决方案:如果要避免Resource.LoadAll,将执行以下操作:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DemoScriptable : ScriptableObject
{
    //Hot Cold Split. This can also be a dictionary, but this way you have list and can run a for or foreach loop.
    public static List<DemoScriptable> DemoScriptables = new List<DemoScriptable>();
    public static List<Guid> DemoScriptableHashCodes = new List<Guid>();

    private Guid guid;
    private void Awake()
    {
        //can be HashCode, but this is not guaranteed to be unique.
        // the other option is using InstanceID
        guid = Guid.NewGuid();

        //check guid to avoid complex comparison
        if (!DemoScriptableHashCodes.Contains(guid))
        {
            DemoScriptableHashCodes.Add(guid);
            DemoScriptables.Add(this);
        }

    }

    private void Destroy()
    {
        // check guid to avoid complex comparison
        if (DemoScriptableHashCodes.Contains(guid))
        {
            DemoScriptables.Remove(this);
            DemoScriptableHashCodes.Remove(guid);
        }
    }

}

要访问列表,请调用DemoScriptable.DemoScriptables