using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.IO; using Process = System.Diagnostics.Process; using UnityEditor; using UnityEngine; namespace Colosseum.Editor { /// /// Multiplayer Play Mode 관련 상태와 리플렉션 정보를 점검하는 디버그 메뉴입니다. /// public static class MultiplayerPlayModeDebugMenu { private const string MultiplayerManagerAssetPath = "ProjectSettings/MultiplayerManager.asset"; private const string DiagnosticsDirectory = "Temp/MPP"; private const string VirtualProjectsRoot = "Library/VP"; [MenuItem("Tools/Colosseum/Multiplayer/Log Play Mode Module Types")] private static void LogPlayModeModuleTypes() { Assembly playModeAssembly = typeof(UnityEditor.PlayModeStateChange).Assembly; Type[] types = playModeAssembly .GetTypes() .Where(type => type.FullName != null && (type.FullName.Contains("PlayMode", StringComparison.OrdinalIgnoreCase) || type.FullName.Contains("Scenario", StringComparison.OrdinalIgnoreCase) || type.FullName.Contains("Multiplayer", StringComparison.OrdinalIgnoreCase))) .OrderBy(type => type.FullName) .ToArray(); StringBuilder builder = new StringBuilder(); builder.AppendLine("[MPP] PlayModeModule 타입 목록"); for (int i = 0; i < types.Length; i++) { builder.Append("- "); builder.AppendLine(types[i].FullName); } string diagnosticsPath = EnsureDiagnosticsFilePath("PlayModeModuleTypes.txt"); File.WriteAllText(diagnosticsPath, builder.ToString(), Encoding.UTF8); Debug.Log($"[MPP] PlayModeModule 타입 목록을 저장했습니다. {diagnosticsPath}"); } [MenuItem("Tools/Colosseum/Multiplayer/Log Play Mode User Settings")] private static void LogPlayModeUserSettings() { Type settingsType = Type.GetType("Unity.PlayMode.Editor.PlayModeUserSettings, UnityEditor.PlayModeModule"); if (settingsType == null) { Debug.LogWarning("[MPP] Unity.PlayMode.Editor.PlayModeUserSettings 타입을 찾지 못했습니다."); return; } string diagnosticsPath = EnsureDiagnosticsFilePath("PlayModeUserSettings.txt"); MethodInfo getOrCreateMethod = settingsType.GetMethod( "GetOrCreateSettings", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object settings = getOrCreateMethod?.Invoke(null, null); if (settings == null) { StringBuilder nullBuilder = new StringBuilder(); nullBuilder.AppendLine("[MPP] PlayModeUserSettings 인스턴스를 가져오지 못했습니다."); nullBuilder.AppendLine($"Type: {settingsType.FullName}"); nullBuilder.AppendLine("Static Members:"); AppendStaticMembers(nullBuilder, settingsType); File.WriteAllText(diagnosticsPath, nullBuilder.ToString(), Encoding.UTF8); Debug.LogWarning($"[MPP] PlayModeUserSettings 인스턴스를 가져오지 못했습니다. 진단 파일: {diagnosticsPath}"); return; } StringBuilder builder = new StringBuilder(); builder.AppendLine("[MPP] PlayModeUserSettings"); AppendMembers(builder, settingsType, settings); AppendStaticMembers(builder, settingsType); File.WriteAllText(diagnosticsPath, builder.ToString(), Encoding.UTF8); Debug.Log($"[MPP] PlayModeUserSettings 정보를 저장했습니다. {diagnosticsPath}"); } [MenuItem("Tools/Colosseum/Multiplayer/Enable Local Deployment")] private static void EnableLocalDeployment() { SerializedObject multiplayerManager = GetMultiplayerManagerSerializedObject(); if (multiplayerManager == null) { return; } SerializedProperty localDeployment = multiplayerManager.FindProperty("m_EnablePlayModeLocalDeployment"); if (localDeployment == null) { Debug.LogWarning("[MPP] m_EnablePlayModeLocalDeployment 속성을 찾지 못했습니다."); return; } localDeployment.intValue = 1; multiplayerManager.ApplyModifiedPropertiesWithoutUndo(); AssetDatabase.SaveAssets(); Debug.Log("[MPP] 로컬 Play Mode 배포를 활성화했습니다."); } [MenuItem("Tools/Colosseum/Multiplayer/Disable Local Deployment")] private static void DisableLocalDeployment() { SerializedObject multiplayerManager = GetMultiplayerManagerSerializedObject(); if (multiplayerManager == null) { return; } SerializedProperty localDeployment = multiplayerManager.FindProperty("m_EnablePlayModeLocalDeployment"); if (localDeployment == null) { Debug.LogWarning("[MPP] m_EnablePlayModeLocalDeployment 속성을 찾지 못했습니다."); return; } localDeployment.intValue = 0; multiplayerManager.ApplyModifiedPropertiesWithoutUndo(); AssetDatabase.SaveAssets(); Debug.Log("[MPP] 로컬 Play Mode 배포를 비활성화했습니다."); } [MenuItem("Tools/Colosseum/Multiplayer/Log Multiplayer Manager Settings")] private static void LogMultiplayerManagerSettings() { SerializedObject multiplayerManager = GetMultiplayerManagerSerializedObject(); if (multiplayerManager == null) { return; } SerializedProperty roles = multiplayerManager.FindProperty("m_EnableMultiplayerRoles"); SerializedProperty localDeployment = multiplayerManager.FindProperty("m_EnablePlayModeLocalDeployment"); SerializedProperty remoteDeployment = multiplayerManager.FindProperty("m_EnablePlayModeRemoteDeployment"); Debug.Log( $"[MPP] MultiplayerManager | Roles={roles?.intValue ?? -1} | " + $"LocalDeployment={localDeployment?.intValue ?? -1} | " + $"RemoteDeployment={remoteDeployment?.intValue ?? -1}"); } [MenuItem("Tools/Colosseum/Multiplayer/Log Virtual Player Clones")] private static void LogVirtualPlayerClones() { string[] cloneDirectories = GetVirtualPlayerCloneDirectories(); if (cloneDirectories.Length == 0) { Debug.LogWarning("[MPP] Library/VP 아래에 가상 플레이어 복제본을 찾지 못했습니다."); return; } StringBuilder builder = new StringBuilder(); builder.AppendLine("[MPP] 가상 플레이어 복제본 목록"); for (int i = 0; i < cloneDirectories.Length; i++) { builder.Append("- "); builder.AppendLine(Path.GetFullPath(cloneDirectories[i])); } Debug.Log(builder.ToString()); } [MenuItem("Tools/Colosseum/Multiplayer/Launch First Virtual Player Clone")] private static void LaunchFirstVirtualPlayerClone() { string[] cloneDirectories = GetVirtualPlayerCloneDirectories(); if (cloneDirectories.Length == 0) { Debug.LogWarning("[MPP] 실행할 가상 플레이어 복제본이 없습니다."); return; } string cloneProjectPath = Path.GetFullPath(cloneDirectories[0]); Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = EditorApplication.applicationPath, Arguments = $"-projectPath \"{cloneProjectPath}\"", UseShellExecute = true, }); Debug.Log($"[MPP] 가상 플레이어 복제본을 실행했습니다. {cloneProjectPath}"); } private static SerializedObject GetMultiplayerManagerSerializedObject() { UnityEngine.Object[] assets = AssetDatabase.LoadAllAssetsAtPath(MultiplayerManagerAssetPath); if (assets == null || assets.Length == 0 || assets[0] == null) { Debug.LogWarning("[MPP] MultiplayerManager.asset를 찾지 못했습니다."); return null; } return new SerializedObject(assets[0]); } private static void AppendMembers(StringBuilder builder, Type settingsType, object settings) { const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; List properties = settingsType.GetProperties(flags) .Where(property => property.GetIndexParameters().Length == 0) .OrderBy(property => property.Name) .ToList(); for (int i = 0; i < properties.Count; i++) { PropertyInfo property = properties[i]; object value = null; bool success = true; try { value = property.GetValue(settings); } catch (Exception exception) { success = false; value = exception.GetType().Name; } builder.Append("- Property "); builder.Append(property.Name); builder.Append(" = "); builder.AppendLine(success ? FormatValue(value) : $""); } List fields = settingsType.GetFields(flags) .OrderBy(field => field.Name) .ToList(); for (int i = 0; i < fields.Count; i++) { FieldInfo field = fields[i]; object value = field.GetValue(settings); builder.Append("- Field "); builder.Append(field.Name); builder.Append(" = "); builder.AppendLine(FormatValue(value)); } } private static void AppendStaticMembers(StringBuilder builder, Type settingsType) { const BindingFlags flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; List properties = settingsType.GetProperties(flags) .Where(property => property.GetIndexParameters().Length == 0) .OrderBy(property => property.Name) .ToList(); for (int i = 0; i < properties.Count; i++) { PropertyInfo property = properties[i]; object value = null; bool success = true; try { value = property.GetValue(null); } catch (Exception exception) { success = false; value = exception.GetType().Name; } builder.Append("- Static Property "); builder.Append(property.Name); builder.Append(" = "); builder.AppendLine(success ? FormatValue(value) : $""); } List fields = settingsType.GetFields(flags) .OrderBy(field => field.Name) .ToList(); for (int i = 0; i < fields.Count; i++) { FieldInfo field = fields[i]; object value = field.GetValue(null); builder.Append("- Static Field "); builder.Append(field.Name); builder.Append(" = "); builder.AppendLine(FormatValue(value)); } List methods = settingsType.GetMethods(flags) .Where(method => !method.IsSpecialName) .OrderBy(method => method.Name) .ToList(); for (int i = 0; i < methods.Count; i++) { MethodInfo method = methods[i]; string parameterSummary = string.Join( ", ", method.GetParameters().Select(parameter => $"{parameter.ParameterType.Name} {parameter.Name}")); builder.Append("- Static Method "); builder.Append(method.ReturnType.Name); builder.Append(' '); builder.Append(method.Name); builder.Append('('); builder.Append(parameterSummary); builder.AppendLine(")"); } } private static string EnsureDiagnosticsFilePath(string fileName) { Directory.CreateDirectory(DiagnosticsDirectory); return Path.Combine(DiagnosticsDirectory, fileName); } private static string[] GetVirtualPlayerCloneDirectories() { if (!Directory.Exists(VirtualProjectsRoot)) { return Array.Empty(); } return Directory .GetDirectories(VirtualProjectsRoot, "mppm*") .OrderBy(path => path) .ToArray(); } private static string FormatValue(object value) { if (value == null) { return "null"; } if (value is string stringValue) { return stringValue; } if (value is IEnumerable enumerable) { return "[" + string.Join(", ", enumerable.Select(FormatValue)) + "]"; } return value.ToString(); } } }