Unity 项目中查找仅具有单一颜色的纹理

如何使用

只需在"项目"窗口中创建一个名为**"编辑器"** 的文件夹,然后在其中添加此脚本即可。然后,打开**"窗口-单色纹理检测器"** 并点击"刷新"。

你可能会问,为什么我需要这个?某些纹理可以是 1024x1024 或更大,并且仅包含单一颜色(Asset Store 上的许多艺术资源将此类纹理用于其材质的金属/发射/镜面/等通道)。为什么你想要一个 1k 纹理?您可以简单地将其最大尺寸设置为 32,在显着优化纹理尺寸的同时,它也会产生相同的效果。该插件可用于快速找到此类纹理。

cs 复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

public class SingleColorTextureDetector : EditorWindow, IHasCustomMenu
{
	[Serializable]
	private class SaveData
	{
		public List<string> paths = new List<string>();
	}

	private const string LOW_RES_DUMMY_TEXTURE_PATH = "Assets/low_dummyy_texturee.png";
	private const string HIGH_RES_DUMMY_TEXTURE_PATH = "Assets/high_dummyy_texturee.png";
	private const string SAVE_FILE_PATH = "Library/SingleColorAssetDetector.json";
	private const float BUTTON_DRAG_THRESHOLD_SQR = 600f;

	private readonly MethodInfo instanceIDFromGUID = typeof( AssetDatabase ).GetMethod( "GetInstanceIDFromGUID", BindingFlags.NonPublic | BindingFlags.Static );

	private List<string> results = new List<string>(); // This is not readonly so that it can be serialized

	private double lastClickTime;
	private string lastClickedPath;

	private readonly GUIContent buttonGUIContent = new GUIContent();
	private Vector2 buttonPressPosition;
	private Vector2 scrollPos;

	[MenuItem( "Window/Single Color Texture Detector" )]
	private static void Init()
	{
		SingleColorTextureDetector window = GetWindow<SingleColorTextureDetector>();
		window.titleContent = new GUIContent( "Solid Textures" );
		window.minSize = new Vector2( 200f, 150f );
		window.Show();
	}

	private void Awake()
	{
		LoadSession( null );
	}

	// Show additional options in the window's context menu
	public void AddItemsToMenu( GenericMenu menu )
	{
		if( results.Count > 0 )
			menu.AddItem( new GUIContent( "Save To Clipboard" ), false, () => GUIUtility.systemCopyBuffer = JsonUtility.ToJson( new SaveData() { paths = results }, true ) );
		else
			menu.AddDisabledItem( new GUIContent( "Save To Clipboard" ) );

		if( string.IsNullOrEmpty( GUIUtility.systemCopyBuffer ) )
			menu.AddDisabledItem( new GUIContent( "Load From Clipboard" ) );
		else
		{
			menu.AddItem( new GUIContent( "Load From Clipboard" ), false, () =>
			{
				string json = GUIUtility.systemCopyBuffer;
				LoadSession( json );
				SaveSession( json ); // If load succeeds, overwrite the saved session
			} );
		}
	}

	private void OnGUI()
	{
		Event ev = Event.current;
		scrollPos = GUILayout.BeginScrollView( scrollPos );

		// Calculate single color Textures
		if( GUILayout.Button( "Refresh" ) )
		{
			try
			{
				double startTime = EditorApplication.timeSinceStartup;

				CalculateSingleColorTextures();
				SaveSession( null );

				Debug.Log( "Refreshed in " + ( EditorApplication.timeSinceStartup - startTime ) + " seconds." );
			}
			catch( Exception e )
			{
				Debug.LogException( e );
			}
			finally
			{
				EditorUtility.ClearProgressBar();

				if( File.Exists( LOW_RES_DUMMY_TEXTURE_PATH ) )
					AssetDatabase.DeleteAsset( LOW_RES_DUMMY_TEXTURE_PATH );
				if( File.Exists( HIGH_RES_DUMMY_TEXTURE_PATH ) )
					AssetDatabase.DeleteAsset( HIGH_RES_DUMMY_TEXTURE_PATH );
			}

			GUIUtility.ExitGUI();
		}

		// Draw found results
		if( results.Count > 0 )
		{
			EditorGUILayout.HelpBox( "- Double click a path to select the Texture asset\n- Right/middle click a path to hide it from the list", MessageType.Info );

			for( int i = 0; i < results.Count; i++ )
			{
				Rect rect = EditorGUILayout.GetControlRect( false, EditorGUIUtility.singleLineHeight + 2f );
				rect.xMin += 3f;
				rect.xMax -= 3f;

				Rect iconRect = new Rect( rect.x, rect.y, rect.height, rect.height );
				rect.xMin += iconRect.width + 2f;

				Texture icon = AssetDatabase.GetCachedIcon( results[i] );
				if( icon )
					EditorGUI.DrawTextureTransparent( iconRect, icon );

				// Buttons must support 1) click and 2) drag & drop. The most reliable way is to simulate GUI.Button from scratch
				buttonGUIContent.text = results[i];
				int buttonControlID = GUIUtility.GetControlID( FocusType.Passive );
				switch( ev.GetTypeForControl( buttonControlID ) )
				{
					case EventType.MouseDown:
						if( rect.Contains( ev.mousePosition ) )
						{
							GUIUtility.hotControl = buttonControlID;
							buttonPressPosition = ev.mousePosition;
						}

						break;
					case EventType.MouseDrag:
						if( GUIUtility.hotControl == buttonControlID && ev.button == 0 && ( ev.mousePosition - buttonPressPosition ).sqrMagnitude >= BUTTON_DRAG_THRESHOLD_SQR )
						{
							GUIUtility.hotControl = 0;

							Object asset = AssetDatabase.LoadMainAssetAtPath( results[i] );
							if( asset )
							{
								// Credit: https://forum.unity.com/threads/editor-draganddrop-bug-system-needs-to-be-initialized-by-unity.219342/#post-1464056
								DragAndDrop.PrepareStartDrag();
								DragAndDrop.objectReferences = new Object[] { asset };
								DragAndDrop.StartDrag( "DuplicateAssetDetector" );
							}

							ev.Use();
						}

						break;
					case EventType.MouseUp:
						if( GUIUtility.hotControl == buttonControlID )
						{
							GUIUtility.hotControl = 0;

							if( rect.Contains( ev.mousePosition ) )
							{
								if( ev.button == 0 && File.Exists( results[i] ) )
								{
									// Ping clicked Texture
									double clickTime = EditorApplication.timeSinceStartup;
									if( clickTime - lastClickTime < 0.5f && lastClickedPath == results[i] )
									{
										if( !ev.control && !ev.command && !ev.shift )
											Selection.objects = new Object[] { AssetDatabase.LoadMainAssetAtPath( results[i] ) };
										else
										{
											// While holding CTRL, either add clicked asset to current selection or remove it from current selection
											Object asset = AssetDatabase.LoadMainAssetAtPath( results[i] );
											List<Object> selection = new List<Object>( Selection.objects );
											if( !selection.Remove( asset ) )
												selection.Add( asset );

											Selection.objects = selection.ToArray();
										}
									}
									else if( instanceIDFromGUID != null )
										EditorGUIUtility.PingObject( (int) instanceIDFromGUID.Invoke( null, new object[] { AssetDatabase.AssetPathToGUID( results[i] ) } ) );
									else
										EditorGUIUtility.PingObject( AssetDatabase.LoadMainAssetAtPath( results[i] ) );

									lastClickTime = clickTime;
									lastClickedPath = results[i];
								}
								else if( ev.button == 1 )
								{
									// Show an option to hide that Texture from the list
									int _i = i;
									GenericMenu menu = new GenericMenu();
									menu.AddItem( new GUIContent( "Hide" ), false, () => HideTexture( _i ) );
									menu.ShowAsContext();
								}
								else if( ev.button == 2 )
									HideTexture( i );
							}
						}
						break;
					case EventType.Repaint:
						EditorStyles.textField.Draw( rect, buttonGUIContent, buttonControlID );
						break;
				}

				if( ev.isMouse && GUIUtility.hotControl == buttonControlID )
					ev.Use();
			}

			GUILayout.Space( 1f );
		}

		GUILayout.EndScrollView();
	}

	private void CalculateSingleColorTextures()
	{
		// Dummy Texture is used to read Textures' pixels
		CreateDummyTexture( LOW_RES_DUMMY_TEXTURE_PATH, 32 );
		CreateDummyTexture( HIGH_RES_DUMMY_TEXTURE_PATH, 1024 );

		results.Clear();

		string[] textureGUIDs = AssetDatabase.FindAssets( "t:Texture" );
		if( textureGUIDs.Length == 0 )
			return;

		string pathsLengthStr = "/" + textureGUIDs.Length.ToString();
		float progressMultiplier = 1f / textureGUIDs.Length;

		for( int i = 0; i < textureGUIDs.Length; i++ )
		{
			if( i % 15 == 0 && EditorUtility.DisplayCancelableProgressBar( "Please wait...", string.Concat( "Searching: ", ( i + 1 ).ToString(), pathsLengthStr ), ( i + 1 ) * progressMultiplier ) )
				throw new Exception( "Search aborted" );

			if( string.IsNullOrEmpty( textureGUIDs[i] ) )
				continue;

			string path = AssetDatabase.GUIDToAssetPath( textureGUIDs[i] );
			if( string.IsNullOrEmpty( path ) || !path.StartsWith( "Assets/" ) || path == LOW_RES_DUMMY_TEXTURE_PATH || path == HIGH_RES_DUMMY_TEXTURE_PATH )
				continue;

			// Happens for Font Assets' Textures, for example
			if( !typeof( Texture ).IsAssignableFrom( AssetDatabase.GetMainAssetTypeAtPath( path ) ) )
				continue;

			// First downscale the Texture to 32 pixels for performance reasons, then downscale it to 1024 pixels to verify the result
			if( CheckTextureAtPath( path, LOW_RES_DUMMY_TEXTURE_PATH ) && CheckTextureAtPath( path, HIGH_RES_DUMMY_TEXTURE_PATH ) )
				results.Add( path );
		}
	}

	// Creates dummy Texture asset that will be used to read Textures' pixels
	private void CreateDummyTexture( string path, int maxSize )
	{
		if( !File.Exists( path ) )
		{
			File.WriteAllBytes( path, new Texture2D( 2, 2 ).EncodeToPNG() );
			AssetDatabase.ImportAsset( path, ImportAssetOptions.ForceUpdate );
		}

		TextureImporter textureImporter = AssetImporter.GetAtPath( path ) as TextureImporter;
		textureImporter.maxTextureSize = maxSize;
		textureImporter.isReadable = true;
		textureImporter.filterMode = FilterMode.Point;
		textureImporter.mipmapEnabled = false;
		textureImporter.alphaSource = TextureImporterAlphaSource.FromInput;
		textureImporter.alphaIsTransparency = true;
		textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
		textureImporter.SaveAndReimport();
	}

	// Checks if downsized Texture's pixels are all same
	private bool CheckTextureAtPath( string texturePath, string dummyTexturePath )
	{
		File.Copy( texturePath, dummyTexturePath, true );
		AssetDatabase.ImportAsset( dummyTexturePath, ImportAssetOptions.ForceUpdate );

		Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>( dummyTexturePath );
		if( !texture ) // RenderTextures, for example, are also Textures but not Texture2Ds
			return false;

		Color32[] colors = texture.GetPixels32();
		Color32 color = colors[0];
		for( int i = 1; i < colors.Length; i++ )
		{
			Color32 color2 = colors[i];
			if( color2.r != color.r || color2.g != color.g || color2.b != color.b || color2.a != color.a )
				return false;
		}

		return true;
	}

	// Hides the Texture at the specified index from the results
	private void HideTexture( int textureIndex )
	{
		results.RemoveAt( textureIndex );

		SaveSession( null );
		Repaint();
	}

	// Saves current session to file
	private void SaveSession( string json )
	{
		if( string.IsNullOrEmpty( json ) )
			json = JsonUtility.ToJson( new SaveData() { paths = results }, false );

		File.WriteAllText( SAVE_FILE_PATH, json );
	}

	// Restores previous session
	private void LoadSession( string json )
	{
		if( string.IsNullOrEmpty( json ) )
		{
			if( !File.Exists( SAVE_FILE_PATH ) )
				return;

			json = File.ReadAllText( SAVE_FILE_PATH );
		}

		SaveData saveData = JsonUtility.FromJson<SaveData>( json );

		// Remove non-existent paths
		for( int i = saveData.paths.Count - 1; i >= 0; i-- )
		{
			if( !File.Exists( saveData.paths[i] ) )
				saveData.paths.RemoveAt( i );
		}

		results = saveData.paths;
		Repaint();
	}
}
相关推荐
码农君莫笑24 分钟前
使用blazor开发信息管理系统的应用场景
数据库·信息可视化·c#·.net·visual studio
可喜~可乐3 小时前
C# WPF开发
microsoft·c#·wpf
界面开发小八哥5 小时前
DevExpress WPF中文教程:Grid - 如何移动和调整列大小?(二)
ui·.net·wpf·界面控件·devexpress·ui开发
666和7777 小时前
C#的单元测试
开发语言·单元测试·c#
小码编匠8 小时前
WPF 星空效果:创建逼真的宇宙背景
后端·c#·.net
超龄魔法少女9 小时前
[Unity] ShaderGraph动态修改Keyword Enum,实现不同效果一键切换
unity·技术美术·shadergraph
蔗理苦10 小时前
2024-12-24 NO1. XR Interaction ToolKit 环境配置
unity·quest3·xr toolkit
花生糖@10 小时前
Android XR 应用程序开发 | 从 Unity 6 开发准备到应用程序构建的步骤
android·unity·xr·android xr
向宇it10 小时前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
虾球xz11 小时前
游戏引擎学习第55天
学习·游戏引擎