Unity ScriptableObject Factory - PullRequest
       12

Unity ScriptableObject Factory

0 голосов
/ 09 ноября 2019

Моя фабрика создает экземпляры на основе свойств ScriptableObject ("SO").

Мне бы не хотелось добавлять в Factory каждую новую созданную мной SO. Так что в идеале что-то вроде каждого 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. Вторая проблема заключается в том, чтоэтот OnEnable вызывается только тогда, когда сцена имеет MonoBehaviour, ссылающийся на этот актив ScriptableObject, что не всегда так. Даже сейчас, когда я воспроизвожу поведение в небольшом проекте, я не могу точно определить требования для вызова OnEnable.

  3. Третья проблема заключается в том, что яполучая это исключение:

Некоторые объекты не были очищены при закрытии сцены. (Вы породили новые GameObjects из OnDestroy?) Изображение prefabOfThing (clone)

Я не понимаю это исключение, для меня имеет смысл, что если OnEnable вызывается до загрузки сцены, а затем SO уничтожаетсяпри загрузке сцены.


Второй подход заключается в том, чтобы фабрика была моно-поведением и делала магию отражения.

В пробуждении есть что-то вроде:

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

Но теперь я застрял - у меня есть классы для ThingTypes, например, ThingAType, но не отдельные SO.

И еще одной проблемой является то, что OnEnable вызывается до пробуждения моей фабрики, поэтому он еще не инициализирован. Порядок выполнения скрипта не улучшает ситуацию.

Я не знаю хорошего способа создания заводских предварительных пробуждений, я подумал об использовании чего-то вроде следующего. Но проблема с этим подходом состоит в том, что экземпляр 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;

Как я могу использовать OnEnable для объектов ScriptableObject, чтобы заполнить мою фабрику типамиэто обойдется?

1 Ответ

1 голос
/ 10 ноября 2019

Вместо того, чтобы объекты сценариев добавляли самих себя, рассмотрите возможность вызова фабрики 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

...