You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Scriptable Objects构建通用动态变量系统

通用Scriptable Object变量系统实现方案

我完全懂你的痛点——为每种数值类型重复写几乎一样的Variable类和对应的Setter脚本,简直是重复劳动的噩梦!好在C#的泛型和Unity的编辑器扩展可以帮我们解决这个问题,不用再为每个类型复制粘贴代码。

第一步:构建泛型Variable基类

首先,我们需要一个泛型的基类,它能处理所有数值类型的序列化和值同步,同时兼容Unity的Scriptable Object系统。因为Unity不能直接序列化泛型字段,我们需要用一个object类型作为中间存储,在序列化回调里完成类型转换:

public abstract class Variable<T> : ScriptableObject, ISerializationCallbackReceiver
{
    [SerializeField]
    private T initialValue;
    [NonSerialized]
    public T value;

    public void OnAfterDeserialize()
    {
        value = initialValue;
    }

    public void OnBeforeSerialize()
    {
        // 可选:如果需要在编辑模式下保存运行时修改的值,取消下面注释
        // initialValue = value;
    }

    // 统一的赋值方法,方便外部调用
    public void SetValue(T newValue)
    {
        value = newValue;
    }

    // 运算符重载,让变量可以直接当作对应类型使用(比如 float current = myFloatVar;)
    public static implicit operator T(Variable<T> variable)
    {
        return variable.value;
    }
}

这个基类已经封装了核心逻辑:初始值序列化、运行时值同步,还通过运算符重载简化了变量的使用方式,后续所有类型的Variable都基于这个基类扩展。

第二步:快速创建具体类型的Variable

Unity目前不支持直接创建泛型Scriptable Object的资产,我们有两种简单的解决方式:

选项1:极简子类实现(几行代码搞定)

每个类型只需要写一行继承代码,核心逻辑全部复用基类:

[CreateAssetMenu(menuName = "Variables/Float")]
public class FloatVariable : Variable<float> { }

[CreateAssetMenu(menuName = "Variables/Double")]
public class DoubleVariable : Variable<double> { }

[CreateAssetMenu(menuName = "Variables/Int")]
public class IntVariable : Variable<int> { }

虽然要写这些子类,但完全没有重复逻辑,后续修改只需要维护泛型基类即可。

选项2:编辑器脚本自动生成(彻底自动化)

如果你连子类都不想写,可以用编辑器脚本实现一键创建不同类型的Variable资产:

using UnityEngine;
using UnityEditor;

public static class VariableCreator
{
    [MenuItem("Assets/Create/Variable/Float")]
    public static void CreateFloatVariable() => CreateVariable<float>("Float Variable");

    [MenuItem("Assets/Create/Variable/Double")]
    public static void CreateDoubleVariable() => CreateVariable<double>("Double Variable");

    [MenuItem("Assets/Create/Variable/Int")]
    public static void CreateIntVariable() => CreateVariable<int>("Int Variable");

    private static void CreateVariable<T>(string name)
    {
        var variable = ScriptableObject.CreateInstance<Variable<T>>();
        string path = $"Assets/New{name}.asset";
        AssetDatabase.CreateAsset(variable, path);
        AssetDatabase.SaveAssets();
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = variable;
    }
}

这样你在Unity的Assets菜单里就能直接创建各种类型的Variable资产,完全不用手写子类!

第三步:通用ImageFillSetter脚本

现在解决Setter脚本的重复问题,我们用泛型+反射实现一个支持所有数值类型的通用版本:

using UnityEngine;
using UnityEngine.UI;
using System.Reflection;

public class GenericImageFillSetter : MonoBehaviour
{
    [SerializeField]
    private ScriptableObject variable;
    [SerializeField]
    private Image targetImage;

    private PropertyInfo _valueProperty;

    private void Awake()
    {
        // 反射获取Variable<T>的value属性
        _valueProperty = variable.GetType().GetProperty("value", BindingFlags.Public | BindingFlags.Instance);
    }

    private void Update()
    {
        if (_valueProperty == null) return;

        var value = _valueProperty.GetValue(variable);
        // 利用IConvertible接口自动转换为float(所有C#数值类型都支持)
        if (value is IConvertible convertible)
        {
            targetImage.fillAmount = convertible.ToSingle(null);
        }
    }
}

这个脚本不需要为每种类型单独编写,只要是能转换为float的数值类型,都能自动适配,完美解决重复实现的问题。

额外提示:Inspector显示优化

默认情况下,泛型基类的initialValue字段在Inspector里会正常显示,因为我们给它加了[SerializeField]标记,你创建的FloatVariable、IntVariable等资产,在Inspector里能直接编辑初始值,和之前的单独类体验完全一致。

内容的提问来源于stack exchange,提问作者João Santana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:58:19