Как создать кнопку для каждой сцены (уровня) во время выполнения. Unity3d - PullRequest
0 голосов
/ 04 апреля 2019

Я искал во всем Интернете, как динамически создать кнопку для каждой сцены (уровня) во время выполнения, но не смог найти. Я обнаружил, что вы можете ссылаться на файл сцены единства в инспекторе, используя открытую переменную типа Object, а также SceneAsset, но, похоже, это работает только в редакторе, а когда игра построена для Android, она не работает. Я не хочу создавать каждую кнопку, а затем вручную передавать параметр sceneName для каждой сцены.

Ответы [ 3 ]

0 голосов
/ 04 апреля 2019

Ваш вопрос очень широкий ...

Чтобы поместить сцены в список в Инспекторе, я использовал это:

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();
    }
}

Теперь вы можете просто сослаться на ресурсы сцены и получить соответствующие ScenePaths и имена.


Теперь к кнопкам,Самый простой способ - использовать одну кнопку в качестве префаба, создавать и настраивать кнопки во время выполнения, например,

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
0 голосов
/ 05 апреля 2019

SceneAsset работает только в редакторе (поэтому он находится в пространстве имен UnityEditor.) Представлением сцены во время выполнения является класс Scenemangement.Scene, но, к сожалению, его нельзя легко установить в инспектор.

Вы можете обойти это ограничение несколькими способами:

  1. Если вы не используете комплекты ресурсов, вы можете использовать SceneManager.GetSceneAt для получения каждой сцены по индексу. В сочетании с SceneManager.sceneCountInBuildSettings это, вероятно, самый простой способ убедиться, что вы включили каждую сцену в сборку.
  2. Если имена ваших сцен остаются согласованными, вы можете просто использовать массив строк, которые вы установили в инспекторе.
  3. Если имена ваших сцен соответствуют шаблону, вы можете написать свой код, предполагая этот шаблон (например: если сцены называются «Level_1», «Level_2» и т. Д., То вы знаете, что "Level_"+n является допустимой сценой , если n находится между 1 и общим количеством уровней). Это немного более хрупко, чем жесткое кодирование полных имен (если сцена когда-либо добавляется, которая не следует соглашению, она не будет работать), но имеет меньше затрат на сопровождение при добавлении новых сцен.
  4. Если вам нужна система, ориентированная на будущее и устойчивая к ошибкам, вы можете создать специальный инспектор. Это может быть настолько просто или настолько многофункционально, насколько вам нужно, но любая пользовательская реализация инспектора должна преобразовать выбор в инспекторе в имя сцены и / или путь, чтобы его можно было использовать во время выполнения. (Ответ , предоставленный @ derHugo , использует этот подход, как и некоторые ответы в аналогичной теме ответов Unity .)

Лично я использовал соглашение об именах (№ 3 выше), когда мне нужно было сделать это в прошлом, но это был проект с одним человеком, поэтому стоимость обучения использованию соглашения об именах была фактически нулевой.

0 голосов
/ 04 апреля 2019

Вы пытались сделать готовый из кнопки? Вы также можете создать сценарий, который получает имя сцены из SceneManager.GetActiveScene().name, а затем присоединить этот сценарий к вашему префабу. Затем вы можете инициализировать объект этого префаба во время выполнения с помощью:

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

Наконец, вам нужно сослаться на свой холст в этом скрипте, чтобы вы могли установить его как дочерний.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...