#if ENABLE_UNET using System; using System.IO; using System.Reflection; using UnityEditorInternal; using UnityEngine; using UnityEngine.Networking; using UnityObject = UnityEngine.Object; namespace UnityEditor { [CustomEditor(typeof(NetworkManager), true)] [CanEditMultipleObjects] public class NetworkManagerEditor : Editor { protected SerializedProperty m_DontDestroyOnLoadProperty; protected SerializedProperty m_RunInBackgroundProperty; SerializedProperty m_NetworkAddressProperty; SerializedProperty m_NetworkPortProperty; SerializedProperty m_ServerBindToIPProperty; SerializedProperty m_ServerBindAddressProperty; protected SerializedProperty m_LogLevelProperty; SerializedProperty m_PlayerPrefabProperty; SerializedProperty m_AutoCreatePlayerProperty; SerializedProperty m_PlayerSpawnMethodProperty; SerializedProperty m_SpawnListProperty; SerializedProperty m_CustomConfigProperty; SerializedProperty m_UseWebSocketsProperty; SerializedProperty m_ChannelListProperty; ReorderableList m_ChannelList; GUIContent m_ShowNetworkLabel; GUIContent m_ShowSpawnLabel; GUIContent m_OfflineSceneLabel; GUIContent m_OnlineSceneLabel; protected GUIContent m_DontDestroyOnLoadLabel; protected GUIContent m_RunInBackgroundLabel; GUIContent m_MaxConnectionsLabel; GUIContent m_MinUpdateTimeoutLabel; GUIContent m_ConnectTimeoutLabel; GUIContent m_DisconnectTimeoutLabel; GUIContent m_PingTimeoutLabel; GUIContent m_ThreadAwakeTimeoutLabel; GUIContent m_ReactorModelLabel; GUIContent m_ReactorMaximumReceivedMessagesLabel; GUIContent m_ReactorMaximumSentMessagesLabel; GUIContent m_UseWebSocketsLabel; GUIContent m_NetworkAddressLabel; GUIContent m_NetworkPortLabel; GUIContent m_ServerBindToIPLabel; GUIContent m_ServerBindAddressLabel; GUIContent m_PlayerPrefabLabel; GUIContent m_AutoCreatePlayerLabel; GUIContent m_PlayerSpawnMethodLabel; GUIContent m_AdvancedConfigurationLabel; ReorderableList m_SpawnList; protected bool m_Initialized; protected NetworkManager m_NetworkManager; protected void Init() { if (m_Initialized) { return; } m_Initialized = true; m_NetworkManager = target as NetworkManager; m_ShowNetworkLabel = new GUIContent("Network Info", "Network host settings"); m_ShowSpawnLabel = new GUIContent("Spawn Info", "Registered spawnable objects"); m_OfflineSceneLabel = new GUIContent("Offline Scene", "The scene loaded when the network goes offline (disconnected from server)"); m_OnlineSceneLabel = new GUIContent("Online Scene", "The scene loaded when the network comes online (connected to server)"); m_DontDestroyOnLoadLabel = new GUIContent("Don't Destroy on Load", "Enable to persist the NetworkManager across scene changes."); m_RunInBackgroundLabel = new GUIContent("Run in Background", "Enable to ensure that the application runs when it does not have focus.\n\nThis is required when testing multiple instances on a single machine, but not recommended for shipping on mobile platforms."); m_MaxConnectionsLabel = new GUIContent("Max Connections", "Maximum number of network connections"); m_MinUpdateTimeoutLabel = new GUIContent("Min Update Timeout", "Minimum time network thread waits for events"); m_ConnectTimeoutLabel = new GUIContent("Connect Timeout", "Time to wait for timeout on connecting"); m_DisconnectTimeoutLabel = new GUIContent("Disconnect Timeout", "Time to wait for detecting disconnect"); m_PingTimeoutLabel = new GUIContent("Ping Timeout", "Time to wait for ping messages"); m_ThreadAwakeTimeoutLabel = new GUIContent("Thread Awake Timeout", "The minimum time period when system will check if there are any messages for send (or receive)."); m_ReactorModelLabel = new GUIContent("Reactor Model", "Defines reactor model for the network library"); m_ReactorMaximumReceivedMessagesLabel = new GUIContent("Reactor Max Recv Messages", "Defines maximum amount of messages in the receive queue"); m_ReactorMaximumSentMessagesLabel = new GUIContent("Reactor Max Sent Messages", "Defines maximum message count in sent queue"); m_UseWebSocketsLabel = new GUIContent("Use WebSockets", "This makes the server listen for connections using WebSockets. This allows WebGL clients to connect to the server."); m_NetworkAddressLabel = new GUIContent("Network Address", "The network address currently in use."); m_NetworkPortLabel = new GUIContent("Network Port", "The network port currently in use."); m_ServerBindToIPLabel = new GUIContent("Server Bind to IP", "Enable to bind the server to a specific IP address."); m_ServerBindAddressLabel = new GUIContent("Server Bind Address Label", "IP to bind the server to, when Server Bind to IP is enabled."); m_PlayerPrefabLabel = new GUIContent("Player Prefab", "The default prefab to be used to create player objects on the server."); m_AutoCreatePlayerLabel = new GUIContent("Auto Create Player", "Enable to automatically create player objects on connect and on Scene change."); m_PlayerSpawnMethodLabel = new GUIContent("Player Spawn Method", "How to determine which NetworkStartPosition to spawn players at, from all NetworkStartPositions in the Scene.\n\nRandom chooses a random NetworkStartPosition.\n\nRound Robin chooses the next NetworkStartPosition on a round-robin basis."); m_AdvancedConfigurationLabel = new GUIContent("Advanced Configuration", "Enable to view and edit advanced settings."); // top-level properties m_DontDestroyOnLoadProperty = serializedObject.FindProperty("m_DontDestroyOnLoad"); m_RunInBackgroundProperty = serializedObject.FindProperty("m_RunInBackground"); m_LogLevelProperty = serializedObject.FindProperty("m_LogLevel"); // network foldout properties m_NetworkAddressProperty = serializedObject.FindProperty("m_NetworkAddress"); m_NetworkPortProperty = serializedObject.FindProperty("m_NetworkPort"); m_ServerBindToIPProperty = serializedObject.FindProperty("m_ServerBindToIP"); m_ServerBindAddressProperty = serializedObject.FindProperty("m_ServerBindAddress"); // spawn foldout properties m_PlayerPrefabProperty = serializedObject.FindProperty("m_PlayerPrefab"); m_AutoCreatePlayerProperty = serializedObject.FindProperty("m_AutoCreatePlayer"); m_PlayerSpawnMethodProperty = serializedObject.FindProperty("m_PlayerSpawnMethod"); m_SpawnListProperty = serializedObject.FindProperty("m_SpawnPrefabs"); m_SpawnList = new ReorderableList(serializedObject, m_SpawnListProperty); m_SpawnList.drawHeaderCallback = DrawHeader; m_SpawnList.drawElementCallback = DrawChild; m_SpawnList.onReorderCallback = Changed; m_SpawnList.onRemoveCallback = RemoveButton; m_SpawnList.onChangedCallback = Changed; m_SpawnList.onReorderCallback = Changed; m_SpawnList.onAddCallback = AddButton; m_SpawnList.elementHeight = 16; // this uses a 16x16 icon. other sizes make it stretch. // network configuration m_CustomConfigProperty = serializedObject.FindProperty("m_CustomConfig"); m_ChannelListProperty = serializedObject.FindProperty("m_Channels"); m_ChannelList = new ReorderableList(serializedObject, m_ChannelListProperty); m_ChannelList.drawHeaderCallback = ChannelDrawHeader; m_ChannelList.drawElementCallback = ChannelDrawChild; m_ChannelList.onReorderCallback = ChannelChanged; m_ChannelList.onAddDropdownCallback = ChannelAddButton; m_ChannelList.onRemoveCallback = ChannelRemoveButton; m_ChannelList.onChangedCallback = ChannelChanged; m_ChannelList.onReorderCallback = ChannelChanged; m_ChannelList.onAddCallback = ChannelChanged; // web sockets m_UseWebSocketsProperty = serializedObject.FindProperty("m_UseWebSockets"); } static void ShowPropertySuffix(GUIContent content, SerializedProperty prop, string suffix) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(prop, content); GUILayout.Label(suffix, EditorStyles.miniLabel, GUILayout.Width(64)); EditorGUILayout.EndHorizontal(); } protected void ShowConfigInfo() { bool oldCustomConfig = m_NetworkManager.customConfig; EditorGUILayout.PropertyField(m_CustomConfigProperty, m_AdvancedConfigurationLabel); // Populate default channels first time a custom config is created. if (m_CustomConfigProperty.boolValue) { if (!oldCustomConfig) { if (m_NetworkManager.channels.Count == 0) { m_NetworkManager.channels.Add(QosType.ReliableSequenced); m_NetworkManager.channels.Add(QosType.Unreliable); m_NetworkManager.customConfig = true; m_CustomConfigProperty.serializedObject.Update(); m_ChannelList.serializedProperty.serializedObject.Update(); } } } if (m_NetworkManager.customConfig) { EditorGUI.indentLevel += 1; var maxConn = serializedObject.FindProperty("m_MaxConnections"); ShowPropertySuffix(m_MaxConnectionsLabel, maxConn, "connections"); m_ChannelList.DoLayoutList(); maxConn.isExpanded = EditorGUILayout.Foldout(maxConn.isExpanded, "Timeouts"); if (maxConn.isExpanded) { EditorGUI.indentLevel += 1; var minUpdateTimeout = serializedObject.FindProperty("m_ConnectionConfig.m_MinUpdateTimeout"); var connectTimeout = serializedObject.FindProperty("m_ConnectionConfig.m_ConnectTimeout"); var disconnectTimeout = serializedObject.FindProperty("m_ConnectionConfig.m_DisconnectTimeout"); var pingTimeout = serializedObject.FindProperty("m_ConnectionConfig.m_PingTimeout"); ShowPropertySuffix(m_MinUpdateTimeoutLabel, minUpdateTimeout, "millisec"); ShowPropertySuffix(m_ConnectTimeoutLabel, connectTimeout, "millisec"); ShowPropertySuffix(m_DisconnectTimeoutLabel, disconnectTimeout, "millisec"); ShowPropertySuffix(m_PingTimeoutLabel, pingTimeout, "millisec"); EditorGUI.indentLevel -= 1; } var threadAwakeTimeout = serializedObject.FindProperty("m_GlobalConfig.m_ThreadAwakeTimeout"); threadAwakeTimeout.isExpanded = EditorGUILayout.Foldout(threadAwakeTimeout.isExpanded, "Global Config"); if (threadAwakeTimeout.isExpanded) { EditorGUI.indentLevel += 1; var reactorModel = serializedObject.FindProperty("m_GlobalConfig.m_ReactorModel"); var reactorMaximumReceivedMessages = serializedObject.FindProperty("m_GlobalConfig.m_ReactorMaximumReceivedMessages"); var reactorMaximumSentMessages = serializedObject.FindProperty("m_GlobalConfig.m_ReactorMaximumSentMessages"); ShowPropertySuffix(m_ThreadAwakeTimeoutLabel, threadAwakeTimeout, "millisec"); EditorGUILayout.PropertyField(reactorModel, m_ReactorModelLabel); ShowPropertySuffix(m_ReactorMaximumReceivedMessagesLabel, reactorMaximumReceivedMessages, "messages"); ShowPropertySuffix(m_ReactorMaximumSentMessagesLabel, reactorMaximumSentMessages, "messages"); EditorGUI.indentLevel -= 1; } EditorGUI.indentLevel -= 1; } } protected void ShowSpawnInfo() { m_PlayerPrefabProperty.isExpanded = EditorGUILayout.Foldout(m_PlayerPrefabProperty.isExpanded, m_ShowSpawnLabel); if (!m_PlayerPrefabProperty.isExpanded) { return; } EditorGUI.indentLevel += 1; EditorGUILayout.PropertyField(m_PlayerPrefabProperty, m_PlayerPrefabLabel); EditorGUILayout.PropertyField(m_AutoCreatePlayerProperty, m_AutoCreatePlayerLabel); EditorGUILayout.PropertyField(m_PlayerSpawnMethodProperty, m_PlayerSpawnMethodLabel); EditorGUI.BeginChangeCheck(); m_SpawnList.DoLayoutList(); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); } EditorGUI.indentLevel -= 1; } protected SceneAsset GetSceneObject(string sceneObjectName) { if (string.IsNullOrEmpty(sceneObjectName)) { return null; } foreach (var editorScene in EditorBuildSettings.scenes) { var sceneNameWithoutExtension = Path.GetFileNameWithoutExtension(editorScene.path); if (sceneNameWithoutExtension == sceneObjectName) { return AssetDatabase.LoadAssetAtPath(editorScene.path, typeof(SceneAsset)) as SceneAsset; } } if (LogFilter.logWarn) { Debug.LogWarning("Scene [" + sceneObjectName + "] cannot be used with networking. Add this scene to the 'Scenes in the Build' in build settings."); } return null; } protected void ShowNetworkInfo() { m_NetworkAddressProperty.isExpanded = EditorGUILayout.Foldout(m_NetworkAddressProperty.isExpanded, m_ShowNetworkLabel); if (!m_NetworkAddressProperty.isExpanded) { return; } EditorGUI.indentLevel += 1; if (EditorGUILayout.PropertyField(m_UseWebSocketsProperty, m_UseWebSocketsLabel)) { NetworkServer.useWebSockets = m_NetworkManager.useWebSockets; } EditorGUILayout.PropertyField(m_NetworkAddressProperty, m_NetworkAddressLabel); EditorGUILayout.PropertyField(m_NetworkPortProperty, m_NetworkPortLabel); EditorGUILayout.PropertyField(m_ServerBindToIPProperty, m_ServerBindToIPLabel); if (m_NetworkManager.serverBindToIP) { EditorGUI.indentLevel += 1; EditorGUILayout.PropertyField(m_ServerBindAddressProperty, m_ServerBindAddressLabel); EditorGUI.indentLevel -= 1; } EditorGUI.indentLevel -= 1; } protected void ShowScenes() { var offlineObj = GetSceneObject(m_NetworkManager.offlineScene); var newOfflineScene = EditorGUILayout.ObjectField(m_OfflineSceneLabel, offlineObj, typeof(SceneAsset), false); if (newOfflineScene == null) { var prop = serializedObject.FindProperty("m_OfflineScene"); prop.stringValue = ""; EditorUtility.SetDirty(target); } else { if (newOfflineScene.name != m_NetworkManager.offlineScene) { var sceneObj = GetSceneObject(newOfflineScene.name); if (sceneObj == null) { Debug.LogWarning("The scene " + newOfflineScene.name + " cannot be used. To use this scene add it to the build settings for the project"); } else { var prop = serializedObject.FindProperty("m_OfflineScene"); prop.stringValue = newOfflineScene.name; EditorUtility.SetDirty(target); } } } var onlineObj = GetSceneObject(m_NetworkManager.onlineScene); var newOnlineScene = EditorGUILayout.ObjectField(m_OnlineSceneLabel, onlineObj, typeof(SceneAsset), false); if (newOnlineScene == null) { var prop = serializedObject.FindProperty("m_OnlineScene"); prop.stringValue = ""; EditorUtility.SetDirty(target); } else { if (newOnlineScene.name != m_NetworkManager.onlineScene) { var sceneObj = GetSceneObject(newOnlineScene.name); if (sceneObj == null) { Debug.LogWarning("The scene " + newOnlineScene.name + " cannot be used. To use this scene add it to the build settings for the project"); } else { var prop = serializedObject.FindProperty("m_OnlineScene"); prop.stringValue = newOnlineScene.name; EditorUtility.SetDirty(target); } } } } protected void ShowDerivedProperties(Type baseType, Type superType) { bool first = true; SerializedProperty property = serializedObject.GetIterator(); bool expanded = true; while (property.NextVisible(expanded)) { // ignore properties from base class. var f = baseType.GetField(property.name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var p = baseType.GetProperty(property.name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (f == null && superType != null) { f = superType.GetField(property.name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (p == null && superType != null) { p = superType.GetProperty(property.name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (f == null && p == null) { if (first) { first = false; EditorGUI.BeginChangeCheck(); serializedObject.Update(); EditorGUILayout.Separator(); } EditorGUILayout.PropertyField(property, true); expanded = false; } } if (!first) { serializedObject.ApplyModifiedProperties(); EditorGUI.EndChangeCheck(); } } public override void OnInspectorGUI() { if (m_DontDestroyOnLoadProperty == null || m_DontDestroyOnLoadLabel == null) m_Initialized = false; Init(); serializedObject.Update(); EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty, m_DontDestroyOnLoadLabel); EditorGUILayout.PropertyField(m_RunInBackgroundProperty , m_RunInBackgroundLabel); if (EditorGUILayout.PropertyField(m_LogLevelProperty)) { LogFilter.currentLogLevel = m_NetworkManager.logLevel; } ShowScenes(); ShowNetworkInfo(); ShowSpawnInfo(); ShowConfigInfo(); serializedObject.ApplyModifiedProperties(); ShowDerivedProperties(typeof(NetworkManager), null); } static void DrawHeader(Rect headerRect) { GUI.Label(headerRect, "Registered Spawnable Prefabs:"); } internal void DrawChild(Rect r, int index, bool isActive, bool isFocused) { SerializedProperty prefab = m_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 { var uv = go.GetComponent(); if (uv != null) { label = new GUIContent(go.name, "AssetId: [" + uv.assetId + "]"); } else { label = new GUIContent(go.name, "No Network Identity"); } } var newGameObject = (GameObject)EditorGUI.ObjectField(r, label, go, typeof(GameObject), false); if (newGameObject != go) { if (newGameObject != null && !newGameObject.GetComponent()) { if (LogFilter.logError) { 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) { m_SpawnListProperty.arraySize += 1; list.index = m_SpawnListProperty.arraySize - 1; var obj = m_SpawnListProperty.GetArrayElementAtIndex(m_SpawnListProperty.arraySize - 1); if (obj.objectReferenceValue != null) obj.objectReferenceValue = null; m_SpawnList.index = m_SpawnList.count - 1; Changed(list); } internal void RemoveButton(ReorderableList list) { m_SpawnListProperty.DeleteArrayElementAtIndex(m_SpawnList.index); if (list.index >= m_SpawnListProperty.arraySize) { list.index = m_SpawnListProperty.arraySize - 1; } } // List widget functions static void ChannelDrawHeader(Rect headerRect) { GUI.Label(headerRect, "Qos Channels:"); } internal void ChannelDrawChild(Rect r, int index, bool isActive, bool isFocused) { QosType qos = (QosType)m_ChannelListProperty.GetArrayElementAtIndex(index).enumValueIndex; QosType newValue = (QosType)EditorGUI.EnumPopup(r, "Channel #" + index, qos); if (newValue != qos) { var obj = m_ChannelListProperty.GetArrayElementAtIndex(index); obj.enumValueIndex = (int)newValue; } } internal void ChannelChanged(ReorderableList list) { EditorUtility.SetDirty(target); } internal void ChannelAddButton(Rect rect, ReorderableList list) { m_ChannelListProperty.arraySize += 1; var obj = m_ChannelListProperty.GetArrayElementAtIndex(m_ChannelListProperty.arraySize - 1); obj.enumValueIndex = (int)QosType.ReliableSequenced; list.index = m_ChannelListProperty.arraySize - 1; } internal void ChannelRemoveButton(ReorderableList list) { if (m_NetworkManager.channels.Count == 1) { if (LogFilter.logError) { Debug.LogError("Cannot remove channel. There must be at least one QoS channel."); } return; } m_ChannelListProperty.DeleteArrayElementAtIndex(m_ChannelList.index); if (list.index >= m_ChannelListProperty.arraySize - 1) { list.index = m_ChannelListProperty.arraySize - 1; } } } } #endif //ENABLE_UNET