/* * UniWindowControllerEditor.cs * * Author: Kirurobo http://x.com/kirurobo * License: MIT */ // Assembry Definition を有効にしてから、ビルド時に 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 (!isWarningDismissed) 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)) { // 自動の場合も警告を出す ShowInfo( "Direct3D12 不支持透明窗口。" + "请考虑在播放器设置中使用 Direct3D11 替代'自动图形 API for Windows'设置。", null ); } else if (PlayerSettings.GetGraphicsAPIs(BuildTarget.StandaloneWindows).Contains(GraphicsDeviceType.Direct3D12)) { // Graphhics 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 以外は今のところ非対応で Readonly とする 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