【问题标题】:How to create a MonoBehaviour fabric for non-MonoBehaviour classes?如何为非 MonoBehaviour 类创建 MonoBehaviour 结构?
【发布时间】:2021-10-19 22:14:55
【问题描述】:

我正在开发一个小型核反应堆模拟器游戏。我有一堆反应堆组件类:HeatVentHeatExchangerUraniumCell 等。它们不是从 MonoBehaviour 派生的,因为它们没有任何Unity 逻辑,但它们确实实现了共享接口 IReactorComponent。我想做的是能够创建这些组件的预制件(简单的散热孔、高级散热孔、双铀电池等)。预制件会有不同的精灵和类似的东西,但主要问题是定义什么反应堆预制件相关的组件类,因为我不能只在检查器上拖放非 MonoBehaviour 脚本。另外,我希望能够在检查器中设置设置(例如,HeatVent 具有 CoolAmountHeatCapacity 属性,UraniumCell 具有 FuelAmountHeatProducePowerProduce 属性)。

我已经阅读了工厂方法模式,据我了解,我必须为每个反应器组件类创建一个派生自 MonoBehaviour 的结构类,例如 HeatVentBehaviourHeatExchangerBehaviour 等等。是的,这完全解决了我的预制件问题,但是有没有办法不为每个类创建额外的 MonoBehaviour 包装?如果我有 15 个 IReactorComponent 类,我需要创建 15 个结构,这感觉不是最好的解决方案。

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    听起来你要找的是ScriptableObject

    这些实例是 assets,因此它们不在场景中,而是在 Assets 文件夹中,并且基本上表现得有点像 prefabs,除了:它们已经存在,不再需要实例化。

    大多数情况下,它们仅用作可配置的数据容器。他们有一个 Inspector,因此您可以轻松地用您想要的数据和对其他资产的引用(例如,您案例中的相关预制件)填充它们。

    但除此之外,您还可以让它们实现类似于您的界面的行为,从而通过使用来自不同 ScriptableObjects 的方法的不同实现来改变场景对象的行为!

    对于工厂,您只需要确定使用哪种方法即可使用哪个ScriptableObject 实例,例如通过使用不同的方法或通过Dictionary 填写您的 SO 参考。


    作为一个例子,这可能是什么样子(确保每个 MonoBehaviour 和 ScriptableObject 都有其单独的脚本文件并具有匹配的名称)

    SpawnManager.cs

    public class SpawnManager : MonoBehaviour
    {
        [SerializeField] private ReactorComponentBehaviour _behaviourPrefab;
        [SerializeField] private BaseReactorComponent[] _components;
    
        public bool TrySpawn<T>(out T component, out ReactorComponentBehaviour componentBehaviour) where T : IReactorComponent
        {
            component = default(T);
            componentBehaviour = default;
    
            var foundComponent = components.FirstOrDefault(c => c.GetType() == typeof(T));
    
            if(foundComponent == null) 
            {
                Debug.LogError($"No component found of type {T.GetType().Name}!");
                return false;
            }
    
            // Here Instantiate doesn't spawn anything into the scene but
            // rather creates a copy of the ScriptableObject asset
            // This is just to avoid that any changes in the fields during the game
            // would change the original ScriptableObject asset and thereby ALL related behavior instances 
            component = Instantiate ( (T) foundComponent);
    
            // This now indeed spawns the related MonoBehaviour + GameOver
            componentBehaviour = Instantiate (behaviourPrefab);
            componentBehaviour.Init(component); 
    
           return true;   
        }
    }
    

    BaseReactorComponent.cs

    public abstract class BaseReactorComponent : ScriptableObject, IReactorComponent
    {
        public abstract void WhateverIReactorComponentNeeds();
    
        // Common fields and methods e.g.
        public Sprite Icon;
    }
    

    HeatVent.cs

    [CreateAssetMenu]
    public class HeatVent : BaseReactorComponent
    {
        public int CoolAmount;
        public int HeatCapacity;
    
        public override void WhateverIReactorComponentNeeds ()
        {
            // Do something
        }
    }
    

    UraniumCell.cs

    [CreateAssetMenu]
    public class UraniumCell : BaseReactorComponent
    {
        public int FuelAmount;
        public int HeatProduce;
        public int PowerProduce;
    
        public override void WhateverIReactorComponentNeeds ()
        {
            // Do something
        }
    }
    

    最后你只需要一个带有

    的基础预制件

    ReactorComponentBehavior.cs

    public class ReactorComponentBehavior : MonoBehaviour
    {
        [SerializeField] private Image _image;
    
        private IReactorComponent _component;
    
        public void Init(IReactorComponent component)
        {
            _componemt = component;
    
            // Do other stuff like e.g. adjust visuals according to the component etc
            _image.sprite = component.Icon;
        }
    
        // And then use whatever this behavior should do with the assigned component
    }
    

    所以最后你会像这样使用它

    if(spawManagerReference.TrySpawn<HeatVent>(out var component, out var componentBehaviour)
    {
        // Do something with the behavior e.g. set its position, parent etc
    }
    else
    {
        Debug.LogError($"Failed to get a {nameof(HeatVent)}!");
    }
    

    如果在某些时候你仍然想要不同的附加行为,你可以让它们从常见的 ReactorComponentBehavior 继承,而是引用 BaseReactorComponent 本身内部的预制件.. 然后每个组件都可以带来自己的预制件,但仍然有一个共同的核心行为

    【讨论】:

      猜你喜欢
      • 2020-04-22
      • 2015-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-11
      • 1970-01-01
      • 2014-01-16
      • 1970-01-01
      相关资源
      最近更新 更多