#虚幻引擎#unity3d#游戏开发#建筑学
这是我在其他人的虚幻引擎项目中遇到的一种常见(反)模式:一个附加了一个或多个碰撞组件的网格。这会起作用,但它不是最佳的,并且可能表明开发人员已经习惯了 Unity。
Unity 和虚幻引擎之间的一个重要架构差异是组件和标准变换层次结构之间的关系。“标准变换层次结构”是指内置组件和用户代码共享对一组通用变换(位置、旋转、缩放)和层次结构(父/子引用)值的访问的接口。在任何现代游戏引擎中,总会有一个1中央变换层次结构实现,作为所有 3D 事物的可访问和可预测的主干。正是这种原生层次结构支持编辑器的拖动移动功能、重叠检测和渲染剔除等功能,所有这些都不需要游戏开发人员在编辑器事件、物理子系统和渲染子系统之间显式读取和写入转换值.
在 Unity 中,Transform 组件是标准变换层次值的唯一所有者,并且是一个密封类;每个游戏对象只有一个变换组件,反之亦然;依赖于变换的组件(例如,需要知道在哪里渲染的网格)使用其游戏对象的一个变换组件的变换。
在虚幻引擎中,场景组件是标准变换层次值的指定所有者;变换相关组件是场景组件的子类。看起来好像Actor 也具有标准变换值,但这只是因为每个 Actor 至少有一个场景组件(相反,场景组件完全能够在没有任何拥有的 Actor 的情况下存在)。
基本上,区别归结为:两个引擎都有由“组件”类组成的“实体”类。在 Unity 中,“实体”类有一个变换;在虚幻引擎中,“组件”类可以有一个变换。差异可能看起来很微妙,但具有重要的架构含义,并表明每个引擎中的模式更广泛。
Unity 中的 Mesh Renderer 没有变换值,因为它使用其游戏对象的变换。同样,它实际上也不拥有对它应该渲染的网格的任何引用:相反,它从其相邻的网格过滤器中借用该值。如果游戏对象还有一个网格碰撞器,这是一种不同类型的功能,也依赖于三角形网格的使用,它将使用与其相邻网格渲染器相同的网格过滤器。Mesh Filter 和 Transform 一样,每个 Game Object 只能使用一个。
在虚幻引擎中,静态网格组件需要一个变换值,所以它扩展了场景组件。样条网格组件需要做与静态网格组件相同的事情,但有额外的细微差别,因此它扩展了静态网格组件。虚幻引擎的静态网格体组件并没有将“网格渲染”和“网格碰撞”分解为单独的组件,而是实现了渲染和碰撞(每个都可以选择激活或停用)。
这当然看起来像是组合与继承的问题。考虑到“组合优于继承”的口头禅,您可能会得出结论,Unity 选择了更好、更灵活的方法。相反,Unity 的方法只是将耦合转移到一个不太方便的抽象级别:游戏对象最终被定义为“一组具有相同变换、网格资源引用、物理属性......的组件”等。
这限制了游戏对象作为“实体”机制的有用性。逻辑上组成一个实体(玩家)的一组组件可能具有不同的位置和网格(右手、左手、相机)。在这种情况下,Unity 要求您将该实体分解为松散形成的部分实体的汤,因为作为引擎,它将“实体”与“位置”和“网格”以及许多其他概念耦合在一起。
另一方面,虚幻引擎的 Actor 可以更简单地定义为“一个放置逻辑的地方,该逻辑将一个或多个组件组合在一起作为一个单一的代表实体”。
虚幻引擎的解耦方法允许您通过引擎工具更清楚地表示和执行游戏的主要逻辑封装。正如您在上面的屏幕截图和下面的图表中看到的那样,这产生了一个更精简的对象模型,使开发人员更容易编辑游戏,并使 CPU 更容易运行游戏。
虚幻引擎的组件有时确实表现出极端的继承深度,但只要您“深入了解”,您就会发现大量的合成。 FPrimitiveSceneProxy是渲染子系统实际解析的对象类型。 FBodyInstance是碰撞子系统实际解析的对象类型。 UStaticMeshComponent由and组成,以一种协调它们以表示一件事的方式将它们包装起来,您可以在编辑器中和通过高级脚本轻松定义它。FPrimitiveSceneProxyFBodyInstance
在 Unity 中,组件是组合单个事物的物理和视觉方面的唯一方法,因为组件是我们开发人员对这些子系统的唯一访问权限。在虚幻引擎中,我们只是有更多的组合层,非托管(非反射)C++ 类是最低层。
因此,虽然可以使用组件来组合事物的视觉和物理方面,但这不是必需的,也不是理想的——引擎已经在较低级别将这些事物组合在一起,并允许我们通过静态网格调整它们组合的细微差别组件的属性。
我已经提到了几种使虚幻引擎的方法更易于管理的方法,但它并非完全没有缺点。从技术上讲,将与碰撞相关的信息附加到每个静态网格体组件(包括那些实际上不需要碰撞的组件)会产生一些浪费的内存开销(尽管这可能与 Unity 的额外组件和游戏对象实例的内存开销相匹配)在相同的情况下需要)。最重要的是,未使用的 Collision 属性的存在会导致编辑器中的视觉混乱,这可能是一项繁琐的解析工作。Unity 中的典型属性面板有一些极简主义的东西,使得在编辑器中工作时更容易磨练相关属性。
清理完所有这些后,让我们重新审视本文开头的通电网格。您现在知道静态网格体组件已经将渲染子系统和碰撞子系统组合在一起,因此不需要额外的组件。不必要的并不总是等同于“坏”,但在这种情况下,它肯定等同于“更糟”。
拥有 3 个组件而不是 1 个组件的缺点,所有其他条件都相同,包括
对于单个实例,这可能永远不会产生明显的差异。但是,在整个游戏中应用于多个演员(每个演员都有多个实例)时,您最终可能会得到数百或数千个组件的差异,这可能会导致在编辑器中调整属性的时间相差数小时,并且在低端移动设备上每帧几毫秒。
我将逐步完成正确的实施。
如果目标只是给出网格碰撞,它可以像在网格组件本身上启用所需的碰撞设置(在碰撞组件上找到的完全相同的设置)一样简单。但是,如果有人在静态网格下添加碰撞组件,很可能是因为他们希望碰撞包含特定的形状排列,无论是出于功能目的还是仅仅为了优化。
对于这个例子来说,这当然是正确的。只需在静态网格组件上添加物理配置文件就足以使其与物体发生碰撞,但它会使用三角形网格碰撞来实现。使用单个胶囊和单个盒子在计算上会更便宜,也许您还需要区分手柄与物品主体的碰撞。
我一直在谈论静态网格体组件,并提到它承载与渲染相关的属性和与物理相关的属性。静态网格资产也是如此,您可以在其中找到与此特定任务最相关的属性。
相关的静态网格体资源可以使用静态网格体组件详细信息中的“浏览至”() 图标位于内容浏览器中。从那里,它可以像任何其他资产一样打开和编辑。
在 Unreal Engine 中,与在 Unity 中一样,静态网格体资源隐含地提供三角形网格体碰撞。在虚幻引擎中,静态网格体资源可以额外包含“简单碰撞”。您可以使用此功能来实现特定的形状排列,而无需使用其他组件。
打开静态网格体资源后,首先要做的是确保“简单碰撞”可视化已打开。
打开静态网格体并查看其简单碰撞时,您可能会发现已经定义了某种形式的简单碰撞(它可能已被导入,或在导入时生成)。在这种特定情况下,已经有一个凸包,但是胶囊 + 盒子的设置在性能方面会更便宜,并导致更高保真度的碰撞行为。
在静态网格体资源编辑器顶部工具栏中的碰撞菜单中,我们可以找到删除现有不需要的碰撞以及添加新形状的功能。
添加后,可以使用 3D 小部件和/或详细信息面板修改形状。
这些更改对静态网格体的所有实例立即生效(当然,不包括那些配置为忽略简单碰撞并始终使用复杂碰撞的实例)。您可以通过在关卡编辑器中打开“显示碰撞”(alt + C)来确认这一点。
你有几个选择:
如果网格很复杂,选项 1 可能比选项 2 使用更多的内存。如果网格很简单,选项 2 可能比选项 1 使用更多的内存。
这可以在资产中按形状进行控制。
每个形状都可以命名。
相关形状的名称通过 Sweep Result 结构提供。
骨架网格体也是如此:可以通过将碰撞组件附加到骨架网格体组件来添加碰撞,但最好在资产编辑器中添加碰撞,以便它直接从单个骨架网格体组件中显示出来。概念是相同的,但步骤有点不同,考虑到骨架网格物体碰撞需要考虑运动和其他附加因素。
UE5 表面上看起来有很大的不同,但就本文讨论的所有内容而言,唯一的区别是 UE4 的编辑器在位置略有不同的地方有一两个菜单。
在虚幻引擎中创作碰撞时,最好在网格资产本身上定义碰撞。由于对虚幻引擎的架构更加惯用,它将促进更好的性能,并且如果在多个地方需要这种碰撞,肯定会更容易维护。