591 lines
21 KiB
C#
591 lines
21 KiB
C#
/*
|
||
* 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
|
||
{
|
||
/// <summary>
|
||
/// UniWindowController 的编辑器自定义部分
|
||
/// </summary>
|
||
[CustomEditor(typeof(UniWindowController))]
|
||
public class UniWindowControllerEditor : Editor
|
||
{
|
||
/// <summary>
|
||
/// 用于显示光标下方颜色的属性
|
||
/// </summary>
|
||
SerializedProperty pickedColor;
|
||
|
||
/// <summary>
|
||
/// 游戏视图窗口
|
||
/// </summary>
|
||
private EditorWindow gameViewWindow;
|
||
|
||
/// <summary>
|
||
/// 是否已关闭项目设置相关的警告
|
||
private bool isWarningDismissed = false;
|
||
|
||
/// <summary>
|
||
/// 是否已关闭 URP 相关警告
|
||
/// </summary>
|
||
private bool isUrpWarningDismissed = true;
|
||
|
||
/// <summary>
|
||
/// URP 是否有效
|
||
/// </summary>
|
||
private bool hasUrp = false;
|
||
|
||
void OnEnable()
|
||
{
|
||
LoadSettings();
|
||
|
||
pickedColor = serializedObject.FindProperty("pickedColor");
|
||
|
||
// 判断 URP 是否有效
|
||
hasUrp = GetUrpSettings();
|
||
}
|
||
|
||
void OnDisable()
|
||
{
|
||
SaveSettings();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检测 URP 是否有效
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
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");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自定义检视面板的显示
|
||
/// </summary>
|
||
/// <description>
|
||
/// 显示参考信息以及推荐设置的更改栏。
|
||
/// </description>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示 Project Settings 相关推荐设置的自动配置栏
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示 URP 相关推荐设置的自动配置栏
|
||
/// </summary>
|
||
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();
|
||
|
||
/// <summary>
|
||
/// 显示或修复设置
|
||
/// </summary>
|
||
/// <param name="message">警告消息</param>
|
||
/// <param name="fixAction">修复操作</param>
|
||
/// <param name="silentFix">false: 显示警告和修复按钮, true: 静默修复</param>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 仅显示建议
|
||
/// </summary>
|
||
/// <param name="message">警告消息</param>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证播放器设置
|
||
/// </summary>
|
||
/// <param name="silentFix">false: 显示警告和修复按钮, true: 静默修复</param>
|
||
/// <returns>如果有无效项则返回 true</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证播放器设置
|
||
/// </summary>
|
||
/// <param name="silentFix">false: 显示警告和修复按钮, true: 静默修复</param>
|
||
/// <returns>如果有无效项则返回 true</returns>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置布尔属性为可编辑
|
||
/// 参考: 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/
|
||
/// </summary>
|
||
[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 |