从Unity中启动构建多场景,应该如何操作?

8
在Unity中,多场景编辑是一个非常有用的功能,它允许我们通过编辑器播放模式启动当前场景,并保持其当前的层次结构状态。
然而,在构建和运行项目时,它并不识别编辑器中当前场景的设置,并以Build Settings中设置的内容开始。
那么,有没有办法让构建过程意识到多场景编辑层次结构的当前编辑器状态,并构建和运行该设置呢?
2个回答

8

1. 将编辑器场景添加到构建设置中

首先,您可以使用编辑器脚本来收集设置,使用 EditorSceneManager.GetSceneManagerSetup 来接收编辑器中场景的当前设置。

  • 我假设您只想要 loaded 场景,因此请创建仅包含 isLoaded = true 的场景列表。
  • 使用 EditorBuildSettings.scenes 将这些场景添加到构建设置中。

1.a. 在 MenuItem 单击时更新

我将其作为菜单中的额外按钮,因为您可能不希望它总是自动执行。

using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;

public static class UpdateBuildSettigns
{
    [MenuItem("Example/UpdateBuildSettings")]
    public static void UpdateSettings()
    {
        // get current editor setup
        SceneSetup[] editorScenes = EditorSceneManager.GetSceneManagerSetup();

        // filter list e.g. get only scenes with isActive true
        var activeEditorScenes = editorScenes.Where(scene => scene.isLoaded);

        // set those scenes as the buildsettings
        List<EditorBuildSettingsScene> editorBuildSettingsScenes = new List<EditorBuildSettingsScene>();
        foreach (var sceneAsset in activeEditorScenes)
        {
            string scenePath = sceneAsset.path;

            // ignore unsaved scenes
            if (!string.IsNullOrEmpty(scenePath)) continue;

            editorBuildSettingsScenes.Add(new EditorBuildSettingsScene(scenePath, true));
        }

        // Set the Build Settings window Scene list
        EditorBuildSettings.scenes = editorBuildSettingsScenes.ToArray();
    }
}

菜单按钮更新

输入图像描述


1.b. 在场景加载和卸载时自动更新

如果您想要自动发生更新,您还可以将调用添加为回调到EditorSceneManager.sceneOpenedEditorSceneManager.sceneClosed,使用InitializeOnLoad和一个静态构造函数,以在重新编译或打开Unity Editor后添加回调。

using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;

[InitializeOnLoad]
public static class UpdateBuildSettigns
{
    // ofcourse you still can also call it via menu item
    [MenuItem("Example/UpdateBuildSettings")]
    public static void UpdateSettings()
    {
        //...
    }

    static UpdateBuildSettigns()
    {
        // it is always save to remove callbacks even if they are not there
        // makes sure they are always only added once
        //
        // this is a static constructor so actually there should be no
        // callbacks yet ... but .. you never know ;)
        EditorSceneManager.sceneOpened -= OnSceneLoaded;
        EditorSceneManager.sceneClosed -= OnSceneUnloaded;

        EditorSceneManager.sceneOpened += OnSceneLoaded;
        EditorSceneManager.sceneClosed += OnSceneUnloaded;
    }

    private static void OnSceneUnloaded(Scene current)
    {
        UpdateSettings();
    }

    private static void OnSceneLoaded(Scene current, OpenSceneMode mode)
    {
        UpdateSettings();
    }
}

使用自动更新

输入图像描述


1.c. 启用/禁用自动更新

如果您想要更多的控制权,您还可以添加额外的菜单项来启用和禁用自动更新,例如:

// flag to check if auto-updates are currently enabled
private static bool isEnabled;

// disable the "EnableAutoUpdate" button if already enabled
[MenuItem("Example/EnableAutoUpdate", true)]
private static bool CanEnable()
{
    return !isEnabled;
}

// disable the "DisableAutoUpdate" button if already disabled
[MenuItem("Example/DisableAutoUpdate", true)]
private static bool CanDisable()
{
    return isEnabled;
}

// add callbacks
[MenuItem("Example/EnableAutoUpdate")]
private static void EnableAutoUpdate()
{
    // it is always save to remove callbacks even if they are not there
    // makes sure they are always only added once
    EditorSceneManager.sceneOpened -= OnSceneLoaded;
    EditorSceneManager.sceneClosed -= OnSceneUnloaded;

    EditorSceneManager.sceneOpened += OnSceneLoaded;
    EditorSceneManager.sceneClosed += OnSceneUnloaded;

    isEnabled = true;
}

// remove callbacks
[MenuItem("Example/DisableAutoUpdate")]
private static void DisableAutoUpdate()
{
    EditorSceneManager.sceneOpened -= OnSceneLoaded;
    EditorSceneManager.sceneClosed -= OnSceneUnloaded;

    isEnabled = false;
}

注意,由于使用了UnityEditor命名空间,您应该将此脚本放置在一个编辑器文件夹中或使用正确的预处理器。
#if UNITY_EDITOR

// above code here

#endif

2. 从构建设置中加载所有场景

稍后在第一个场景运行应用程序时,应该有一个负责加载所有这些场景的脚本。例如:

// making it a component to make sure it is inside of one scene
public class SceneLoader : MonoBehaviour
{
    private void Start()
    {
        var thisScene = SceneManager.GetActiveScene();

        // load all scenes
        for(int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
        {
            // skip if is current scene since we don't want it twice
            if(thisScene.buildIndex == i) continue;

             // Skip if scene is already loaded
            if(SceneManager.GetSceneByBuildIndex(i).IsValid()) continue;

            SceneManager.LoadScene(i, LoadSceneMode.Additive);
            // or depending on your usecase
            SceneManager.LoadSceneAsync(i, LoadSceneMode.Additive);
        }
    }
}

refs:


1
进入Unity尝试理解。带着头疼回来了。太神奇了。这是一篇文章。我认为200分远远不够。 - Confused
@困惑 不用谢,很高兴能帮到你 ;) - derHugo
我讨厌法西斯主义。而且这个地方充满了它的气息。我离开了一段时间,专注于游戏的其他部分。结果他们自动将一半的积分授予了另一个答案。多么糟糕的预测行为发明啊。 - Confused
这是尽管你的答案已经获得了更多的投票。难以置信。 - Confused
@Confused 我不知道这与法西斯主义有什么关系。我不在乎这些分数,但是在回答问题时我确实学到了一些新东西 ;) 你可以向管理员投诉,但我认为无法要回赏金分数^^ 看起来如果有疑问,它们只会授予给第一个给出的答案,我必须承认我的答案是在最后一天提交的;) - derHugo
显示剩余2条评论

4
我建议在Unity中的启动场景附加某种脚本,以便在游戏启动后触发其余所需场景的加载。这需要一些调整才能正确开始(例如,在尝试加载场景之前检测到它们尚未加载)。如果需要,我可能会使用代码片段来扩展答案以实现结果。目前,您可以在此处查看文档:https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.GetSceneByName.htmlhttps://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadSceneAsync.html
基本思路是:
  1. 使用SceneManager.GetSceneByName获取必需的场景,并过滤掉已经加载的所有场景。
  2. 对于尚未加载的场景,调用LoadSceneAsync并附加某种协程以检查加载进度。
  3. 当所有场景都加载完成后,运行回调函数,以便游戏的其余部分知道场景已经加载完成,可以运行其余需要依赖于这些场景的必要操作。
如果要在构建时保留当前层次结构(一组在编辑器中打开的场景),则可以使用BuildPipeline实现: https://docs.unity3d.com/Manual/BuildPlayerPipeline.html 有一种方法可以使用可编程访问的场景列表创建构建:
 // Get filename.
 string path = EditorUtility.SaveFolderPanel("Choose Location of Built Game", "", "");
 string[] levels = new string[] {"Assets/Scene1.unity", "Assets/Scene2.unity"}; // You'd have to assemble this list yourself.
 // Build player.
 BuildPipeline.BuildPlayer(levels, path + "/BuiltGame.exe", BuildTarget.StandaloneWindows, BuildOptions.None);

(你可以根据当前加载的场景来确定,这不会是一个标准的(Cmd + b)构建,但非常接近。)


我尝试了几种这样的方法,但一直遇到构建设置干扰的问题。我有一个特定的构建设置,适用于整个游戏。然而,在编辑一组嵌套场景并希望从那里作为构建进行播放时,我无法使其正常工作,而不会弄乱现有的构建设置,我不想失去它们。感觉就像我必须维护两个单独的项目。一个用于工作和处理,另一个作为使用您上述技术完成的作品的最终目的地。 - Confused
你不应该只是能够使用上述方法逐步复制所需的确切场景配置吗(例如,只需在任何给定时刻加载缺失的场景)?如果这样做行不通,我可能误解了你的问题。你能否提供一个示例项目,并详细描述它在不同情况下的加载方式(从编辑器开始和构建时)? - MarengoHue
想象一下当前的 [command + B] 构建是根据项目的最终完整状态构建,从“主页”场景开始。而 [command + alt + B] 则构建当前场景及其多场景设置为游戏,即使当前场景中的场景尚未添加到构建设置中。 - Confused
我已经更新了答案,包括BuildPipeline信息。 - MarengoHue
它允许您自定义Unity构建播放器的方式,包括您刚才提到的内容。请查看手册页面-它已经有了您需要的几乎所有代码(除了可由您自己编写的构建包含场景列表的代码)。 - MarengoHue
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接