Unity编辑器工具进阶:从异步陷阱到命令行自动化

你是否已经掌握了编写基础的 Unity 编辑器脚本,甚至懂得使用 AssetDatabase.Start/StopAssetEditing 来进行批量性能优化?恭喜你,你已经是一名合格的编辑器工具开发者了!(不清楚的可查看这篇文章

但是,当你的工具面临更复杂的挑战——比如需要处理耗时操作而不能卡死UI,或者要被集成到自动化构建流程(CI/CD)中时,两个新的“大Boss”便会悄然出现。

今天,我们就来直面这两个高级挑战:异步操作的“伪装” 和 打通命令行(Batch Mode)的“任督二脉”。

第一幕:识破异步的“伪装” —— EditorCoroutine 的正确用法

我们先来看一个非常具有迷惑性的场景。为了优化一个耗时很长的批量资源处理任务,我们学会了使用 AssetDatabase.Start/StopAssetEditing 来合并资源导入,代码看似是这样的:

// 一个看似完美,实则错误的版本
public void ProcessLotsOfAssets()
{
    AssetDatabase.StartAssetEditing(); // 暂停导入
    try
    {
        // 为了不卡死UI,我们使用了编辑器协程
        EditorCoroutine.Start(MyAsyncProcess()); 
    }
    finally
    {
        // 陷阱!这行代码会立即执行,而不是等协程结束后!
        AssetDatabase.StopAssetEditing(); // 恢复导入
    }
}

IEnumerator MyAsyncProcess()
{
    // ... 在这里循环成百上千次,进行加载、修改、保存资源 ...
    Debug.Log("正在处理资源...");
    yield return null; // 只是为了举例,实际逻辑会更复杂
}

当你满怀期待地运行它,却发现 Unity 仍然在为每一个你保存的资源单独执行“Importing”,说好的批量导入呢?

这就是异步操作的“伪装”。

EditorCoroutine.Start() 的作用仅仅是“启动”一个协程,把它挂载到编辑器的更新循环上,然后它会立刻返回,主线程会毫不停歇地继续向下执行。这意味着,finally 块里的 AssetDatabase.StopAssetEditing() 在 MyAsyncProcess 协程里的任何文件都还没来得及保存时,就已经被调用了!我们精心设计的批量模式,就这样被提前终结了。

【正确的异步收尾姿势】

要驯服这头异步猛兽,你必须记住一个核心原则:异步操作的“终点”在它的回调函数里。

EditorCoroutine.Start 提供了两个关键的参数:一个在协程成功完成时调用(onComplete),另一个在协程抛出异常时调用(onException)。我们的收尾工作(比如 StopAssetEditing()),必须放在这两个回调中。

// 正确的版本
public void ProcessLotsOfAssets()
{
    AssetDatabase.StartAssetEditing();

    EditorCoroutine.Start(
        MyAsyncProcess(), // 1. 要执行的协程

        // 2. 成功完成时的回调
        () => 
        {
            try
            {
                Debug.Log("所有任务成功完成!");
            }
            finally
            {
                // 在这里停止,才是真正的批量处理结束!
                AssetDatabase.StopAssetEditing();
            }
        },

        // 3. 发生异常时的回调
        (e) =>
        {
            try
            {
                Debug.LogError($"发生错误: {e.Message}");
            }
            finally
            {
                // 即使出错了,也要确保“刹车”被松开,避免Unity卡住
                AssetDatabase.StopAssetEditing();
            }
        }
    );
}

通过将收尾逻辑移入回调,我们确保了无论协程是正常结束还是意外终止,StopAssetEditing() 都会在所有异步任务真正尘埃落定之后,被可靠地执行。


第二幕:打通任督二脉 —— 兼容UI与命令行(Batch Mode)

现在,我们的工具在编辑器里表现完美,既不卡顿,性能又高。老板很高兴,决定把它集成到 Jenkins 的自动化构建管线里,通过命令行来调用它。

然后,构建失败了。日志里充满了刺眼的红色错误。

这是因为,你的工具在设计之初,可能只考虑了“人类用户”在编辑器UI下的使用场景。而当它进入“机器人模式”(无人值守的命令行),环境就完全变了。

【让工具具备“双重人格”】

要让工具既能服务于开发者,又能融入自动化流程,我们需要让它学会“看人下菜碟”。

1. 学会“看环境”:Application.isBatchMode

Unity 提供了一个极其有用的静态属性:Application.isBatchMode。当脚本在命令行模式下运行时,它为 true;在正常的编辑器UI下运行时,它为 false。这是我们实现双轨制逻辑的总开关。

2. 隔离“视觉”元素

命令行模式下没有图形界面,任何试图渲染UI的代码都会报错。因此,所有UI相关的API调用,都必须被包裹在环境检测中:

if (!Application.isBatchMode)
{
    EditorUtility.DisplayProgressBar("处理中", "请稍候...", 0.5f);
}

3. “同步”执行你的协程

最大的挑战来了:EditorCoroutine 依赖于编辑器的 Update 心跳,它在命令行模式下根本无法运行。那我们那些复杂的、包含 yield return 的逻辑怎么办?重写一套完全同步的版本吗?

大可不必!我们可以巧妙地创建一个同步协程执行器。它的原理很简单:用一个 while 循环来“榨干”一个协程,一次性把它从头到尾跑完。

private static void RunCoroutineSynchronously(IEnumerator coroutine)
{
    // 用一个栈来处理嵌套的协程 (yield return anotherCoroutine)
    var stack = new Stack<IEnumerator>();
    stack.Push(coroutine);

    while (stack.Count > 0)
    {
        var current = stack.Peek();
        if (current.MoveNext())
        {
            // 如果yield return返回的是另一个协程,就把它压入栈顶
            if (current.Current is IEnumerator next)
            {
                stack.Push(next);
            }
            // 注意:我们忽略了 yield return null 等待帧的指令,因为在同步模式下没有“下一帧”
        }
        else
        {
            // 当前协程执行完毕,弹出
            stack.Pop();
        }
    }
}

4. 组装你的双轨制入口

最后,在你的工具入口函数里,布下“天罗地网”:

public static void MyAwesomeToolEntry()
{
    // 如果是命令行模式...
    if (Application.isBatchMode)
    {
        Debug.Log("进入命令行模式...");
        AssetDatabase.StartAssetEditing();
        try
        {
            // ...就用同步执行器跑完所有逻辑
            RunCoroutineSynchronously(MyCoreLogicCoroutine());
        }
        finally
        {
            AssetDatabase.StopAssetEditing();
            Debug.Log("命令行处理完成。");
        }
    }
    // 否则,就是熟悉的编辑器UI模式...
    else
    {
        // ...就用异步协程,并把收尾工作放进回调里
        AssetDatabase.StartAssetEditing();
        EditorCoroutine.Start(MyCoreLogicCoroutine(), 
            () => { AssetDatabase.StopAssetEditing(); },
            (e) => { AssetDatabase.StopAssetEditing(); }
        );
    }
}

通过这番改造,你的编辑器工具就真正“进化”了。它既保留了在UI环境下的交互性和流畅性,又获得了在自动化管线中稳定运行的强大能力,成为了一个真正专业的生产力工具。

希望这两个进阶技巧,能帮助你在Unity编辑器开发的道路上走得更远、更稳!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值