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()); } }
使用方式
- 将Baker子类改为
partial类,比如public partial class PieBaker : Baker - 点击Unity编辑器顶部菜单
Tools/Generate Baker Create Methods - 自动生成包含创建逻辑的代码文件,无需手动编写
内容的提问来源于stack exchange,提问作者TheUmpteenth
相关产品推荐
相关产品推荐

