Breachers:用AssetPostprocessor和Blender快速迭代设计
快速成长中的比利时游戏公司Triangle Factory使用Unity制作了数款VR多人游戏,包括Hyper Dash和最新的Breachers。他们使用了Cinemachine、Unity Profiler、Game Server Hosting、Matchmaker、Voice Chat(Vivox)和Friends为玩家打造了一款全身心投入的体验。以下博文来自关卡设计负责人/技术美术Jel
·
快速成长中的比利时游戏公司Triangle Factory使用Unity制作了数款VR多人游戏,包括 Hyper Dash 和最新的 Breachers 。他们使用了Cinemachine、Unity Profiler、 Game Server Hosting 、Matchmaker、 Voice Chat(Vivox) 和 Friends 为玩家打造了一款全身心投入的体验。
以下博文来自关卡设计负责人/技术美术Jel Sadones和开发负责人Pieter Vantorre,将与我们分享"Blender到Unity"的生产管线,以及VR战术FPS游戏 Breachers 的制作方式。
Unity在过去十多年来一直是Triangle Factory首选的引擎和开发环境,而在环境建模和设计上,团队已在多年来尝试过许多工作流。我们试过ProBuilder(仍是我们爱用的原型制作工具)这类引擎内的建模工具,也试过从其他建模软件导入做好的预制件来组装场景。至于目前的项目,我们选择在Blender里建立关卡模型,依赖Unity的 AssetPostprocessor 把资产导入到项目中。
这里,我们希望与大家分享下团队为何选择这种流程,以及它怎样支撑起快速的设计迭代。
找到合适的工作流程
在2021年,我们发布了首款大型VR游戏 Hyper Dash —— 一个快节奏的5v5竞技射击游戏。2019年开始开发这款游戏时,很多人都熟悉了我们采用的“Blender到Unity”流程:用Blender建立模型,导出为FBX文件,再导入到Unity。整个手动流程包含以下几步:
-
创建场景里的动态对象,比如武器弹药、重生点、占领点
-
加入碰撞体来防止玩家走到或传送到特定区域
-
创建看不见的路线让机器人能正确行动
-
其他

图注:Hyper Dash(2021)
这种流程适合较小的项目,但会随项目变大而愈发繁琐。在我们计划开发下一个游戏时,改进工作流已经不可避免。
从原型中找出痛点
Breachers 带有复杂的关卡布局、微妙的游戏机制、更多技术系统,以及更加高级的图形质量,其目标平台为最新一代的独立VR硬件。从复杂性看,它比 Hyper Dash 走得更远,这点很快体现在了工作流程里。

在制作原型阶段,我们仍大量依靠预制件来保存动态对象,比如窗户挡板。这些挡板会在热身阶段阻隔内部与外部的视线,防止队伍双方看到对方。
在测试原型时,我们经常要四处变更窗户的位置来改进游戏玩法,于是也经常需要到Blender里修改模型并将它重新导出到Unity,之后就需要手动摆放挡板来契合我们的修改。就这样,很多时间都花在了在 Scene 视图里四处观察、手动检查和改正模型上。即便如此,我们在多次游戏测试期间还是会经常发现不对劲的地方。

图注:藏身处关卡的原型

图注:最终版的藏身处
很明显,这种流程没法让我们在测试期间快速更新地图设计,无论是内部测试还是alpha公测。我们本想在公测期间让玩家们免费试玩一张地图来收集反馈。用户反馈当然非常宝贵,但随反馈而来的大量手动改进过程并不让人期待。
基于预制件流程的另一个缺点在于性能。我们的主要目标平台是便携的独立VR头显,同时又想尽可能地提高画质,因此我们希望尽可能地榨取每一点性能。
用预制件组装关卡并没有直接在建模软件里创建一片连续的模型网格来得高效。就算把两个墙的组件拼在一起,贴合处也依旧有看不到的面。只要用预制件,关卡里就会有许多看不见的面(或位于对象底部,或和墙贴在一起),它们会占用宝贵的光照贴图空间。在一整个关卡里,这些性能浪费积少成多,最终影响视觉质量。

图注:原型里沙盒关卡中的可破拆窗户
最后一个问题是,某些看起来不相关的修改一旦应用到Blender的原模型上就会产生问题,比如重命名某个对象。随着游戏或关卡不断完善,资产一般都需要重新组织,取些更清晰、连贯的名称。但在Blender里重新命名对象再导出很容易毫无征兆地破坏Unity里的覆写和附加内容,导致回滚。
在下方的简单例子里,通风栅预制件会喷吐烟雾。我们的美术在导入模型网格后添加了一个烟雾粒子系统作为子对象,并加上了表面类型组件来标明对象是个金属物品。
如果在Blender里重命名模型,便会发生以下这种情况:
重命名后的模型再导入Unity时,引擎无法根据名称来找到模型网格,便会从预制件里移除它。被移除对象的子对象会被转交给预制件的根对象,脚本会被移除,新对象需要由我们手动清理。
明确生产管线的目标
Breachers 原型制作阶段结束后,我们在2022年初开始准备进入生产阶段,美术和开发团队聚在一起讨论了怎样解决这些问题。我们讨论了理想资产管线所应具备的几个特征,以支持 Breachers 快速、灵活的迭代:
-
所有关卡模型的创建和修改都应当用Blender完成。
-
所得即所见:设计师用Blender创建的内容应当尽量在Unity中还原。
-
Blender里的更新应当自动导入Unity,无需手动完成。

让Blender和Unity相处融洽
正如上方提到的,我们的主要目标是在Blender里精确地可视化游戏——不仅要正确地反映Unity里的成果,还要反映游戏机制的建立方式。 Breachers 的游戏玩法不仅基于关卡布局,还基于动态对象(比如可打破的墙壁)和其他不可见的元素(比如音量和碰撞体)。我们希望在设计阶段就暴露出这些信息,并将其精确地搬到Unity。

图注:Blender里的摩天大楼关卡(仅包含静态关卡模型和道具)

图注:加入动态对象后的场景

图注:导入Unity后尚未烘焙光照的场景

图注:Unity里的最终版
自定义属性与Unity的AssetPostprocessor
自定义属性是整个流程的关键之一,主要在Blender里添加。这些属性会经由FBX文件导入Unity,用于查看或运行自定义逻辑。

图注:Blender中对象的自定义属性
这让流程更加灵活、稳定。这些属性会一直保留在对象身上,让我们能重新组织和命名关卡内容,不必担心造成破坏或失去同步。
Unity有一个强大的AssetPostprocessor,可以在导入时对资产做出修改。我们正是用了这个以在导入时解读自定义属性并执行操作。
用例
预制件关联
自定义属性PrefabLink会告诉Unity导入的Blender对象应当被替换成哪个已存在的预制件,同时保留模型的变换(transform)。我们可以在Blender里摆放动态对象,在导入Unity后继续使用预制件。上方Blender场景里的窗户挡板就是个很好的例子。

表面类型
关于表面的定义对 Breachers 也非常重要。行走在金属阶梯上的声音必须不同于水泥地,子弹穿过木材的声音需要不同于穿过金属。每种表面在接受冲击时会有不同的效果。在Unity里一件件地标注道具的表面类型会耗费大量时间,于是我们同样在Blender里为模型碰撞体设定相应的自定义属性。
静态标记(Static flags)
另一个重要的优化设定是Unity的静态标记。正确地设立标记可以极大地影响可见度遮挡、光照烘焙和批处理。我们用Blender的自定义属性为关卡的每一部分设好了标记,包括可重复使用的道具,并将这些信息导入到了Unity。
碰撞体
最后,我们要分享的是碰撞体的建立方式。Unity有一个简单却高效的系统能自动为带有_LOD0、_LOD1等后缀的模型检测其所处的细节级别。受此启发,我们同样为碰撞体创建了一个类似的系统:通过在名称里加入_BoxCollider或_NoCollision后缀来确定从Blender导出的模型网格是否要替换成碰撞体。

图注:注意Blender里的_BoxCollider和_NoCollision名称

图注:标记为_BoxCollider的对象会被转变为一个BoxCollider
代码示例
下方是我们LevelSetupPostprocessor的一段代码,它会读取自定义属性,并为每个导入的对象分配对应的静态标记:
public
class
LevelSetupPostprocessor
:
AssetPostprocessor
{
// 由包含自定义属性的对象组成的字典
private
readonly
Dictionary
<
string
,
(
string
[
]
,
object
[
]
)
>
_userPropertyMap
=
new
(
)
;
// 我们支持的所有自定义属性列表
private
static
readonly
string
[
]
SupportedPropNames
=
new
[
]
{
"Surface"
,
"Layer"
,
"PrefabLink"
,
"Collision"
,
"StaticFlags"
,
"LightmapScale"
,
"LightMeshPreset"
}
;
// AssetPostprocessor发出的Unity Event
// 在模型的每个对象上调用
private
void
OnPostprocessGameObjectWithUserProperties
(
GameObject
go
,
string
[
]
propNames
,
object
[
]
values
)
{
// 检查是否有相关属性,将其加入字典
if
(
SupportedPropNames
.
Select
(
x
=>
x
.
ToLowerInvariant
(
)
)
.
Intersect
(
propNames
.
Select
(
x
=>
x
.
ToLowerInvariant
(
)
)
)
.
Any
(
)
)
{
_userPropertyMap
.
Add
(
go
.
name
,
(
propNames
,
values
)
)
;
}
}
// AssetPostprocessor发出的Unity Event
private
void
OnPostprocessModel
(
GameObject
model
)
{
// 对于每一个检测到的自定义属性
// 找到对应的模型预制件变体
// 并应用相应的逻辑
for
(
int
i
=
_userPropertyMap
.
Count
-
1
;
i
>=
0
;
i
--
)
{
var
kvp
=
_userPropertyMap
.
ElementAt
(
i
)
;
GameObject
go
=
FindGameObjectInHierarchy
(
model
,
kvp
.
Key
)
;
// 根据名称查找模型子对象
string
[
]
propNames
=
kvp
.
Value
.
Item1
;
object
[
]
values
=
kvp
.
Value
.
Item2
;
for
(
int
j
=
0
;
j
<
propNames
.
Length
;
j
++
)
{
object
value
=
values
[
j
]
;
switch
(
propNames
[
j
]
)
{
case
"staticflags"
:
HandleStaticFlags
(
go
,
value
)
;
break
;
// ...
}
}
}
}
// 根据Blender的自定义属性向对象添加StaticFlag
private
void
HandleStaticFlags
(
GameObject
go
,
object
value
)
{
string
[
]
staticFlags
=
value
.
ToString
(
)
.
Split
(
','
)
;
StaticEditorFlags
activeFlags
=
0
;
for
(
int
i
=
0
;
i
<
staticFlags
.
Length
;
++
i
)
{
string
flag
=
staticFlags
[
i
]
.
ToLower
(
)
.
Trim
(
)
;
switch
(
flag
)
{
case
"batching static"
:
activeFlags
|=
StaticEditorFlags
.
BatchingStatic
;
break
;
// ...
default
:
LogWarning
(
$"Unknown static flag
{
flag
}
detected when importing
{
go
.
name
}
"
,
go
)
;
break
;
}
}
GameObjectUtility
.
SetStaticEditorFlags
(
go
,
activeFlags
)
;
}
}
为Unity定制Blender
要让所有这些流畅运行,我们还需要在Blender中做些工作。
自定义属性不容易被发现,每次还要手动输入,这种体验并不是很好。依靠手动输入也很容易出错,使得Blender流程带来的优势化为乌有。抛弃预制件工作流也让我们没法利用预制件的优点,比如建立一个对象库来浏览和挑选对象。幸好Blender也和Unity一样非常灵活、可以扩展。
Blender资产库
预制件库问题可以用Blender 3.2的资产库(Asset Libraries)解决。新系统类似于Unity的预制件系统:你可以将资产保存为单独的文件,然后导入到场景,文件上的修改也会自动反映到场景中。另外,所有自定义属性或碰撞体都会正确地应用到每个资产的实例上。

图注:我们的Blender道具库

图注:所有动态对象都带有PrefabLink自定义属性
自定义Blender插件
我们为Blender写了一个内部插件来用更清晰的界面设置自定义属性。只需选中对象、点击按钮就行,不再需要手动输入。
我们还使用了开源的Bundle Exporter插件来一键导出所有FBX文件。我们修改了插件使之能导出自定义属性,并更新了UI用于满足我们快速导出的需要。

结论
建立 Breachers 的关卡设计流程最初耗费了大量时间,但我们一直相信这是正确的选择。其实还挺好玩的!
从最初alpha测试的简单布局到几个月后的最终发布,关卡的迭代一直都非常迅速、轻松。我们移除了不少设计师与美术肩上的重担,同时也给予了他们部分程序员的权力和职责。
Unity和Blender之间流畅的整合过程也为我们留下了深刻印象,我们相信正是这种集成让 Breachers 成为我们乐于与世界分享且引以自豪的游戏。
谢谢你看到这里,祝玩得开心!

更多推荐
所有评论(0)