我一直在网上寻找如何在运行时为每个场景(级别)动态创建一个按钮,但找不到。我发现可以使用类型为Object的公共变量以及SceneAsset在检查器中引用统一的场景文件,但这似乎仅在编辑器中有效,而当该游戏是为Android构建的时,它将无法正常工作。我不想创建每个按钮,然后手动为每个场景传递sceneName参数。
答案 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
类,但不幸的是,它可以不能在检查器中轻松设置。
您可以通过多种方式解决该限制:
SceneManager.GetSceneAt
来按索引获取每个场景。与SceneManager.sceneCountInBuildSettings
结合使用,这可能是确保在构建中包括每个场景的最简单方法。"Level_"+n
是有效场景(如果n在1和级别总数之间)。这比对全名进行硬编码(如果添加了不遵循约定的场景,将不起作用)要脆弱得多,但是添加新场景时的维护开销较小。个人而言,过去我需要使用命名约定(上面的#3),但这是一个单人项目,因此使用命名约定的培训成本实际上为零。 >