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

Unity中无法从EditorWindow派生类创建ScriptableObject资产求助

Unity Editor工具创建ScriptableObject失败问题排查与解决

问题描述

我正在开发一款Unity工具,功能是从Timeline提取数据,为DAW生成会话文件,并创建可作为游戏内音频事件的ScriptableObject。目前DAW会话文件生成正常,但创建ScriptableObject时遇到报错。

代码结构是用EditorWindow派生类调用一个MonoBehaviour类中的协程来创建ScriptableObject(协程用于下载静音音频文件并赋值给ScriptableObject字段,已排除协程本身的问题)。猜测可能是EditorWindow和MonoBehaviour的工作机制差异导致,但不确定。

相关代码

ReaperSessionTimeline(EditorWindow类)

public class ReaperSessionTimeline:EditorWindow
{
    private void OnGUI()
    {
        timelineAsset = 
            EditorGUILayout.ObjectField("TimelineAsset", timelineAsset, typeof(TimelineAsset), false) as
                TimelineAsset;
        if(GUILayout.Button("Create"))
            this.StartCoroutine(audioEventCreator.CreateAudioEvent(timelineAsset);
    }
}

AudioEventCreator(MonoBehaviour类)

namespace SettingsGenerator
{
    public class AudioEvent: ScriptableObject
    {
        public AudioClip clip;
    }

    public class AudioEventCreator: MonoBehaviour
    {
        AudioClip clip;
        public IEnumerator CreateAudioEvent(TimelineAsset tm)
        {
            string folderPath = $"{Application.dataPath}/Audio/sfx/cinematics/{tm.name}";
           
            if (!AssetDatabase.IsValidFolder(folderPath))
            {
                System.IO.Directory.CreateDirectory(folderPath);
            }
            // 为简洁起见移除了UnityWebRequest代码

            AudioEvent audioEvent = ScriptableObject.CreateInstance<SettingsGenerator.AudioEvent>();
            audioEvent.clip = clip;
            string name = AssetDatabase.GenerateUniqueAssetPath($"{folderPath}/{tm.name}.asset");
            AssetDatabase.CreateAsset(audioEvent, name);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            EditorUtility.FocusProjectWindow();
            Selection.activeObject = audioEvent;
        }
    } 
}

控制台报错信息

Couldn't create asset file!
UnityEngine.StackTraceUtility:ExtractStackTrace ()
SettingsGenerator.AudioEventCreator/<CreateAudioEvent>d__1:MoveNext () (at Assets/Editor/reaper_sessions_unity_timeline_tool/AudioEventCreator.cs:57)
Unity.EditorCoroutines.Editor.EditorCoroutine/YieldProcessor:MoveNext (System.Collections.IEnumerator) (at ./Library/PackageCache/com.unity.editorcoroutines@1.0.0/Editor/EditorCoroutine.cs:80)
Unity.EditorCoroutines.Editor.EditorCoroutine:ProcessIEnumeratorRecursive (System.Collections.IEnumerator) (at ./Library/PackageCache/com.unity.editorcoroutines@1.0.0/Editor/EditorCoroutine.cs:134)
Unity.EditorCoroutines.Editor.EditorCoroutine:MoveNext () (at ./Library/PackageCache/com.unity.editorcoroutines@1.0.0/Editor/EditorCoroutine.cs:115)
UnityEditor.EditorApplication:Internal_CallUpdateFunctions ()

另一条报错:

UnityException: Creating asset at path D:/Documents/unity_tools/Assets/Audio/sfx/cinematics/intro/intro.asset failed.
SettingsGenerator.AudioEventCreator+<CreateAudioEvent>d__1.MoveNext () (at Assets/Editor/reaper_sessions_unity_timeline_tool/AudioEventCreator.cs:57)
Unity.EditorCoroutines.Editor.EditorCoroutine+YieldProcessor.MoveNext (System.Collections.IEnumerator enumerator) (at ./Library/PackageCache/com.unity.editorcoroutines@1.0.0/Editor/EditorCoroutine.cs:80)
Unity.EditorCoroutines.Editor.EditorCoroutine.ProcessIEnumeratorRecursive (System.Collections.IEnumerator enumerator) (at ./Library/PackageCache/com.unity.editorcoroutines@1.0.0/Editor/EditorCoroutine.cs:134)
Unity.EditorCoroutines.Editor.EditorCoroutine:MoveNext () (at ./Library/PackageCache/com.unity.editorcoroutines@1.0.0/Editor/EditorCoroutine.cs:115)
UnityEditor.EditorApplication.Internal_CallUpdateFunctions () (at <fd823a7663ea4cdb840e6c898e2256ce>:0)

排查过非协程的常规方法,也尝试用Unity官方文档示例创建材质资产,均出现相同错误。不理解控制台中的MoveNext方法是什么,想知道问题根源是否和EditorWindow的特殊工作方式有关。


问题根源与解决方案

1. 路径格式错误(核心问题)

AssetDatabase的API要求路径必须是相对于项目Assets文件夹的相对路径,而你用了Application.dataPath拼接出的绝对路径,这会直接导致AssetDatabase.CreateAsset失败。比如你现在的绝对路径D:/Documents/unity_tools/Assets/Audio/...,AssetDatabase需要的是Assets/Audio/sfx/cinematics/...这种格式。

2. AudioEventCreator继承MonoBehaviour的不合理性

在Editor工具中,无需继承MonoBehaviour——MonoBehaviour是用于运行时挂载在GameObject上的组件,Editor工具可以直接用普通静态类,避免不必要的生命周期问题。

3. 关于MoveNext的解释

MoveNext是C#迭代器(包括协程)的核心方法,协程本质是状态机,每次执行到yield语句时暂停,下次恢复时调用MoveNext继续执行。报错里的MoveNext只是说明错误发生在协程执行过程中,并非问题根源。

修正后的代码示例

修改AudioEventCreator为普通静态类

namespace SettingsGenerator
{
    public class AudioEvent : ScriptableObject
    {
        public AudioClip clip;
    }

    public static class AudioEventCreator
    {
        public static IEnumerator CreateAudioEvent(TimelineAsset tm)
        {
            // 拆分相对路径与绝对路径
            string relativeFolderPath = $"Audio/sfx/cinematics/{tm.name}";
            string absoluteFolderPath = $"{Application.dataPath}/{relativeFolderPath}";
           
            // 检查文件夹时使用Assets开头的相对路径
            if (!AssetDatabase.IsValidFolder($"Assets/{relativeFolderPath}"))
            {
                System.IO.Directory.CreateDirectory(absoluteFolderPath);
                // 创建目录后必须刷新AssetDatabase,让Unity识别新文件夹
                AssetDatabase.Refresh();
            }

            // 保留你的UnityWebRequest下载逻辑,下载完成后赋值clip变量

            AudioEvent audioEvent = ScriptableObject.CreateInstance<AudioEvent>();
            audioEvent.clip = clip;
            // 生成Assets开头的相对路径
            string assetPath = AssetDatabase.GenerateUniqueAssetPath($"Assets/{relativeFolderPath}/{tm.name}.asset");
            AssetDatabase.CreateAsset(audioEvent, assetPath);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            EditorUtility.FocusProjectWindow();
            Selection.activeObject = audioEvent;
            
            yield return null;
        }
    } 
}

修正ReaperSessionTimeline的协程调用(修复语法错误)

public class ReaperSessionTimeline : EditorWindow
{
    private TimelineAsset timelineAsset;

    private void OnGUI()
    {
        timelineAsset = 
            EditorGUILayout.ObjectField("TimelineAsset", timelineAsset, typeof(TimelineAsset), false) as
                TimelineAsset;
        // 增加空值判断,调用静态方法并修复括号语法错误
        if(GUILayout.Button("Create") && timelineAsset != null)
            this.StartCoroutine(AudioEventCreator.CreateAudioEvent(timelineAsset));
    }
}

额外注意事项

  • 创建文件夹后必须调用AssetDatabase.Refresh(),否则Unity无法立即识别新文件夹,AssetDatabase.IsValidFolder会返回错误结果。
  • 确保AudioEvent类不在Editor文件夹下(若AudioEventCreator在Editor目录,AudioEvent需放在普通Assets目录,否则打包时会丢失)。
  • 协程在EditorWindow中使用this.StartCoroutine是完全可行的,之前的问题与协程本身无关。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 20:44:51