如何使用Scriptable Objects创建动态变量系统

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用Scriptable Objects创建动态变量系统相关的知识,希望对你有一定的参考价值。

this talk中,我学习了如何使用可编写脚本的对象创建变量,创建FloatVariable,DoubleVariable,StringVariable等类。但是,在同一个演讲中,这个人说他使用了一个更动态的变量系统,它阻止创建几个类来处理所有变量类型。

使用第一个系统,我有一个名为ImageFillSetter的C#脚本,它给出了两个浮点变量和一个Image脚本,它将两个变量的除法返回到图像的fillAmount变量。

但是,当我得到一个双变量,并且我想设置一个具有此值的进度条时,我需要创建另一个名为ImageFillSetterDouble的脚本,并放入这些变量。如果我需要用Integers创建一个?每次我创建这样的脚本时,我都需要创建两个重复项来处理其他数字变量类型?使用这个动态变量系统,这个问题应该解决,但我不知道如何启动/创建这个系统。

代码如下所示:

[CreateAssetMenu(menuName="Variable/Float")]
public class FloatVariable : ScriptableObject, ISerializationCallbackReceiver
{
    public float initialValue;
    [NonSerialized] 
    public float value;

    public void OnAfterDeserialize()
    {
        value = initialValue;
    }

    public void OnBeforeSerialize() { }
}

我想要的是这样的事情(完全假设,我知道这不起作用)

[CreateAssetMenu(menuName="Variable")]
public class Variable : ScriptableObject, ISerializationCallbackReceiver
{
    public var initialValue;
    [NonSerialized] 
    public var value;

    public void OnAfterDeserialize()
    {
        value = initialValue;
    }

    public void OnBeforeSerialize() { }
}
答案

看看Generics

有一个抽象类

public abstract class ValueAsset<T> : ScriptableObject
{
    public T value;

    // Add your methods

    // Here some more examples also using the T value. They might also be abstract but they don't have to be

    // return a T
    public T GetValue()
    {
        return value;
    }

    // or pass a T
    public void SetValue(T input)
    {
        value = input;
    }
}

这个类你永远不会实例化,但现在从它派生多个实现,例如

[CreateAssetMenu(fileName = "new int", menuName = "ValueAssets/int")]
public class IntValue : ValueAsset<int>
{
    // Maybe constructors here or additional fields and methods
}

[CreateAssetMenu(fileName = "new float", menuName = "ValueAssets/float")]
public class FloatValue : ValueAsset<float>
{
    // Maybe constructors here or additional fields
}

您还可以拥有多个通用值,例如

public abstract class OtherExample<TKey, TValue> : ScriptableObject
{
    // Note that this is just an example
    // Dictionary is not serializable
    public Dictionary<TKey, TValue> values = new Dictionary<TKey, TValue>();

    public void AddPair(TKey key, TVakue value)
    {
        values.Add(key, value);
    }
}

并实现类似的东西

public OneImplementation : OtherExample<string, GameObject>
{
    //...
}

这可以用于参考值(组件,GameObject等)


因此,对于IntValue,方法GetValue将返回intSetValue将以int作为参数。他们采取相同的方式并在float返回FloatValue


使用ImageFillSetter<T>做同样的事情,你可以制作你的方法abstract并为不同的T值实现不同的行为(例如不同的解析等)

注意:我不知道为什么,但过去我注意到了

public ValueAsset<T> valueAsset;

即使稍后实现,也不会在检查器中序列化,因此您必须在实现中实现具有正确类型的字段。您还可以在运行时覆盖它,但如果您不需要它,可以跳过整个FetchValue部分,无论如何使用valueReference - 只是为了完整性而添加它。

public abstract class ImageFillSettet<T> : MonoBehaviour
{
    // Will not appear in the Inspector
    public ValueAsset<T> ValueAsset;

    // Override this in implementation
    protected abstract void FetchValue();

    // Use it for Initializing the value
    private void Awake ()
    {
        FetchValue();
    }

    public abstract void SetFill();   
}

比以后

public class ImageFillSetterFloat : ImageFillSetter<float>
{
    // Show in the inspector
    [SerializeField] private FloatValue valueReference;

    // Provide the reference to the base class
    protected override void Fetch value()
    {
        valueAsset = valueReference;
    }

    public override void SetFill()
    {
        // Use valueReference for something
    }
}
另一答案

我知道有一个可接受的答案可行,但我觉得链接视频中描述的ScriptableObject变量的使用被误解了。

我认为你最好不要让你的FloatVariable独立于计算。

假设计算是针对玩家的健康状况,您的填充值将由currentHealth/maxHealth计算。

public class PlayerHealth: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    [SerializeField] private float maxHealth;
    [SerializeField] private float currentHealth;

    void Update()
    {
        this.floatReference.value = currentHealth/maxHealth;
    }
}


public class ImageFillSetter: MonoBehaviour
{
     [SerializeField] private FloatVariable floatReference;
     [SerializeField] private Image imageReference;

     void Update()
    {
        this.imageReference.fill = this.floatReference.value;
    }
}

或者假设玩家健康状况存储为双倍:

public class PlayerHealth: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    [SerializeField] private double maxHealth;
    [SerializeField] private double currentHealth;

    void Update()
    {
        this.floatReference.value = (float)(currentHealth/maxHealth);
    }
}

现在让我们假设您添加一个输入字段,其中填充值可以作为百分比字符串输入(如'76'):

public class FillInput: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    [SerializeField] private Input input;

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Enter))
        {
            this.floatReference.value = float.Parse(input.text)/100f;
        }
    }
}

ImageFillSetter将“观察”FloatVariable而不知道如何计算浮点数。

这样,您只需拥有一个可用于任何图像和任何数据源的ImageFillSetter,同时具有一种或多种更改填充的方法,不需要对ImageFillSetter进行任何更改。

例如,假设您要使用相同的方法来指示异步级别加载进度:

public class FillInput: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    private AsyncOperation loadOperation;

    void LoadLevelAsync(string levelName)
    {
        this.loadOperation = SceneManager.LoadLevelAsync(levelName, LoadSceneMode.Additive);
    }

    void Update()
    {
        this.floatReference.value = this.loadOperation?.progress ?? 0;
    }
}

只要您的ImageFillSetter引用相同的FloatVariable,这将无需进行任何其他更改即可工作。

可以将FloatVariable(或您所拥有的原始文件,例如DoubleVariable)视为存储在数据库中的值。任何人都可以读取价值,任何人都可以保存新值。将值的所有可能计算存储在数据库中而不是进行计算并仅存储答案将是奇怪的。

这并不会改变您需要为每个原语进行Scriptable实现的事实:

  • FloatVariable
  • DoubleVariable
  • StringVariable
  • BoolVariable
  • 等等

但是你只需要其中一个,如derHugo答案的第一部分所示。

以上是关于如何使用Scriptable Objects创建动态变量系统的主要内容,如果未能解决你的问题,请参考以下文章

使用 Scriptable iOS 登录 PremiumSim

如何在 PHP Scriptable Web Browser 中调用 javascript 函数

如何在 Scriptable App 中将 Base64 转换为图像

图书馆搜索引擎软件;iOS14桌面Scriptable精美脚本

快使用Scriptable自己开发一个iPhone小组件吧

Scriptable Object