Содержание
- SearchContextAttribute
- Find all scripts where the Picker is in ListView.
- Find all scripts in Assets or in Packages with the word "overlay" in their name
- Find all materials with a shader that contains the word New
- Find all shaders with the word unlit in their name
- SearchContext and SearchExpression
- SearchContext and SearchQueryAsset
- Custom Query Builder Types Selector
- Advanced Object Selector
- Advanced Picker from an EditorWindow
- Bonus
- Conclusion
The Search Window is great at presenting various items especially assets and GameObjects. It allows to select items and act on them. It is a short step to assume that it could replace the Classic Object Picker.
Good news: Unity already supports this workflow through the Advanced Object Picker (powered by the Search Window framework). Using this Advanced Object Picker has the following advantages over the Classic one:
- The Advanced Picker has way more filtering capabilities (both for assets and GameObjects).
- All the filters can be easily found using the Visual Query Builder:
We will explore how you can customize Unity to use this Advanced Picker both trough Editor Preferences and APIs. All of the code presented in this article comes from the Samples project of the SearchExtension package.
SearchContextAttribute
When creating a new component referencing UnityEngine.Object you can use the SearchContextAttribute to specify that you want to use the Advanced Picker.
// You can specify an initial query, a search provider and some SearchViewFlags
[SearchContext("t:texture filtermode=0", "asset", SearchViewFlags.OpenInBuilderMode | SearchViewFlags.GridView)]
public Texture pixelArtTexture;
This example can be seen in Picker_SearchContext.cs:
Pressing the object field next to the Pixel Art
property will pop this picker:
The biggest advantage of using a SearchContextAttribute is to allow you to specify an initial query that can further discriminate which assets to present to the user.
The Picker_SearchContext.cs file contains multiple usages of SearchContextAttribute:
Find all scripts where the Picker is in ListView.
[SearchContext("p:t:script", "asset", SearchViewFlags.ListView)]
public MonoScript myProjectScript;
Find all scripts in Assets or in Packages with the word "overlay" in their name
[SearchContext("p:t:script overlay", "adb", SearchViewFlags.Packages | SearchViewFlags.CompactView)]
public MonoScript myPackageScript;
Find all materials with a shader that contains the word New
[SearchContext("p:shader:New", assetProviders, SearchViewFlags.HideSearchBar)]
public Material materialNoSearchBar;
Find all shaders with the word unlit
in their name
[SearchContext("p:t:shader unlit", "asset", SearchViewFlags.OpenInBuilderMode | SearchViewFlags.ListView)]
public Shader unlitShader;
SearchContext and SearchExpression
This one example is a bit more complex and powerful. It uses the ref:
filter and the SearchExpression language to find all materials referencing any shaders with unlit in their name.
// Find all material referencing any shader with unlit in its name
[SearchContext("p:t:material ref:{t:shader unlit}", "asset")]
public Material materialWithUnlitShader;
If you want to dig deeper in SearchExpression and what this powerful query language allows feel free to read this primer.
SearchContext and SearchQueryAsset
If you have read my previous article about SearchQueryAsset, you know these assets can encapsulate a query and its view state. You can use the path or guid of a SearchQuery to setup an Advanced Picker:
// Open Picker with a preloaded SearchQueryAsset specified by its path
[SearchContext("Assets/Queries/t_sprite.asset", assetProviders)]
public Sprite searchQueryPathSprite;
// Open Picker with a preloaded SearchQueryAsset specified by its guid
[SearchContext("40060e4225366c64a9e24cd17cc9fdc1", assetProviders)]
public Sprite searchQueryGuidSprite;
Custom Query Builder Types Selector
This example is even more crazy: it uses the VisualQueryBuilder marker language to specify a constrained list of 3 types to show in the Type DropDown.
[SearchContext("p: t:<$list:Texture2D, [Texture2D, Material, Shader]$>", "asset", SearchViewFlags.OpenInBuilderMode | SearchViewFlags.DisableBuilderModeToggle)]
public UnityEngine.Object myObjectOfConstrainedTypes;
Advanced Object Selector
If you want to cast a wider net and use the Advanced Picker for ALL workflows and not just for some specific properties take a look at the Search Preferences page:
If the Advanced
Object Selector is chosen AND if the Default Advanced Selector
option is toggled, it means ALL ObjectFields
in the editor will use an Advanced Picker:
If you either do not want the Advanced Picker for all workflows or you would like a custom Advanced Picker for a specific Unity Types: the AdvancedObjectSelectorAttribute has got you covered. This attribute allows you to define a function that will let you customize an AdvancedPicker for all usages of a specific type.
The Picker_AdvancedMaterialSelector.cs in the SearchExtensions package has a nice example of such usage:
const string MaterialSelectorId = "material_selector";
// Callback that decide if the current ObjectSelector context can be applied to this special Picker
[AdvancedObjectSelectorValidator(MaterialSelectorId)]
static bool CanOpenSelector(ObjectSelectorSearchContext context)
{
// This selector only works for assets.
if ((context.visibleObjects & VisibleObjects.Assets) == 0)
return false;
// This selector only supports materials and their derived types.
if (!OnlyMaterialTypes(context))
return false;
return true;
}
static bool OnlyMaterialTypes(ObjectSelectorSearchContext context)
{
var requiredTypes = context.requiredTypes.Zip(context.requiredTypeNames, (type, name) => new Tuple<Type, string>(type, name));
return requiredTypes.All(typeAndName =>
{
return typeAndName.Item1 != null && typeof(Material).IsAssignableFrom(typeAndName.Item1) ||
typeAndName.Item2.Contains("material", StringComparison.OrdinalIgnoreCase);
});
}
[AdvancedObjectSelector(MaterialSelectorId, "Unlit Material", 1)]
static void SelectObject(AdvancedObjectSelectorEventType evt, in AdvancedObjectSelectorParameters args)
{
// evt will allow you to do stuff when starting/ending a search session.
// In our case we only want to decide how to open our picker:
if (evt != AdvancedObjectSelectorEventType.OpenAndSearch)
return;
var selectContext = args.context;
var selectHandler = args.selectorClosedHandler;
var trackingHandler = args.trackingHandler;
// This selector handles any kind of materials, but if a specific material type is passed
// in the context, then only this type of material will be shown.
var searchText = "t:material ref:{t:shader unlit}";
var searchContext = SearchService.CreateContext("asset", searchText);
var viewState = new SearchViewState(searchContext,
SearchViewFlags.GridView |
SearchViewFlags.OpenInBuilderMode |
SearchViewFlags.DisableSavedSearchQuery)
{
windowTitle = new GUIContent("Material Selector with unlit shader"),
title = "Material with unlit shader",
selectHandler = (item, canceled) => selectHandler(item?.ToObject(), canceled),
trackingHandler = (item) => trackingHandler(item?.ToObject()),
position = SearchUtils.GetMainWindowCenteredPosition(new Vector2(600, 400))
};
SearchService.ShowPicker(viewState);
}
Basically you will need to do this:
-
Register an AdvancedObjectSelectorValidatorAttribute to tell the system that it should use your custom Advanced Picker for a specific type (in the example above:
Material
). -
Then register an AdvancedObjectSelectorAttribute to create a SearchContext who handles the initial query customization.
-
After that, create a SearchViewState to further customize the appearance of the Picker View.
-
Finally use the SearchService to show the picker!
This Advanced Picker Selector will be visible in the Search Preference page where you can enable or disable it:
Now when clicking on any Material ObjectField
you will get this:
Advanced Picker from an EditorWindow
If you have a custom EditorWindow or a custom Inspector you can embed an Advanced Picker in it. We have seen above that you can invoke the Advanced Picker with the SearchService. But did you know that the ObjectField supports being initialized with a SearchContext?
The file Picker_SearchServiceShowPicker.cs has a great example of both of these usages:
public class Picker_SearchServiceShowPicker : EditorWindow
{
private void OnEnable()
{
var flags = SearchViewFlags.OpenInBuilderMode | SearchViewFlags.ListView | SearchViewFlags.DisableBuilderModeToggle;
var searchContext = SearchService.CreateContext("asset", "t:material ref:{t:shader unlit}");
var objecField = new ObjectField("Material");
objecField.searchContext = searchContext;
objecField.searchViewFlags = flags;
rootVisualElement.Add(objecField);
var nothingSelected = "No Material Selected";
var label = new Label(nothingSelected);
var button = new Button(() =>
{
var searchViewState = new SearchViewState(searchContext)
{
ignoreSaveSearches = true
};
SearchService.ShowPicker(searchContext, (item, canceled) =>
{
if (canceled)
label.text = nothingSelected;
else
label.text = item.label;
});
});
button.text = "Show Picker and select material";
rootVisualElement.Add(button);
rootVisualElement.Add(label);
}
[MenuItem("Window/Search/Select Some Material")]
static private void OpenMyMaterialPicker()
{
GetWindow<Picker_SearchServiceShowPicker>();
}
}
Activating the menu item: Window/Search/Select Some Material
will pop this ugly Window with 2 different Picker workflows:
Bonus
One last example for the road: did you know that you could open a new scene in Unity using the Advanced Picker? We tought it could replace the Ctrl+O
workflow which pops an awful File Dialog (that let you navigate anywhere on disk) but life and its mysteries prevented us to do so. Instead we added this option in the Window/Search
main menu:
Bonus points if you noticed you can even access the Scene Templates of your project through the same Picker! Feel free to rebind Ctrl + O
to this Menu Item:
Conclusion
I hope this gave a good overview of the benefits of using an Advanced Picker to create more precise query allowing you to pick object more rapidly. There are various APIs available to tailor the Advanced Picker to your needs and many internal teams within Unity are converting their Object Selection workflows to use the Advanced Picker.