using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditorInternal; using UnityEngine; namespace Mirror { [CustomEditor(typeof(NetworkManager), true)] [CanEditMultipleObjects] public class NetworkManagerEditor : Editor { SerializedProperty spawnListProperty; ReorderableList spawnList; protected NetworkManager networkManager; protected void Init() { if (spawnList == null) { networkManager = target as NetworkManager; spawnListProperty = serializedObject.FindProperty("spawnPrefabs"); spawnList = new ReorderableList(serializedObject, spawnListProperty) { drawHeaderCallback = DrawHeader, drawElementCallback = DrawChild, onReorderCallback = Changed, onRemoveCallback = RemoveButton, onChangedCallback = Changed, onAddCallback = AddButton, // this uses a 16x16 icon. other sizes make it stretch. elementHeight = 16 }; } } public override void OnInspectorGUI() { Init(); DrawDefaultInspector(); EditorGUI.BeginChangeCheck(); spawnList.DoLayoutList(); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); } if (GUILayout.Button("Populate Spawnable Prefabs")) { ScanForNetworkIdentities(); } } void ScanForNetworkIdentities() { List identities = new List(); bool cancelled = false; try { string[] paths = EditorHelper.IterateOverProject("t:prefab").ToArray(); int count = 0; foreach (string path in paths) { // ignore test & example prefabs. // users sometimes keep the folders in their projects. if (path.Contains("Mirror/Tests/") || path.Contains("Mirror/Examples/")) { continue; } if (EditorUtility.DisplayCancelableProgressBar("Searching for NetworkIdentities..", $"Scanned {count}/{paths.Length} prefabs. Found {identities.Count} new ones", count / (float)paths.Length)) { cancelled = true; break; } count++; NetworkIdentity ni = AssetDatabase.LoadAssetAtPath(path); if (!ni) { continue; } if (!networkManager.spawnPrefabs.Contains(ni.gameObject)) { identities.Add(ni.gameObject); } } } finally { EditorUtility.ClearProgressBar(); if (!cancelled) { // RecordObject is needed for "*" to show up in Scene. // however, this only saves List.Count without the entries. Undo.RecordObject(networkManager, "NetworkManager: populated prefabs"); // add the entries networkManager.spawnPrefabs.AddRange(identities); // sort alphabetically for better UX networkManager.spawnPrefabs = networkManager.spawnPrefabs.OrderBy(go => go.name).ToList(); // SetDirty is required to save the individual entries properly. EditorUtility.SetDirty(target); } // Loading assets might use a lot of memory, so try to unload them after Resources.UnloadUnusedAssets(); } } static void DrawHeader(Rect headerRect) { GUI.Label(headerRect, "Registered Spawnable Prefabs:"); } internal void DrawChild(Rect r, int index, bool isActive, bool isFocused) { SerializedProperty prefab = spawnListProperty.GetArrayElementAtIndex(index); GameObject go = (GameObject)prefab.objectReferenceValue; GUIContent label; if (go == null) { label = new GUIContent("Empty", "Drag a prefab with a NetworkIdentity here"); } else { NetworkIdentity identity = go.GetComponent(); label = new GUIContent(go.name, identity != null ? $"AssetId: [{identity.assetId}]" : "No Network Identity"); } GameObject newGameObject = (GameObject)EditorGUI.ObjectField(r, label, go, typeof(GameObject), false); if (newGameObject != go) { if (newGameObject != null && !newGameObject.GetComponent()) { Debug.LogError($"Prefab {newGameObject} cannot be added as spawnable as it doesn't have a NetworkIdentity."); return; } prefab.objectReferenceValue = newGameObject; } } internal void Changed(ReorderableList list) { EditorUtility.SetDirty(target); } internal void AddButton(ReorderableList list) { spawnListProperty.arraySize += 1; list.index = spawnListProperty.arraySize - 1; SerializedProperty obj = spawnListProperty.GetArrayElementAtIndex(spawnListProperty.arraySize - 1); obj.objectReferenceValue = null; spawnList.index = spawnList.count - 1; Changed(list); } internal void RemoveButton(ReorderableList list) { spawnListProperty.DeleteArrayElementAtIndex(spawnList.index); if (list.index >= spawnListProperty.arraySize) { list.index = spawnListProperty.arraySize - 1; } } } }