如何在运行时为每个场景(级别)创建按钮。 Unity3d

时间:2019-04-04 18:08:17

标签: c# android unity3d

我一直在网上寻找如何在运行时为每个场景(级别)动态创建一个按钮,但找不到。我发现可以使用类型为Object的公共变量以及SceneAsset在检查器中引用统一的场景文件,但这似乎仅在编辑器中有效,而当该游戏是为Android构建的时,它将无法正常工作。我不想创建每个按钮,然后手动为每个场景传递sceneName参数。

3 个答案:

答案 0 :(得分:0)

您是否尝试过制作按钮的预制件?您还可以创建一个脚本,该脚本从SceneManager.GetActiveScene().name获取场景名称,然后将该脚本附加到您的预制件上。然后,您可以在运行时使用以下方法初始化该预制对象:

(GameObject)Instantiate(Resources.Load("MyPrefab"))

最后,您必须在该脚本中引用您的画布,以便您可以将其设置为子代。

答案 1 :(得分:0)

您的问题确实很广泛...

为了将场景放入检查器中的列表,我使用了以下方法:

public class ScenePathManager : MonoBehaviour
{
    [Serializable]
    public class SceneElement
    {
        string Path;
        string Name;
    }

    public List<SceneElement> Scenes = new List<SceneElement>();

    // Loading scenes by path is more secure
    // since two scenes might have the same name
    // but the path is always unique
    public void LoadScene(string path)
    {
        if(string.IsNullOrEmpty(path))
        {
            Debug.LogError("Given path is null or empty!", this);
            return;
        }

        if(!Scenes.Any(s=>string.Equals(s.Path, path)))
        {
            Debug.LogError("Given path " + path + " is invalid!", this);
            return;
        }

        // Load the Scene here e.g. using
        SceneManager.LoadSceneAsync(path);
    }
}

编辑器脚本(必须放置在名为Editor的文件夹中)

using UnityEditor

[CustomEditor(typeof(ScenePathManager))]
private partial class ScenePathManagerEditor : Editor
{
    private SerializedProperty _scenes;

    private ReorderableList _sceneList;

    private void OnEnable()
    {
        _scenes = serializedObject.FindProperty("Scenes");

        _sceneList = new ReorderableList(serializedObject, _scenes)
        {
            displayAdd = true,
            displayRemove = true,
            draggable = true,
            drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Scenes"),
            drawElementCallback = (rect, index, selected, highlighted) =>
            {
                var element = _scenes.GetArrayElementAtIndex(index);
                var path = element.FindPropertyRelative("Path");
                var name = element.FindPropertyRelative("Name");

                // get the SceneAsset that belongs to the current path
                var scene = AssetDatabase.LoadAssetAtPath<SceneAsset>(path.stringValue);

                // draw an object field in the editor
                scene = (SceneAsset)EditorGUI.ObjectField(new Rect(rect.x, rect.y, rect.width * 0.5f, EditorGUIUtility.singleLineHeight), scene, typeof(SceneAsset), false);

                // write back the path of the SceneAsset
                path.stringValue = AssetDatabase.GetAssetOrScenePath(scene);
                name.stringValue = scene.name;
            },
            elementHeight = EditorGUIUtility.singleLineHeight * 1.2f
        };
    }

    public override void OnInspectorGUI()
    {
        DrawScriptField();

        serializedObject.Update();

        // Disable editing of the list on runtime
        EditorGUI.BeginDisabledGroup(true);
        _sceneList.DoLayoutList();
        EditorGUI.EndDisabledGroup();

        serializedObject.ApplyModifiedProperties();
    }

    // Draws the usual Script field in the Inspector
    private void DrawScriptField()
    {
        // Disable editing
        EditorGUI.BeginDisabledGroup(true);
        EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((ScenePathManager)target), typeof(ScenePathManager), false);
        EditorGUI.EndDisabledGroup();

        EditorGUILayout.Space();
    }
}

现在,您可以简单地引用场景资产并获得相应的场景路径和名称。


现在到按钮。最简单的方法是预制一个按钮,并在运行时实例化和配置按钮,例如

public class ButtonSpawner : MonoBehaviour
{
    // somehow get this reference e.g. via drag and drop in the Inspector
    [SerializeField] private ScenePathManager _scenePathManager;

    // reference your prefab here
    [SerializeField] private Button _buttonPrefab;

    public void SpawnButtons()
    {
        foreach(var scene in _scenePathManager.Scenes)
        {
            // ofcourse you will want to spawn this maybe as
            // child of another object or position them etc
            var button = Instantiate(_buttonPrefab);

            // However you want to get the text. You could also give each button
            // special MonoBehaviour class and reference all components you need
            var buttonText = button.GetComponentInChildren<Text>(true);

            buttonText.text = scene.Name;

            button.onClick.AddListener(() =>
            {
                _scenePathManager.LoadScene(scene.Path);
            });
        }
    }
}

您甚至可以自动添加所有引用到构建设置的场景(我仅将其用于附加场景加载,但这只是一个主意;))

#if UNITY_EDITOR
    public void AddListsToBuildSettings()
    {
        var editorBuildSettingsScenes = new List<EditorBuildSettingsScene>
        {
            // Pre-add the current Scene
            new EditorBuildSettingsScene(SceneManager.GetActiveScene().path, true)
        };

        // Add all scenes in ScenePaths to the EditorBuildSettings
        foreach (var scene in ScenePaths)
        {
            // ignore unsaved scenes
            if (string.IsNullOrEmpty(scene?.Path)) continue;

            // skip if scene already in buildSettings
            if (editorBuildSettingsScenes.Any(s => string.Equals(s.path, scene))) continue;

            editorBuildSettingsScenes.Add(new EditorBuildSettingsScene(scene.Path, true));
        }

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

答案 2 :(得分:0)

SceneAsset仅在编辑器中起作用(这就是为什么它在UnityEditor命名空间中的原因。)场景的运行时表示形式是Scenemangement.Scene类,但不幸的是,它可以不能在检查器中轻松设置。

您可以通过多种方式解决该限制:

  1. 如果您不使用资产捆绑包,则可以使用SceneManager.GetSceneAt来按索引获取每个场景。与SceneManager.sceneCountInBuildSettings结合使用,这可能是确保在构建中包括每个场景的最简单方法。
  2. 如果场景名称保持一致,则只需使用在检查器中设置的字符串数组即可。
  3. 如果场景名称遵循某个模式,则可以使用该模式来编写代码(例如:如果场景被命名为“ Level_1”,“ Level_2”等,那么您将知道"Level_"+n是有效场景(如果n在1和级别总数之间)。这比对全名进行硬编码(如果添加了不遵循约定的场景,将不起作用)要脆弱得多,但是添加新场景时的维护开销较小。
  4. 如果您需要一个能够应对未来且防错的系统,则可以创建一个自定义检查器。这可以根据需要简单或丰富,但是任何自定义检查器实现都需要将检查器中的选择转换为场景名称和/或路径,以便可以在运行时使用。 (answer provided by @derHugo使用这种方法,similar Unity Answers topic上的一些答案也是如此。)

个人而言,过去我需要使用命名约定(上面的#3),但这是一个单人项目,因此使用命名约定的培训成本实际上为零。 >