在 Unity3D 中管理复杂的预制层级
Posted
技术标签:
【中文标题】在 Unity3D 中管理复杂的预制层级【英文标题】:Managing complex prefab hierarchies in Unity3D 【发布时间】:2016-12-05 17:22:50 【问题描述】:我在几个地方问过这个问题,但还没有完全弄清楚,所以也许这里的一些聪明人会知道如何解决这个问题。
在项目中维护深层嵌套的预制件层次结构的最佳方法是什么?比如说,我们有几个 GUI 屏幕,由较小的通用组件组成。为了简单起见,我们将前者称为“视图”,而将后者称为“组件”。视图处理语义(例如库存视图、商店视图);组件是可配置的,但没有附加任何业务逻辑(例如,按钮只关心其 OnTap 回调/事件处理程序)。
视图和组件都可以嵌套。
GUI 视图需要在多个场景中重复使用。
Unity 中这种方法的主要问题是,任何嵌套层次结构一旦变成预制件,就会失去对其子预制件的引用,就像下面这个(完全虚构,但仍然有效)示例:
- storeView
- UIViewHeader
- UIHeaderLabel<Text>
- UIList
- UIListItem
- UIThumbnail
- UITitleLabel<Text>
- UISubTitleLabel<Text>
- UIPrimaryButton<Button>
- ...
我想将所有这些小的 UI* 组件保留在单独的预制件中,但也保留 storeView 预制件,以便我可以轻松地将其添加到不同的场景中。不幸的是,一旦我们创建了 storeView 预制件,所有的 UI* 预制件引用都会丢失。 鉴于此,我们可以尝试一种不同的方法,在这种方法中,我们不使用包含内容的 storeView 预制件,而是将其保留为空并选择以下几个选项之一:
-
在运行时将行为附加到 storeView 并加载子预制件
缺点:使设计人员的工作流程更加复杂,将复杂性置于脚本中,从开发人员的角度来看,这也可能更容易出错
优点:更容易在场景之间重用 storeView,组件预制件可以设置样式,全局修改
将 storeView 保留为空预制件,并在每个需要它的场景中重新组装它
缺点:组件需要手动连接,仍然很容易意外保存整个 storeView 并丢失 prefab 引用
优点:保证组件预制参考得到维护,允许视图之间的细微差异(恕我直言,这实际上是一个问题,因为这些应该属于配置层)
将整个 storeView 保存为预制件
缺点:可扩展性非常大,使得迭代新功能或小的 UX 更改更加耗时(额外的 QA、验收测试等...)
优点:这是一个快速而肮脏的解决方案,适用于小型项目
使用 Prefab Evolution 或类似的
缺点:我假设该包将被路线图上的嵌套预制件淘汰?需要取决于 3rd 方代码,并且可能不够灵活(大家有什么意见吗?)
优点:从我看到的许多(混合)评论中,有些评论非常积极。然而,项目越复杂,这些评价就越不积极。
编写一堆自定义编辑器脚本:
缺点:耗时,而且 - 似乎平台应该提供一些东西,即使它主要处理游戏
优点:完全控制行为,开发人员可以根据设计人员的反馈进行改进。有人可能会争辩说,将实现、测试和对设计人员友好的工具作为功能要求进行自律听起来像是一种良好的设计实践(从而减少了技术债务,更易于维护)
这是我个人的、理想的、不切实际的解决方案*:
使用组件化架构,默认情况下,子预制件引用存储在复杂预制件中。在内部将它们视为移动设备(Cocoa)上的 UIView 和子视图,或者 React 中的组件类或更好的功能组件 - React/Cycle/Elm/任何与 FRP 配合得很好的东西(是的,我知道这些方法在很多方面都不同,但关键是可组合性,可以通过功能组合、装饰器等来实现...)。
缺点:我认为我在这里遇到的困难来自于我对 Unity 的缺乏经验,并且有一个更明显的,也许是惯用的解决方案(我恳请:))优点:使测试和迭代新功能更容易,使预制件更强大,同时不会失去任何好处(如果我错了,请纠正我,我仍然熟悉 Unity)
请不要以为我期望 Unity 会提供所有这些,但这是可能的方向之一,即使其中 1% 是正确的。
为了清楚起见,这并不是对 Unity 的抱怨,作为一名以前使用移动(原生)和 Web 工作的开发人员,我发现它解决了我的许多问题,以及其中一些解决方案的简单程度令人印象深刻.
【问题讨论】:
【参考方案1】:我可能会在单独的场景中维护复合视图。只需将这些场景视为预制件,仅包含给定类的单个原型对象。
将SceneManager.LoadScene
与LoadSceneMode.Additive
一起使用,您甚至可以创建一些静态工厂方法,例如:
public class UIStoreView
public static UIStoreView Instance()
SceneManager.LoadScene("UIStoreView", LoadSceneMode.Additive);
return GameObject.Find("UIStoreView");
通过一些命名约定,您可以实现像storeView = UIStoreView.Instance();
这样简单的东西。
不是通用/可扩展的东西,但至少你有一些轻量级/可维护的东西,直到他们推出嵌套预制件(timeline uncertain 他们说)。
【讨论】:
是的,我注意到它是他们路线图上“发展”水龙头中为数不多的永久居民之一。 (对不起,不小心按了 ENTER 并只提交了第一句)但是,这种方法似乎引起了一些担忧: - 似乎不习惯,hacky - 涉及可能的性能问题(有代价加载预制件与场景)第一个问题不像往常那样重要,因为我开始认为可能根本不存在惯用的解决方案,我会等几天,如果没有其他明智的答案我会将此标记为解决方案。干杯。 是的,可能需要一些性能来换取一些灵活性。但是,场景文件实际上只是一个简单的序列化 YAML 资产,就像预制件一样。如果您想到像 Subway Surfers 这样的游戏,他们会一直使用LoadSceneMode.Additive
来传输地图。
与其他方法相比,我觉得它没有那么 hacky,因为复合原型场景可以很好地存储,以组件本身命名,同时这也使用 Unity 自己的自然机制来保持预制件的更新。
不使用嵌套层次结构听起来像是将复杂性推到其他地方而不是最小化它(我也在这样做,但有时它会导致必须手动组合复杂的视图 - 使用场景作为穷人的嵌套预制件似乎可以缓解这个问题)。顺便说一句,我认为提供的工厂方法存在问题(假设添加视图后想要删除 GUI 场景)。 Something along these lines 在我的情况下有效。以上是关于在 Unity3D 中管理复杂的预制层级的主要内容,如果未能解决你的问题,请参考以下文章