原文链接
原文写得很好,但是主题格式我不太喜欢,所以我对文章的格式进行了修整,方便自己日后阅读
正文
前言
Unity 的 Prefab 相信大家一定很熟悉了,但是用起来那叫个又爱又恨,爱是因为它方便,恨是因为它不支持嵌套,经过大家的各种抱怨,终于在 Unity 2018.3.0f2 中迎来了功能支持~
先说下 Prefab 都有哪些好处吧:
- 类似模版的概念,修改基本 Prefab,所有实例都会相应的更新改变。(这也是原本 Prefab 就有的功能,也是最基本的功能)
- 嵌套,可多级嵌套,在角色、UI、特效等上都有很大的发挥空间。
- 变体,在做同一类型资源(有不同外观或属性)但是又希望每一个都是个 Prefab 时比较有用。
好处了解完后,接下来就详细的从头到尾来捋一捋。
Prefab 基础知识
首先要说下如何生成 Prefab,以一个机器人 Robot(利用一些基本 Cube 拼出一个机器人)为例:
- 利用一些 Cube 拼出的机器人
如何生成 Prefab?
- 首先在 Hierarchy 面板中创建出我们的机器人,并且把所有的部件都放在一个叫
Robot的空物体下面。(因为我们是要把整个机器人做为 Prefab 来保存,所以需要把它们组合到一个对象下面来管理) - 在 Hierarchy 面板中,将 Robot 拖到 Project 面板内(具体拖到哪个文件夹可自由决定),即可在相应的文件夹内生成 Robot 的 Prefab。
在 Hierarchy 面板中,对象名称左边有个小图标,在普通情况下它是一个灰白色的小方块,而当它是个 Prefab 实例时,则是一个蓝色的小方块,这是在 Hierarchy 面板区分物体是否为 Prefab 的一个直观的方式。
在 Project 面板中,Prefab 是以.prefab为后缀命名的。
如何编缉 Prefab?
有多种方式可以对 Prefab 进行编缉,这里编缉的意思是指对 Prefab 本身做修改,以使修改自动更新到所有的实例中。
- 第一种方式是在 Prefab Mode 中进行编缉。
Prefab Mode 是新版中的新术语,意思就是进入一个独立的空间中,在其中对 Prefab 进行修改编缉。
- 第二种方式是在外面进行,也就是普通界面下进行修改编缉。
Prefab Mode
要在 Prefab Mode 中编缉,首先就要进入到此模式下,有以下几种进入方式:
- 在 Hierarchy 面板中,Prefab 对象的最右侧有个向右的小箭头,通过点击此箭头即可进入 Prefab Mode 中。
从 Hierarchy 进入 Prefab Mode
- 同样是在 Hierarchy 面板选中 Prefab 后,在 Inspector 面板最上方会显示
Open字样的按钮,点击 Open 即可进入。
从 Hierarchy 选中对象并从 Inspector 面板中进入 Prefab Mode
- 在 Project 面板中,选中 Prefab 后,在 Inspector 面板中会显示
Open Prefab字样的按钮,点击即可进入。
从 Project 进入 Prefab Mode
进入后呈现如下所示:
Prefab Mode
- 一级一级的退出 Prefab Mode,每点一次回退一级。
- 点击标签可以回退到指定的上一级,多级嵌套时可以方便的回到某个层级中。
- Auto Save,自动保存,默认勾选,在 Prefab Mode 中所做的任何修改都会 自动进行保存。如果电脑性能不佳,有出现卡顿感时,可以选择去掉勾选,在需要时主动点击 Save 按钮(当去掉勾选时,在其左边会出现 Save 按钮)。另外,当去掉勾选,同时也做了一些修改,但是在没有点 Save 后,直接选择退出 Prefab Mode,此时会弹出一个提示对话框,需要选择是否对修改进行保存。还是相当贴心的。。。
Editing Environment
编缉环境,什么意思呢,就是说当我们进入 Prefab Mode 时, 默认 使用的都是一个只有天空盒的空场景,那如果我们想要换个场景怎么办呢?
通过 Edit>Project Settings>Editor 打开工程设置界面:
- 设置 Editing Environments
在此设置界面中有两个 Environment 可供设置:
Regular Environment:常规 Prefab 的编缉环境,常规是什么意思呢,其实就是 非 UI 类的 都属于常规类,也就是说凡是 Prefab 父级是 Transform 的都会用这个环境。
- 常规类用 Regular Environment
UI Environment:UI 类的 Prefab 编缉环境,凡是 Prefab 父级是 RectTransform 的都会用这个环境。
- UI 类用 UI Environment
实例
现在把 Robot Prefab 从 Project 中拖入 Hierarchy 中,也就表示在场景中生成了一个 Robot 的实例,然后再拖进来一个,移动一下它的位置,至此,我们在场景中有了两个 Robot 的实例。
大部分情况下,我们都希望生成出来的实例是有些稍稍不同的,比如某个 Robot 大一点,某个 Robot 又高一些,甚至某些 Robot 有些额外的部件等等。
总体而言呢,会有以下几种类型的不同:
- 属性值的不同
- 组件的新增与删除
- 子物体的添加
注意,除此以外的操作会要求进入到 Prefab Mode 模式下更改,说明在实例上不支持,必须修改 Prefab 本体才行。比如 子级排序变更、子对象的删除 等操作。
删除子对象的操作可以采用在 Inspector 面板中禁用显示来变相达到需求。
当实例上有修改时,在 Inspector 界面中会以一个蓝色条状进行标示,以示区别。
- Prefab 实例修改后的标记
Prefab 的覆盖与恢复
概念
覆盖与恢复操作是 Prefab 实例中很重要的两个概念:
- Overrides(覆盖) 将实例中所做的修改覆盖应用到原始 Prefab 中去,此操作会更新所有的实例。
比如场景中有 1000 个机器人实例,当把其中一个改成 2 倍大,然后将其覆盖,此时场景中所有的实例都会变成 2 倍大。(前提是另外那 999 个机器人的缩放值没有修改过)
- Revert(恢复) 将某个实例上的修改移除,并恢复到原始 Prefab 上的状态。
覆盖与恢复(方法一)
实现覆盖与恢复的方法很多,先说第一种,当我们选中实例时会在 Inspector 面板顶部显示三个按钮:
Open:进入 Prefab ModeSelect:在 Project 面板中选中此 PrefabOverrides:覆盖或者恢复实例上的修改
其中覆盖与恢复操作就在 Overrides 中,默认情况下点击 Overrides 时,由于实例上没有任何修改,所以不会有覆盖与恢复相关的按钮,如下所示:
- 实例上的基本操作
对象的
Position与Rotation属性有修改时,不会算作 Overrides,也就是说我们在实例上不管怎么修改位置与旋转都不会产生覆盖操作,同理,当我们修改了 Prefab 自身的位置与旋转值时也不会影响到任何实例。 注意Scale值会被收集到 Overrides 中。
当实例上产生可覆盖的修改时,面板将显示如下:
Revert All:恢复实例的所有修改到 Prefab 原始状态,也就是移除所有修改,恢复到默认状态。Apply All:将此实例上的所有修改都覆盖到 Prefab 本身,此操作会影响到所有的实例。- 当一个实例有修改时,会在此处列出每个修改块,通过点击可查看具体的修改信息。
- 查看每个修改块的具体修改内容,如果是新增的就会单独以 Added 的形式展示,如果是原先 Prefab 就有的,则会显示出两者的对比,便于参照,可以说是非常良心了。同时在此小面板的右上角有
Revert和Apply两个按钮,与 1、2 大同小异,只不过是单独针对选中的对象进行操作的。
覆盖与恢复(方法二 )
第二种方法,还是在 Inspector 面板中,在修改属性的上面点击右键,从而会弹出相关的命令,如下图所示:
- 在 Component 上覆盖与恢复 Prefab
Apply to Prefab "XXX":表示将此条修改覆盖到原始 Prefab中。Revert:表示恢复此条修改。
添加与删除组件也可以用此方法来弹出操作命令,这里不再详细说明。 此种方式在修改了单独的属性并想快速覆盖与恢复时比较常用。
覆盖与恢复(方法三 )
第三种方法,这回是在 Hierarchy 面板中,在对象上点击右键,如下图所示:
注意,此种方法只能对新增的子物体进行覆盖与恢复,无法针对组件以及对象上的属性修改做覆盖与恢复操作。
这三种方法,各有各的针对情况,
- 第一种方法 最全面,但是相对要小心处理,
- 第二方法 针对组件上的属性与添加和删除组件很好用,
- 第三种方法 主要针对添加子物体时的操作。
所以三种方法相互配合才是提高操作效率的最佳手段。
嵌套 Prefab
新版中 最重要 的功能就是支持嵌套了,实际操作起来也是非常简单,重要的一些概念在上面都已经讨论过了。
如下图中,我们将 Robot 的各个部件都单独生成一个 Prefab 并且放置于 Robot 内,这就形成了嵌套 Prefab。
- Prefab 中的嵌套 Prefab
嵌套的 Prefab 的本身就是 Prefab,把它当作常规 Prefab 就行了,只不过在新版本中,可以将它放在其它 Prefab 内,并同时保留自身的引用关系,当本体更新时它也会自动更新,这在以前的版本中是做不到的。
同时也支持多级嵌套,即 Prefab 中有子 Prefab,子 Prefab 中又可有自己的子 Prefab。。。以此类推,具体不清楚有没有嵌套层级数量限制,理论上来讲应该是无限的。
Prefab 变体
此概念也是新版本中的功能,具体是什么意思呢?
试想一下,假如我们的 Robot 是一个怪物,那么我们现在想要多个怪物,但是它们外观又不大一样,并且各自的属性也不太相同,同时呢,它们又都是属于 Robot 类,也就是说,我们要生产一批 类型差不多但稍有细微差别 的一组 Robot。
按照 以前的做法,我们肯定是生成多个 Prefab,各个 Prefab 不相关,不同的外观不同的属性,一旦想改变所有的属性时就必须要 去一个个的修改,这是极其不方便的。
那么 Prefab 的变体就是为了解决此类问题而生的。
利用 Prefab 变体我们就可以快速生成多个 Prefab,同时我们可以给予它们 不同的外观与属性,但是它们又 共同继承 自原始的 Prefab,当我需要整体都改变时只需调整最原始的 Prefab 即可。
如何创建 Prefab 变体
在 Project 面板中,在原始 Prefab 上点击右键,从中依次选择 Creat>Prefab Variant 即可。
- 创建 Prefab 变体菜单
然后在原始 Prefab 边上就会出现一个变体资源,注意 图标样式与普通 Prefab 是不同的。
- 生成 Prefab 变体
这个时候就可以像修改普通 Prefab 一样的去修改变体 Prefab 了,但是有一点 千万要注意:
在变体的 Prefab Mode 中,选中 Prefab 最上层时的 Overides 下覆盖按钮变成了 Apply All to Base,此命令会使用变体的修改去覆盖原始 Prefab,请 千万明确 此操作确实是你想要的再去点。
通常情况下,变体是不会去覆盖本体的,否则就失去了变体的意义了。
- 变体上的覆盖到基类
下图简单展示了一个变体与本体的效果差别:
变体比本体中多了脚的部件,同时头部的属性值也发生了变化。
此时当我们去修改本体属性时,变体也会自动更新。这就是变体的 核心方便 之处。
- 变体与本本
Prefab 解散
同样,新功能,其实 Prefab 确实很像一个组,组内包含各式各样的部件与组件,那既然是一个组,那就有解散组的可能性。
所以官方也是考虑到了这一点,也提供了相应的功能支持。
如何解散
- 首先解散操作 只能 在实例上进行操作,也就是只能在 Hierarchy 中进行。所以先在 Hierarchy 中选中 Prefab 实例。
- 然后右键,从中选择
Unpack Prefab或者Unpack Prefab Completely。
- Unpack Prefab:解散 Prefab 与本体的关联,此时在 Hierarchy 面板中父级会变成灰色图标,表示不再是一个实例。在 Inspector 面板中 Prefab 相关的操作也会消失。但是 子级的 Prefab 属性还是存在的。(如果子级有 Prefab 的话)
- UnPack Prefab Completely:完全解散 Prefab,此时不管是父级还是多深的子级全部失去与各自 Prefab 的关联,也就是纯粹变成了场景中的一组对象而已。
在对 Prefab 变体 实例 Unpack Prefab 后,它会首先解散成普通 Prefab(此时与原始 Prefab 会进行关联)。当再次执行 Unpack Prefab 后才会失去与本体 Prefab 的关联。
运行时覆盖
新版本同时也带来了一些 新的问题,除了 API 需要更新以外,运行时修改并覆盖的功能也没了,在之前运行时选中 Prefab 实例,在 Inspector 面板是有 Apply 按钮来覆盖修改的,但是新版本中就完全消失了,这个功能对于美术特效人员来说 非常重要,可以在运行时边调效果边保存,虽说也可以手动拖到 Project 面板中来覆盖保存,但未免总是有些不方便,于是抛砖引玉,给出下面这段运行时保存的代码:
- 运行时保存代码
public class EffectPrefabApplyEditorWindow : EditorWindow
{
[MenuItem("Window/Effect Apply...")]
static void Open()
{
EffectPrefabApplyEditorWindow window = EditorWindow.Getwindow<EffectPrefabApplyEditorWindow>();
window.titleContent = new GuIContent("prefab Apply...");
window.show();
}
void OnGUI()
{
if (GUILayout.Button("Apply(覆盖特效 Prefab)",GUILayout.Height(30)))
{
var go = Selection.activeGameObject;
var guids = AssetDatabase.FindAssets("t:Gameobject", new string[]{"Assets/Resources/Effects"});
bool isOk = false;
}
foreach (var g in guids)
{
string path = AssetDatabase.GUIDToAssetPath(g);
GameObject prefab = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject;
if (go.name == prefab.name)
{
PrefabUtility.SaveAsPrefabAssetAndConnect(go, path, InteractionMode.AutomatedAction);
EditorUtility.DisplayDialog("Tips","Prefab 覆盖成功!","OK");
isOk = true;
break;
}
}
if (!isOk)
{
EditorUtility.DisplayDialog("Tips","没有找到对应的 Prefab","OK");
}
EditorGuILayout.LabelField("在 Hierarchy 中选择特效的最上级,然后点击 Apply, 否则保存不成功!");
}
}
工具界面最终如下:
- 工具界面
大概思路
设想利用 EditorWindow 做成一个小工具窗,其中有一个按钮,在运行时修改完特效后选中 此特效的父级 直接点击按钮即可。
由于新版本中运行时 Prefab 实例会 丢失 与原始 Prefab 的关联(目测是这样),所以只能通过名称在相应的目录内来查找,查找到后进行替换,否则提示找不到。
此工具只是抛砖引玉,通用性太差,如果大家有更好更方便的方法请留言回复。