/*
* 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