Notifications
Article
使用Preset(预设)功能改善工作流,验证决策,避免错误
Updated a month ago
1.2 K
6
现在我们无需编程,使用Preset(预设)功能就能自定义Unity中的几乎所有:Components(组件)、Importers(导入器)、Managers(管理器)。Preset功能可以帮助大小团队完成流线式重复任务、验证设计决策,在项目中应用生产标准和项目模板。在本文中,我们将深挖Preset功能的各个特点,包括基本功能、提示与技巧,然后学习部分使用案例。
作者:Mac Tanenbaum和Bastien Humeau,2019年10月11日
原文地址:https://blogs.unity3d.com/2019/10/11/improve-workflows-validate-decisions-and-avoid-errors-with-presets/

首先,什么是Preset?

基本上,Preset是一个能让你覆写一个组件、导入器或管理器的默认设置功能(实际上任何Unity.Object相关的东西都行)。你项目中的RigidBody(刚体)需要默认设置质量为10?要关掉重力效果?用预设功能设置吧。
我们还能在层级中导入或实例化的部分上使用Preset:纹理、FBX文件,光照、摄像机或刚体之类的MonoBehavior组件。
注意Preset属于编辑器功能,主要用于改善创作工作流,并不能用于运行时。Preset并不会影响到你的二进制数据,所以GameObject.AddComponent<RigidBody>()上不会应用这些默认设置。但是,编辑器中的ObjectFactory API支持Preset功能,它将在ObjectFactory.AddComponent<RigidBody>()上得以应用。
要创建一个预设非常简单,在已有导入器或同类组件的检视器中做出调整后,点击右上的Preset图标即可。在弹出窗口中选择“Save Current To…(将现设置存为…)”将设置存为资源。

使用Preset快速工作的提示与技巧

以下是在项目中使用Preset的几种有趣的方法。

点击Preset图标

好吧,这一条显而易见。我们能在任何检视器中点击Preset图标选择相同组件或文件类型下的预设设置。

拖入检视器

我们可以从项目窗口中将一条预设拖到一个组件上,来改变它的各项数值。
我们还可以将预设拖入检视器,为游戏对象添加一个新的组件。

拖入层级视图

Preset也能用于在层级中创建新的对象。我们可以从项目窗口中将预设拖到层级视图中,快速创建一个包含预设相关组件的新游戏对象。
Preset的工作流和预制件比较相像,但是它们并不一样。根据设计,预设功能与其中创建的任何对象都没有连接。
我们甚至可以拖入多个预设,生成一个带有相关组件的游戏对象。

Preset管理

Preset可用Preset Manager(预设管理器)管理,我们可以在Edit→Project→Settings下找到管理器。
要添加一个默认设置,请打开Preset Manager,点击“Add Default Preset(添加默认预设)”,然后选择需要的默认设置类型。最后,点击None (Preset)字段右边的圆圈,在弹出的Select Preset(选择预设)窗口中将其分配到匹配的类型中。
下方是一个有许多内容的Preset Manager,可以看到在本文开头创建的Rigidbody 10预设,它将作为项目中刚体的默认设置。
在此设置下,每个新建的刚体将默认质量为10,且禁用重力效果。
Preset Manager允许在一个类型中保存多个默认设置。这个在2019.3中新推出的强大预设功能,可以让我们以不同的名字来保存使用多个“默认”设置。在上图中的Camera(摄像机)部分,可以看到过滤器空白的第一条使用的是Camera Gameplay预设,意味着每个摄像机默认将应用这个预设。但是我们为“UI”部分绑定了名为Camera UI的UI摄像机预设设置。在此设置下,如果我们创建了任何对象名中带“UI”字样的摄像机(UICamera、Camera UI或HUDUICamera),则Camera UI预设将被用于默认设置。
一些Preset Manager的注意事项:
  • 字符串匹配功能并不能准确区分条目,以上文“UI”为例,“GuideCamera”中也有“ui”字样,你可能不希望使用“UI”预设,但系统仍会将其设为默认。
  • 当一个对象匹配同一类型中的多个默认设置时,最后一条预设将默认被应用。
  • 我们可以点击组件中的“Reset(重置)”键将组件重置为默认。
  • 在导入器中,筛选器将匹配文件名,而不是匹配游戏对象名。

管理器Preset

Preset功能有一个非常棒的优点,它可以协调美术团队的关键创作与决策:光线是什么颜色?刚体应该有多少重量?导入法线贴图的标准是什么?当使用Preset预设管理器设置时,我们可以将这些概念上升到整个项目高度。我们来看看Physics Manager(物理管理器)及Tags&Layers Manager(标签和层级管理器)中的几个例子。
在该例中,我们通过一系列的选择来设置不同层级间的物理互动(敌人不会影响到其他敌人,友军的枪火不会影响到其它友军)。这些决策在往后的开发或类似项目开发中也能派上用场,而Preset功能可帮助我们存留这些决策设置,在不同的项目中分享。
在下方视频中,我们将两个管理器设置另存,再将其应用到另一个项目中。这能大大地帮助我们在新项目中应用原有的预设设置。

当然,还有API!

上文中提到的视觉互动方式在大部分情况下应该是足够的,但是如果你正在创建一条重要的管线或构建一套工装,Preset的编程控制一定会非常有用。为此我们公开了Preset的API,提高便利性与生产力。
下文例子可在Github上找到。
我们以一个简单的例子来演示下。假设我们希望添加一个将一条光照的属性应用到场景中所有光照的工具。
[MenuItem("CONTEXT/Light/Replicate Color in Scene")] public static void ApplyAllLights(MenuCommand command) { // Get our current selected light(获取我们选中的光照) var referenceLight = command.context as Light; if (referenceLight != null) { // Create a Preset out of it(据其创建一个Preset) var lightPreset = new Preset(referenceLight); // Find all Light components in the scene of our reference light(找到场景中所有参照光照的光照组件) var allLights = referenceLight.gameObject.scene.GetRootGameObjects() .SelectMany(r => r.GetComponentsInChildren<Light>(true)); // Choose which serialized property we want to apply to everyone(选择应用于所有光照的序列化属性) var propertyToApply = new[] { "m_Color" }; // Apply the Preset with only the selected property to all the Lights foreach (var light in allLights)(将选择属性的Preset按“ForEach”应用到所有光照上) { lightPreset.ApplyTo(light, propertyToApply); } } }
该段代码将取得参照对象光照,然后就地创建一个Preset (var lightPreset = new Preset(referenceLight))。接着使用referenceLight(参照光照)处取得的m_Color为场景中所有其他光照设置数值。
以下是新工具在Unity中的表现。
注意此处展示的是程式化的“部分预设”应用示例,即代码只抓取整个预设属性中的一个子属性。我们希望在不久后将该功能公开在UI中。
在第二个示例中,我们来看看如何将单个预设应用到项目文件夹中的所有同类型资源上。
[MenuItem("CONTEXT/Material/Replicate Material Color in Folder")] public static void ApplyAllMaterialsInFolder(MenuCommand command) { // Get our current selected Material(抓取选中材质) var referenceMaterial = command.context as Material; if (referenceMaterial != null) { var assetPath = AssetDatabase.GetAssetPath(referenceMaterial); if (!string.IsNullOrEmpty(assetPath)) { // Create a Preset out of it(据其创建一个Preset) var materialPreset = new Preset(referenceMaterial); // Find all Material assets in the same folder(在相同文件夹下找到所有材质资源) var assetFolder = Path.GetDirectoryName(assetPath); var allMaterials = AssetDatabase.FindAssets("t:Material", new[] { assetFolder }) .Select(AssetDatabase.GUIDToAssetPath) .Select(AssetDatabase.LoadAssetAtPath<Material>); // Select the first color entry; in the standard shader this entry is _Color(选取第一条颜色条目;在标准着色器中一般为‘_Color’) var propertyToApply = new[] { "m_SavedProperties.m_Colors.Array.data[0]" }; // Apply the Preset with only the selected property to all the Materials(将带选中属性Preset应用到所有材质上) foreach (var material in allMaterials) { materialPreset.ApplyTo(material, propertyToApply); } } } }
和之前一样,我们取得一个参照对象,即选中的材质 (var referenceMaterial = command.context as Material),然后就地创建一个Preset (var materialPreset = new Preset(referenceMaterial))。然后我们定位到材质所在文件夹,将颜色属性应用到相同文件夹中的所有材质上。
我们再来看看它在编辑器中是怎样的。
最后,我们来看个稍微复杂点的例子。在以下一小段中,我们将用一个选中的光照来创建预设,或根据选择的光照设置更新默认预设(若已有光照默认设置)。请阅读代码中的注释来了解其原理。
public static void UpdateOrAddLightDefaults(MenuCommand command) { // Get our current selected light(抓取选中光照) var referenceLight = command.context as Light; // Get the list of default Presets that apply to that light(抓取应用于该光照上的默认Preset) var defaults = Preset.GetDefaultPresetsForObject(referenceLight); if (defaults.Length == 0) { // We don't have a default yet, let's create one!(未有默认设置,则新创建一个默认设置) var defaultLight = new Preset(referenceLight); // We're kind people, so let's nicely ask the user where to save this default(出于用户体验考虑,询问下用户要将该默认设置保存至何处) var path = EditorUtility.SaveFilePanelInProject("Create Default Light preset", "Light", "preset", "Select a folder to save the new default"); // If the selected path already contains a Preset, its instanceID would be changed by a plain replace(如果选择路径中已有一个Preset设置,则其instanceID将被简单替换掉) // We use this trick to replace the values only so an Object referencing the existing asset will not break(唯有使用此种方法替换数值可以防止参照已有资源的对象不会损坏) var existingAsset = AssetDatabase.LoadAssetAtPath<Preset>(path); if (existingAsset != null) { EditorUtility.CopySerialized(defaultLight, existingAsset); defaultLight = existingAsset; } else { AssetDatabase.CreateAsset(defaultLight, path); } // Load the existing default list(加载已有的默认设置列表) // We don't want to lose any configuration that may point specific GameObject because of the filters(指向具体游戏对象的配置信息可能由于筛选器而损失,需要防止这种情况) var existingDefault = Preset.GetDefaultPresetsForType(defaultLight.GetPresetType()).ToList(); // Insert the new one at the beginning of the list with no filter(在列表的开头插入不带筛选器的新设置) // so it applies to any Light that doesn't have a default(这样该设置便能应用到任何不带默认设置的光照上了) existingDefault.Insert(0, new DefaultPreset("", defaultLight)); // Set the new list as default for Lights.(将新列表设为光照的默认设置) Preset.SetDefaultPresetsForType(defaultLight.GetPresetType(), existingDefault.ToArray()); } else { // We want to update the values only to the last default(在最后一条默认设置中更新数值) // because maybe other Presets apply to other objects first(因为其它Preset可能需要先应用到另外的对象上) // and we don't want to change them(这样可防止更改其它Preset) var lastPreset = defaults.Last(); lastPreset.UpdateProperties(referenceLight); } }
以下视频展示了该功能在Unity中的效果。可以看到在第一次创建新预设设置时,将有一个向导窗口,而在第二次我们则直接更新了现有设置。

未来计划:你想要的Preset功能才是好功能!

我们对该功能的未来版本已有一些想法,包括上边提到的“部分Preset”功能、更完善的筛选器功能及将Preset应用到文件夹上的功能。但是正如开头所说,Preset功能的初衷就是让Unity能根据你的需求来表现。所以请向我们提供你的反馈吧,这对我们非常重要。你对利用该功能改善工作流有什么想法或方法吗?你可以在评论区告诉我们!

"开发过程中遇到问题?在这里提问:connect.unity.com/g/discussion
觉得这篇内容不错or有待提高?请在下方评论区留言。我们会根据大家的需求,优化内容产出^_^"
Tags:
Unity China
694
Comments
L
LeoRic
25 days ago
很强
0
gao
a month ago
Game Designer
kuklyk哇 这么充满时尚感的界面 是unity2020吗
2019.3
0
HaQi
a month ago
见贤思齐
哇,这个功能我吹爆
0
宋朝阳
a month ago
Unity开发
5436
0
超镦逸
a month ago
伟大的独立游戏开发者
kuklyk哇 这么充满时尚感的界面 是unity2020吗
是2019
0