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

Unity C#中ScriptableObject派生类静态创建方法的DRY优化问询

Unity ScriptableObject子类创建逻辑的DRY复用方案

针对ScriptableObject子类重复编写CreateAsset/CreateInstance方法的问题,以下几种方案可以实现代码复用,符合*DRY(Don't Repeat Yourself)*原则:

方案1:泛型工具类抽离通用逻辑

把重复的实例创建、资源保存逻辑抽离到泛型静态工具类中,子类仅需提供资源路径并调用工具方法即可,避免重复编写核心逻辑。

泛型工具类代码

using UnityEngine;
using UnityEditor;

public static class BakerCreator
{
    public static void CreateAsset<T>(string assetPath, bool updateAssetDatabase = true) where T : Baker
    {
        var instance = ScriptableObject.CreateInstance<T>();
        AssetDatabase.CreateAsset(instance, assetPath);
        
        if (updateAssetDatabase)
        {
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
    }

    public static T CreateInstance<T>() where T : Baker
    {
        return ScriptableObject.CreateInstance<T>();
    }
}

子类调用示例(以PieBaker为例)

public class PieBaker : Baker
{
    private static string AssetPath => "Assets/PieBakerAsset.asset";

    public static new void CreateAsset(bool updateAssetDatabase = true)
    {
        BakerCreator.CreateAsset<PieBaker>(AssetPath, updateAssetDatabase);
    }

    public static new Baker CreateInstance()
    {
        return BakerCreator.CreateInstance<PieBaker>();
    }
}

方案2:基类结合反射实现通用创建

如果不想在每个子类中写包装方法,可以在基类通过反射获取调用方的子类类型,同时要求子类重写资源路径属性,实现无重复代码的创建逻辑。

基类修改代码

using UnityEngine;
using UnityEditor;
using System.Diagnostics;
using System.Reflection;

public class Baker : ScriptableObject
{
    // 子类必须重写该属性指定资源路径
    protected virtual string AssetPath => throw new System.NotImplementedException("子类需重写AssetPath属性");

    // 泛型版本,明确指定子类类型调用
    public static void CreateAsset<T>(bool updateAssetDatabase = true) where T : Baker
    {
        var instance = ScriptableObject.CreateInstance<T>();
        AssetDatabase.CreateAsset(instance, instance.AssetPath);
        
        if (updateAssetDatabase)
        {
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
    }

    // 非泛型版本,通过反射自动识别调用的子类类型
    public static void CreateAsset(bool updateAssetDatabase = true)
    {
        // 获取调用该方法的子类类型
        var stackTrace = new StackTrace();
        var callerMethod = stackTrace.GetFrame(1).GetMethod();
        var callerType = callerMethod.DeclaringType;
        
        var instance = (Baker)ScriptableObject.CreateInstance(callerType);
        AssetDatabase.CreateAsset(instance, instance.AssetPath);
        
        if (updateAssetDatabase)
        {
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }
    }
}

子类实现示例

public class PieBaker : Baker
{
    protected override string AssetPath => "Assets/PieBakerAsset.asset";
}

注意:反射方式在编辑器环境下使用完全没问题,但如果在运行时频繁调用需考虑性能开销。

方案3:编辑器代码生成(适合大量子类场景)

如果存在大量Baker子类,可以编写编辑器脚本自动为每个子类生成创建方法代码,彻底消除重复编写的工作,后续修改逻辑只需重新生成即可。

编辑器代码生成脚本

using UnityEditor;
using System.IO;
using System.Text;
using System.Linq;

public class BakerCodeGenerator : EditorWindow
{
    [MenuItem("Tools/Generate Baker Create Methods")]
    public static void ShowGeneratorWindow()
    {
        GetWindow<BakerCodeGenerator>("Baker代码生成");
    }

    private void OnGUI()
    {
        if (GUILayout.Button("生成所有Baker子类的创建方法"))
        {
            GenerateAllBakerSubclassMethods();
            AssetDatabase.Refresh();
            EditorUtility.DisplayDialog("完成", "所有Baker子类的创建方法已生成", "确定");
        }
    }

    private void GenerateAllBakerSubclassMethods()
    {
        // 遍历所有继承自Baker的非抽象类
        var bakerSubclasses = System.AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(assembly => assembly.GetTypes())
            .Where(type => type.IsSubclassOf(typeof(Baker)) && !type.IsAbstract);

        foreach (var subclass in bakerSubclasses)
        {
            GenerateCreateMethodsForSubclass(subclass);
        }
    }

    private void GenerateCreateMethodsForSubclass(System.Type subclass)
    {
        var className = subclass.Name;
        var assetPath = $"Assets/{className}Asset.asset";
        // 生成代码的保存路径,可根据项目结构调整
        var outputPath = $"Assets/Scripts/Bakers/AutoGen/{className}CreateMethods.cs";

        // 拼接生成代码
        var codeBuilder = new StringBuilder();
        codeBuilder.AppendLine("using UnityEngine;");
        codeBuilder.AppendLine("using UnityEditor;");
        codeBuilder.AppendLine();
        codeBuilder.AppendLine($"public partial class {className} : Baker");
        codeBuilder.AppendLine("{");
        codeBuilder.AppendLine($"    private static string AssetPath => \"{assetPath}\";");
        codeBuilder.AppendLine();
        codeBuilder.AppendLine("    public static new void CreateAsset(bool updateAssetDatabase = true)");
        codeBuilder.AppendLine("    {");
        codeBuilder.AppendLine("        var instance = CreateInstance();");
        codeBuilder.AppendLine("        AssetDatabase.CreateAsset(instance, AssetPath);");
        codeBuilder.AppendLine("        if (updateAssetDatabase)");
        codeBuilder.AppendLine("        {");
        codeBuilder.AppendLine("            AssetDatabase.SaveAssets();");
        codeBuilder.AppendLine("            AssetDatabase.Refresh();");
        codeBuilder.AppendLine("        }");
        codeBuilder.AppendLine("    }");
        codeBuilder.AppendLine();
        codeBuilder.AppendLine("    public static new Baker CreateInstance()");
        codeBuilder.AppendLine("    {");
        codeBuilder.AppendLine($"        return CreateInstance<{className}>();");
        codeBuilder.AppendLine("    }");
        codeBuilder.AppendLine("}");

        // 创建目录并写入文件
        Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
        File.WriteAllText(outputPath, codeBuilder.ToString());
    }
}

使用方式

  1. 将Baker子类改为partial类,比如public partial class PieBaker : Baker
  2. 点击Unity编辑器顶部菜单Tools/Generate Baker Create Methods
  3. 自动生成包含创建逻辑的代码文件,无需手动编写

内容的提问来源于stack exchange,提问作者TheUmpteenth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 01:05:01