/* * UniWindowControllerEditor.cs * * Author: Kirurobo http://x.com/kirurobo * License: MIT */ // 启用了程序集定义后,如果构建时没有 Editor 类会报错。 // 因此整体用 UNITY_EDITOR 包裹,没有时直接忽略 #if UNITY_EDITOR using System.Linq; using UnityEngine; using UnityEditor; using System.Reflection; using UnityEngine.Rendering; namespace Kirurobo { /// /// UniWindowController 的编辑器自定义部分 /// [CustomEditor(typeof(UniWindowController))] public class UniWindowControllerEditor : Editor { /// /// 用于显示光标下方颜色的属性 /// SerializedProperty pickedColor; /// /// 游戏视图窗口 /// private EditorWindow gameViewWindow; /// /// 是否已关闭项目设置相关的警告 private bool isWarningDismissed = false; /// /// 是否已关闭 URP 相关警告 /// private bool isUrpWarningDismissed = true; /// /// URP 是否有效 /// private bool hasUrp = false; void OnEnable() { LoadSettings(); pickedColor = serializedObject.FindProperty("pickedColor"); // 判断 URP 是否有效 hasUrp = GetUrpSettings(); } void OnDisable() { SaveSettings(); } /// /// 检测 URP 是否有效 /// /// private bool GetUrpSettings() { var renderPipelineAsset = GraphicsSettings.defaultRenderPipeline; if (renderPipelineAsset == null || renderPipelineAsset.GetType().Name != "UniversalRenderPipelineAsset") { // URP 未设置 return false; } return true; } private void LoadSettings() { isWarningDismissed = EditorUserSettings.GetConfigValue("WindowController_IS_WARNING DISMISSED") == "1"; } private void SaveSettings() { EditorUserSettings.SetConfigValue("WindowController_IS_WARNING DISMISSED", isWarningDismissed ? "1" : "0"); } /// /// 自定义检视面板的显示 /// /// /// 显示参考信息以及推荐设置的更改栏。 /// public override void OnInspectorGUI() { base.OnInspectorGUI(); // 如果获取到了光标下的颜色,则显示其透明度作为参考 if (pickedColor != null) { EditorGUI.BeginDisabledGroup(true); EditorGUILayout.LabelField("选取的Alpha值", pickedColor.colorValue.a.ToString("P0")); EditorGUI.EndDisabledGroup(); } // 显示 Project Settings 的推荐设置 isWarningDismissed = ShowPlayerSettingsValidation(isWarningDismissed); // 显示 URP 相关推荐设置 isUrpWarningDismissed = ShowUrpSettingsValidation(isUrpWarningDismissed); } /// /// 显示 Project Settings 相关推荐设置的自动配置栏 /// private bool ShowPlayerSettingsValidation(bool dismissed) { // 以下是 Project Settings 相关 EditorGUILayout.Space(); bool enableValidation = EditorGUILayout.Foldout(!dismissed, "播放器设置验证"); // 记录是否启用检查 if (enableValidation == dismissed) { dismissed = !enableValidation; } // 检查推荐设置 //if (!isWarningDismissed) if (enableValidation) { if (ValidateSettings(false)) { // 应用所有推荐设置 GUI.backgroundColor = Color.green; if (GUILayout.Button( "✔ 将所有设置修复为推荐值", GUILayout.MinHeight(25f) )) { ValidateSettings(true); } // 关闭验证 GUI.backgroundColor = Color.yellow; if (GUILayout.Button( "✘ 关闭此验证", GUILayout.MinHeight(25f) )) { dismissed = true; //SaveSettings(); // 如果需要立即保存,取消注释 } EditorGUILayout.Space(); } else { GUI.color = Color.green; GUILayout.Label("OK!"); } // 打开播放器设置页面 EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUI.color = Color.white; GUI.backgroundColor = Color.white; if (GUILayout.Button( "打开播放器设置", GUILayout.MinHeight(25f), GUILayout.Width(200f) )) { SettingsService.OpenProjectSettings("Project/Player"); } GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); } return dismissed; } /// /// 显示 URP 相关推荐设置的自动配置栏 /// private bool ShowUrpSettingsValidation(bool dismissed) { // 如果 URP 未启用则不执行任何操作 if (!hasUrp) return dismissed; // 以下是 URP 相关自动设置 EditorGUILayout.Space(); bool enableValidation = EditorGUILayout.Foldout(!dismissed, "URP 设置验证"); // 记录是否启用检查 if (enableValidation == dismissed) { dismissed = !enableValidation; } // 检查推荐设置 if (enableValidation) { if (ValidateUrpSettings(false)) { // 应用所有推荐设置 GUI.backgroundColor = Color.green; if (GUILayout.Button( "✔ 将所有设置修复为推荐值", GUILayout.MinHeight(25f) )) { ValidateUrpSettings(true); } // 关闭验证 GUI.backgroundColor = Color.yellow; if (GUILayout.Button( "✘ 关闭此验证", GUILayout.MinHeight(25f) )) { dismissed = true; } EditorGUILayout.Space(); } else { GUI.color = Color.green; GUILayout.Label("OK!"); } EditorGUILayout.Space(); } return dismissed; } private delegate void FixMethod(); /// /// 显示或修复设置 /// /// 警告消息 /// 修复操作 /// false: 显示警告和修复按钮, true: 静默修复 private void FixSetting(string message, FixMethod fixAction, bool silentFix = false) { if (silentFix) { // 修复 fixAction.Invoke(); } else { // 显示消息和修复按钮 EditorGUILayout.BeginHorizontal(); EditorGUILayout.HelpBox(message, MessageType.Warning, true); GUILayout.FlexibleSpace(); EditorGUILayout.BeginVertical(); EditorGUILayout.Space(); if (GUILayout.Button("修复", GUILayout.Width(60f))) { fixAction.Invoke(); } //GUILayout.FlexibleSpace(); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); } } /// /// 仅显示建议 /// /// 警告消息 private void ShowInfo(string message, Object target = null) { // 显示消息和定位按钮 EditorGUILayout.BeginHorizontal(); EditorGUILayout.HelpBox(message, MessageType.Info, true); GUILayout.FlexibleSpace(); // 无法自动设置的对象,仅在项目窗口中标识 if (target != null) { EditorGUILayout.BeginVertical(); EditorGUILayout.Space(); if (GUILayout.Button("定位", GUILayout.Width(60f))) { EditorGUIUtility.PingObject(target); } //GUILayout.FlexibleSpace(); EditorGUILayout.EndVertical(); } EditorGUILayout.EndHorizontal(); } /// /// 验证播放器设置 /// /// false: 显示警告和修复按钮, true: 静默修复 /// 如果有无效项则返回 true private bool ValidateSettings(bool silentFix = false) { bool invalid = false; // 在后台运行。点击穿透切换等功能需要此设置 if (!PlayerSettings.runInBackground) { invalid = true; FixSetting( "强烈建议启用'后台运行'。", () => PlayerSettings.runInBackground = true, silentFix ); } // 设为可调整大小的窗口。非必需,但当窗口边框无效时大小会变化,因此可调整更自然 if (!PlayerSettings.resizableWindow) { invalid = true; FixSetting( "建议启用'可调整窗口大小'。", () => PlayerSettings.resizableWindow = true, silentFix ); } // 设为窗口模式而非全屏 #if UNITY_2018_1_OR_NEWER // Unity 2018 起全屏指定的行为发生了变化 if (PlayerSettings.fullScreenMode != FullScreenMode.Windowed) { invalid = true; FixSetting( "在'全屏模式'中选择'窗口化'。", () => PlayerSettings.fullScreenMode = FullScreenMode.Windowed, silentFix ); } #else if (PlayerSettings.defaultIsFullScreen) { invalid = true; FixSetting( "不建议启用'默认全屏'。", () => PlayerSettings.defaultIsFullScreen = false, silentFix ); } #endif // 禁止全屏与窗口模式的切换 if (PlayerSettings.allowFullscreenSwitch) { invalid = true; FixSetting( "禁止全屏切换。", () => PlayerSettings.allowFullscreenSwitch = false, silentFix ); } // Windows 下不禁用 Use DXGI Flip Mode Swapchain 则无法透明 // ↓Unity 2019.1.6 以下应该没有 useFlipModelSwapchain // 因此为了排除这些版本而逐一列出,但可能不需要支持到这么老的版本 #if UNITY_2019_1_6 #elif UNITY_2019_1_5 #elif UNITY_2019_1_4 #elif UNITY_2019_1_3 #elif UNITY_2019_1_2 #elif UNITY_2019_1_1 #elif UNITY_2019_1_0 #elif UNITY_2019_1_OR_NEWER // Unity 2019.1.7 以上推荐在 Player 设置中禁用 Use DXGI Flip... if (PlayerSettings.useFlipModelSwapchain) { invalid = true; FixSetting( "禁用'使用 DXGI 翻转模式交换链'以使窗口透明。", () => PlayerSettings.useFlipModelSwapchain = false, silentFix ); } // Direct3D12 不支持透明窗口,建议从 Graphics APIs for Windows 中移除 if (PlayerSettings.GetUseDefaultGraphicsAPIs(BuildTarget.StandaloneWindows)) { // 自动选择图形 API 时也发出警告 ShowInfo( "Direct3D12 不支持透明窗口。" + "请考虑在播放器设置中使用 Direct3D11 替代'自动图形 API for Windows'设置。", null ); } else if (PlayerSettings.GetGraphicsAPIs(BuildTarget.StandaloneWindows).Contains(GraphicsDeviceType.Direct3D12)) { // Graphics APIs for Windows 中包含 Direct3D12 时发出警告 ShowInfo( "Direct3D12 不支持透明窗口。" + "请从播放器设置的'图形 API for Windows'中移除 Direct3D12。", null ); } #endif return invalid; } /// /// 验证播放器设置 /// /// false: 显示警告和修复按钮, true: 静默修复 /// 如果有无效项则返回 true private bool ValidateUrpSettings(bool silentFix = false) { bool invalid = false; // 如果启用了 Universal Render Pipeline,建议禁用 HDR foreach (var cam in Camera.allCameras) { if (cam.allowHDR) { string name = cam.name; invalid = true; FixSetting( $"{name}:禁用摄像机中的'HDR'以使窗口透明。", () => cam.allowHDR = false, silentFix ); } if (cam.allowMSAA) { string name = cam.name; invalid = true; FixSetting( $"{name}:禁用摄像机中的'MSAA'以使窗口透明。", () => cam.allowMSAA = false, silentFix ); } } var urpAsset = GraphicsSettings.defaultRenderPipeline; if (hasUrp && urpAsset != null) { // hasUrp == true 时 urpAsset 应该是 UniversalRenderPipelineAsset,因此应该存在 allowPostProcessAlphaOutput var alphaProcessingProperty = urpAsset.GetType().GetProperty("allowPostProcessAlphaOutput", BindingFlags.Public | BindingFlags.Instance); if (alphaProcessingProperty != null) { var alphaProcessing = alphaProcessingProperty.GetValue(urpAsset); if (!(bool)alphaProcessing) { invalid = true; ShowInfo( "在 URP 资源中启用'Alpha 处理'", urpAsset ); } } } return invalid; } } [CustomPropertyDrawer(typeof(ReadOnlyAttribute))] public class UniWindowControllerReadOnlyDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { GUI.enabled = false; EditorGUI.PropertyField(position, property, label, true); GUI.enabled = true; } } /// /// 设置布尔属性为可编辑 /// 参考: http://ponkotsu-hiyorin.hateblo.jp/entry/2015/10/20/003042 /// 参考: https://forum.unity.com/threads/c-class-property-with-reflection-in-propertydrawer-not-saving-to-prefab.473942/ /// [CustomPropertyDrawer(typeof(EditablePropertyAttribute))] public class UniWindowControllerDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { //base.OnGUI(position, property, label); Object obj = property.serializedObject.targetObject; // 如果设置了 Range(min, max) 则获取 FieldInfo fieldInfo = obj.GetType().GetField( property.name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static ); var rangeAttrs = fieldInfo?.GetCustomAttributes(typeof(RangeAttribute), true) as RangeAttribute[]; RangeAttribute range = (rangeAttrs?.Length > 0 ? rangeAttrs.First() : null); if (EditorApplication.isPlayingOrWillChangePlaymode) { // 以 '_' 开头作为操作条件的依据 if (property.name[0] == '_') { string propertyName = property.name.Substring(1); // 获取去掉 '_' 后的属性名 PropertyInfo info = obj.GetType().GetProperty(propertyName); MethodInfo getMethod = default(MethodInfo); MethodInfo setMethod = default(MethodInfo); if (info.CanRead) { getMethod = info.GetGetMethod(); } if (info.CanWrite) { setMethod = info.GetSetMethod(); } if (property.type == "bool") { var oldValue = property.boolValue; if (getMethod != null) { oldValue = (bool)getMethod.Invoke(obj, null); } GUI.enabled = (setMethod != null); EditorGUI.PropertyField(position, property, label, true); GUI.enabled = true; var newValue = property.boolValue; if ((setMethod != null) && (oldValue != newValue)) { setMethod.Invoke(obj, new[] { (object)newValue }); } } else if (property.type == "float") { var oldValue = property.floatValue; if (getMethod != null) { oldValue = (float) getMethod.Invoke(obj, null); } GUI.enabled = (setMethod != null); if (range != null) { EditorGUI.Slider(position, property, range.min, range.max, label); } else { EditorGUI.PropertyField(position, property, label, true); } GUI.enabled = true; var newValue = property.floatValue; if ((setMethod != null) && (oldValue != newValue)) { setMethod.Invoke(obj, new[] {(object) newValue}); } } else { // bool、float 以外的类型暂不支持,设为只读 GUI.enabled = false; EditorGUI.PropertyField(position, property, label, true); GUI.enabled = true; } } else { // 只读 GUI.enabled = false; EditorGUI.PropertyField(position, property, label, true); GUI.enabled = true; } } else { // 有 Range 指定则显示为滑块 if (range != null) { EditorGUI.Slider(position, property, range.min, range.max, label); } else { EditorGUI.PropertyField(position, property, label, true); } } } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return EditorGUI.GetPropertyHeight(property, label, true); } } } #endif