RayFire 是一款可以在运行时对 3D 对象进行无限次拆除或切片的插件,还可在编辑模式下对其进行预粉碎。
此外,RayFire 还可对模拟对象和拆除的片段提供高级动态模拟控制功能,比如在需要时将其激活。借助风力和旋风力,它可以实现射击、爆炸和侵袭,记录编辑器中的模拟并在运行时播放。
RayFire 需要 Unity 2018.4.34 或更高版本,请先安装支持的 Unity 编辑器。
RayFire For Unity 的优点:
RayFire For Unity 的缺点:
本教程预期阅读和操作时间:2 ~ 3 小时
RayFire 最初是 3DSMAX 中的一款用于制作破坏特效的强大插件,通过自动生成碎片模型减轻了模型师们的负担。后来,RayFire 官方推出了 Unity 插件版本,该插件不仅保留了破碎模型制作的功能,还为游戏工程师们简化了建模方面的流程,更包含了一系列高级运行时效果系统。
如果你希望发布一款拥有非常炫酷破坏场景的战争游戏,或者只是想制作一款破坏类的解压小游戏,又或是通过 Unity 来渲染一些特效视频,RayFire for Unity 就是一个值得考虑的解决方案。
好吧,口说无凭,如果这些不能打动你的话,可以看看下面这些使用到 RayFire 的成功商业案例。首先是官方宣传片——客户们的成品展示:
点击前往 Bilibili 观看
点击前往油管观看
再来一段官方教程中的演示:
点击前往 Bilibili 观看
点击前往油管观看
接下来,让我们通过一个简短的教程初步了解如何使用 RayFire for Unity 。此教程使用的 Unity Editor 版本为 2020.3.22 LTS,推荐使用 Jetbrains Rider 作为开发 IDE,或为 Visual Studio 安装 ReSharper 扩展,我们需要写非常少量的 C# 代码。
从以下途径下载教程项目:
在正式开始之前,我们需要先将 RayFire for Unity 导入场景。如果你比较懒想跳过这部分,也可以直接下载打包好的项目压缩包(有点大)。打开 Unity 包管理器,在"我的资源"中找到并导入 RayFire,或将本地已经下载的.unitypackage
文件导入:
导入全部内容,并将项目根目录下的Rayfire
文件夹移动到Vendors
文件夹中:
然后重新导入所有资源,重启 Unity Editor:
Info
:RayFire 的文档、许可证书和说明文件,如果需要在生产环境中使用,务必仔细阅读许可证书说明;Plugins
:各个平台需要的运行时资源,RayFire for Unity 支持 Windows 、Mac OS X 、Linux 、Android 、iOS 以及 Web 平台,运行时计算功能暂时不支持 Web;Scripts
:核心脚本文件;Tutorial
:官方教程场景,如果需要将插件用于生产环境,强烈建议跟随官方教程视频全部练习一遍。RayFire for Unity 离线资源下载,仅供学习交流!请支持原作者,前往 Unity Assets Store 购买最新版的在线版许可。
据我国《计算机软件保护条例》第十七条规定:“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”
在 3DSMAX 中,我们可以使用 RayFire 为物体自动生成破碎模型,然后打包成 FBX 格式为 Unity 使用。使用 RayFire for Unity 同样可以 Unity Editor 中生成我们需要的破碎素材。
打开/Scenes/01
场景,我们已经在场景中放置了一颗不起眼的手榴弹对象RGD-5
,到目前为止它还没有任何危险性,模型也是“铁板一块”的:
观察预制件RGD-5
的层级,可以发现其由弹体、保险环、保险杆组成,我们需要对弹体进行切片以模拟效果。
使用RayFire Shatter
组件进行分离处理。选中对象RGD-5
,打开检查器,选择添加组件,找到RayFire->RayFire Shatter
,添加:
现在看到的组件控制面板,可以发现有一组令人头大的配置参数,这些参数对应了每种碎片生成类型。由于只是入门教程,这里不会细讲所有类型和其参数,官方教程中很详细地讲解了这些类型和参数的配置。
点击Fragment
按钮即可进行切片,注意RayFire Shatter
只会对所在对象切片,而不会影响子组件。RayFire 会在切片对象所在的层级中新生成一个名为RGD-5_roots
的新对象,这个对象下包含了所有的碎片:
回到RGD-5
对象上,此时编辑器面板中的碎片并没有显示出来。可以看到,在切片之后组件控制面板中多出了一些控件,拉伸Scale View
滑动条,RayFire 会关闭切片前对象的网格渲染,并对碎片对象进行缩放,我们可以看到切片后的效果:
点击Delete Last
可以删除上一次切片结果,即删除生成的碎片游戏对象。
如果觉得切片后的模型内部颜色比较奇怪,可以在Material
参数中设置切片内侧的材质:
对同一对象重复切片将产生同样的结果,要产生不同的结果,在Advance
参数项中修改随机种子:
下面是每种切片类型的简单预览,标题链接到对应的教程视频。每项切片参数都有英文悬浮注释,文档中的解释也不过如此(文档真的不太行,还是推荐看视频)。
Voronoi(泰森多边形):对于比较随意的切片可以使用此类型。参数Amount
决定生成的碎片数量,Center Bias
决定了模型中心碎片的大小:
Splinters(尖碎片):参数Axis
可以指定切片方向:
Custom(自定义):此类型的使用建议参考视频。
Tets(四面体):此模式在默认Density
(密度)参数为 7 时会产生大量碎片,建议调低该参数。
Slices(片):此类型的使用建议参考视频。
Decompose(解体):解体切片比较实用,可以很方便将整体模型分开,然后对分离的各部分再次切片,在对象只有单一网格时无效。
为了能够重复使用我们的劳动成果,切片后的结果应该被保存为预制件,这样就不需要每次都为同样的物体制作切片了。RayFire for Unity 支持使用RayFire Rigid
组件进行运行时的碎片生成,但是这么做会带来比较大的内存和运算开销,最好的做法是直接保存RayFire Shatter
生成的静态资源。
在 Unity 中,预制体在编辑器和游戏中实例化的过程涉及数据的反序列化。RayFire Shatter
组件生成的碎片对象的顶点(即 Mesh 文件中的信息)被直接绑定在对象上,这些碎片的网格信息对应的信息可以在单个场景中存在,但并不会在游戏对象生成预制件时被序列化保存。如果直接将碎片序列化为预制件,下一次反序列化预制件时,顶点数据将全部丢失。
因此,我们需要手动保存这些顶点数据。RayFire 的作者推荐两种保存数据的方法:导出为 FBX 文件或单独导出 Mesh 。
要导出为 FBX 文件,首先需要安装 Unity 官方提供的工具 FBX Exporter 。打开 Unity 包管理器,选择 Unity 注册表,找到 FBX Exporter 并安装导入:
右键RayFire Shatter
生成的游戏对象,点击Export To FBX
:
如果没有特殊需求,直接导出:
可以看到碎片模型已经被成功导出:
FBX 方案的主要问题在于,导出后的碎片模型和原模型不能共享素材,这会导致占用过多的空间并产生信息冗余,因此一般会推荐使用第二种方案:单独导出 Mesh 。
要单独导出网格中的顶点信息,选择原模型的RayFire Shatter
组件,找到Export to asset
一栏。我们可以为导出的 Mesh 文件确定后缀,默认情况下导出的 Mesh 文件名称为对象名_frags
。为了方便管理素材,我们将已经切片的模型放置在ShatteredObjects
文件夹中,预制件存放于ShatteredObjects/Prefabs
,网格文件存放于ShatteredObjects/Meshs
:
RayFire 会自动地为对象链接资源,现在我们可以放心地将对象导出为预制件了,将层级面板中的RGD-5_roots
拖放到资源面板中的ShatteredObjects/Prefabs
文件夹中:
由于之前处理的只是手榴弹的弹体,最后导出的碎片模型和作为子对象的保险杆以及保险环分离了。建立一个空对象,把弹体、引信和拉环装配在一起:
保险的位置微调比较麻烦,我们可以右键原对象的Transform
组件直接复制位置:
保存为预制件ShatteredObjects/Prefabs/RGD-5-Shattered
:
我们已经制作了带有碎片的手榴弹模型,但也仅仅是个模型,没有物理效果的手榴弹连砸死人都做不到。现在,为手榴弹预制件添加物理效果。RayFire 为我们提供集成了 Unity 原生Rigid
刚体组件特性的RayFire Rigid
组件,选中RGD-5-Shattered
对象,为其添加该组件:
又是一大堆令人头疼的参数,官方教程视频详细地讲解了各个部分,这里仅做一些简单的介绍。
RayFire Rigid
组件为对象添加物理效果,并控制破碎的生效。
下面介绍一些常用的配置参数:
Initialization:刚体初始化的方式,共有两种:
RayFire Rigid
的功能只有在初始化后才会生效,未激活前的对象在场景中不会有任何物理效果。
Simulation Type:对象物理模拟的方式,总共有五种,官方教程演示了各种效果:Bilibili,油管。可以在 RayFire 的教程目录中找到介绍该属性的场景:
Activation
项中设置。适合一些建筑破坏场景中的“藕断丝连”效果;Inactive
导致模型嵌套的对象发生鬼畜的情况。Object Type:对象类型将决定刚体组件如何生效,官方教程演示了各种效果:Bilibili,油管。有以下类型:
RayFire Rigid
组件,如果是 Mesh,则要手动添加;RayFire Rigid
组件;Physic:决定对象的物理属性:
Fading:决定对象在被破坏之后如何消失:
On Demolition
只有被破坏的部分会消失,On Activation
所有部分都会消失;By Life Time
表示对象一旦破坏就开始计时,By Simulation And Life Time
在对象不再与外界发生物理交互后消失;RayFire Man
Manager 组件用于设置一组全局参数,这些参数将影响到场景中的所有物体。我们可以创建一个空对象RayFireMan
作为游戏逻辑控制机,然后将RayFire Man
组件挂载到它上面:
在Material Presets
一栏中,可以看到一些材料预设。金属材料是无法被外力破坏的,但是可以被脚本破坏,我们可以利用这一点。
现在考虑手榴弹有哪些特征:
根据这些特征,设置RayFire Rigid
的参数:
By Method
。在实际游戏中,手榴弹在扔出去之前应该无视物理效应跟随玩家手臂移动,虽然在该教程中我们并不会这么做;Dynamic
;Nested Cluster
;Runtime
,对象才会破坏;Light Metal
默认不能被外力破坏,也符合现实中手榴弹的材质。由于我们将 Object Type 设置为了Nested Cluster
,在运行前最好先进行初始化,点击Setup Cluster
,出现连接状态即可。如果没有手动设置,对象在实例化时 RayFire 也会自动设置连接,但这会额外消耗一些运行时资源:
注意:如果对象下的层级发生变化,需要重置 Cluster ,否则 RayFire 会给出警告。
运行场景,可以看到手榴弹悬浮在空中,并没有被激活。点击RayFire Rigid
面板中的Initialize
按钮手动激活,可以看到手榴弹受重力影响掉落,但是在碰撞时没有被破坏;点击Demolish
按钮,观看效果,可以发现保险装置脱出;找到RGD-5_roots
对象,点击Demolish
按钮,可以看到弹体破碎了:
现在,我们的玩具离成为真正的手榴弹只差最后一步:添加真正的爆炸效果。RayFire 很贴心地准备了RayFire Bomb
、RayFire Sound
和RayFire Dust
组件,只需一些简单的步骤,外加最少的脚本,就可以让手榴弹同时具备爆炸、烟尘、火光和声音效果。
打开场景03
,选中RGD-5-Shattered
对象,找到并添加RayFire Dust
组件:
Emit Dust
项决定在什么时候产生烟尘效果,分别是在破坏时、激活时或受到外力影响时。在Dust Materials
项中,可以指定多种烟尘材质,这里使用 RayFire 教程场景中的材质dust_1_m
、dust_2_m
和dust_3_m
:
现在把该组件删除,因为我们之后要引入一个带有更好效果的爆炸特效。在处理环境对象时会再次用到烟尘。
选中RGD-5_roots
对象,找到并添加RayFire Sound
组件:
可以看到在初始化时、激活时、破坏时均可添加音效。现在为手榴弹弹体添加一组爆炸音效,这些音效会随机播放,音效文件可以在AudioClips
文件夹中找到:
RayFire Bomb
组件模拟爆炸产生的冲击波和冲击影响。在预制件编辑面板中为RGD-5_roots
添加该组件:
可以在检查器控制面板中设置炸弹的参数,并添加音效。因为该组件只能添加一种音效,选择之前使用RayFire Sound
组件为弹体在破坏时添加一组随机音效。
为了在之后模拟爆炸冲击波损伤效果,将Apply Damage
勾选,设置Damage Value
为100
:
我们使用Stylized Explosion Pack中提供的内容为手榴弹添加爆炸效果。在Prefabs/Explosion/
下有三个爆炸效果预制件,每个预制件都带有一个DestroyMe
脚本,可以设置其播放后销毁时间:
手榴弹仅靠这些组件还无法正常运作。接下来,用一些“胶水”代码实现我们的爆炸效果。
手榴弹爆炸的过程包括几个步骤:
对应的,我们需要两个类:
Grenade
类:负责弹开保险,并通知引信点火,相当于手榴弹对象;Bomb
类:通用的炸药类,可以点火、设置引信长度、引爆自身。现在创建两个脚本Grenade
和Bomb
,将它们分别挂载到RGD-5-Shattered
对象和RGD-5_roots
对象上,并指定需要的引用。
Grenade
:
using UnityEngine;
using RayFire;
namespace Scripts
{
public class Grenade : MonoBehaviour
{
public RayfireRigid grenade; // 手榴弹对象
public Bomb bomb; // 炸弹脚本
public float fuseTime; // 引信时间(秒)
// 按 G 松开保险
private void DebugIgnite()
{
print("[Debug] Ignite a grenade!");
Ignite();
}
// 通知炸药点火
private void Ignite()
{
grenade.Initialize();
grenade.Demolish();
bomb.Ignite(fuseTime);
}
private void Update()
{
if (Input.GetKey(KeyCode.G)) DebugIgnite();
}
}
}
Bomb
:
using System.Collections;
using RayFire;
using UnityEngine;
namespace Scripts
{
public class Bomb : MonoBehaviour
{
public GameObject bombBody; // 弹体对象
public RayfireBomb rayfireBomb; // 需要引爆的 RayfireBomb
public GameObject explosion; // 爆炸效果对象
// 手榴弹点火
public void Ignite(float fuseTime)
{
StartCoroutine(ExplodeCoroutine(fuseTime)); // 引信点火
}
private IEnumerator ExplodeCoroutine(float fuseTime)
{
yield return new WaitForSeconds(fuseTime); // 引信时间
Explode(); // 爆炸
}
private void Explode()
{
bombBody.GetComponent<RayfireRigid>().Demolish(); // 弹体破碎
rayfireBomb.Explode(0); // 产生冲击波
var exp = Instantiate(explosion);
exp.transform.position = transform.position; // 设置位置
}
}
}
RayFire 内部实现了一个事件系统,我们可以使用脚本为每个事件添加一组事件处理程序(钩子)。以爆炸和破坏事件为例,RayFire 提供两个类RFDemolitionEvent
和RFExplosionEvent
用于注册这些事件。这些事件可以用于实现游戏逻辑,并方便我们调试程序。
要添加事件处理程序,在两个类的GlobalEvent
事件上使用+=
操作符注册委托(C# 的类型安全回调函数)。创建一个EventManager
脚本和对象来管理场景中的事件:
using UnityEngine;
using RayFire;
namespace Scripts
{
public class EventManager : MonoBehaviour
{
// 注册事件
private void Awake()
{
// Debug:当对象别破坏时通知
RFDemolitionEvent.GlobalEvent += (RayfireRigid rigid) =>
print("[Debug] Game Object " + rigid.gameObject.name + " is destroyed!");
// Debug:当炸弹爆炸时通知
RFExplosionEvent.GlobalEvent += (RayfireBomb bomb) =>
print("[Debug] Game Object " + bomb.gameObject.name + " is exploded!");
}
}
}
观看官方教程获取更详细的事件说明。
运行场景,按下键盘G
键,等待 3 秒,Boom!:
现在,我们的小玩具真正成为了大杀器。多亏了 RayFire,需要的代码甚至都没有超过 100 行。
只有一个迷你手榴弹爆炸的场景实在是太逊了,那些细小的弹体碎片都给崩的没影了,完全没有办法体现 RayFire 的强大。接下来,为场景中的环境对象制作碎片模型并应用,练习一下整个过程,然后炸掉我们的劳动成果。记得保存为预制件!别真的一下全炸没了。
在使用 RayFire 制作切片前,我们需要将所有导入资源的访问限制设置为可读。找到/Models/House/House.fbx
,在检查器中设置读写已启用:
模型来自免费的第三方资源Low Poly Ultimate Home Pack,这个作者的游戏对象命名比较烂,可能是因为对象实在是太多了…… 场景04
中原始素材中的对象已经被重新整理层级和排列,找到目标物体对象会更容易些。
进入场景04
,找到Environment
对象下的Light_Poll
,完全解压缩预制件:
选中Cylinder003
,Box1340
和Box1341
,这是我们需要切片的对象。使用之前的知识处理它们(让碎片数量少一些,不然太吃性能):
导出 Mesh,保存到ShatteredObjects/Mesh/LightPoll
中。删除原物体,保留新的碎片根对象,然后在为Light_Poll
添加RayFire Rigid
并设置参数。我们希望电线杆在受到外力损伤程度一定时先解体,部件在二次撞击后破碎:
注意激活伤害检测:
对于比较脆弱的物体,可以考虑将碰撞也纳入伤害计算范围:
将处理后的路灯保存为预制件,存放于ShatteredObject/Prefabs
中。删除原场景中的路灯并用切片后的替换。
选中Pavings
,完全解压预制件,然后按照你的喜好设置Paving
和Paving_Under_House
的切片参数并切片。要求的效果是受到爆炸冲击波影响的碎片部分脱离,选择合适的参数:
重复预制件制作步骤。由于一部分人行道的模型和其他对象模型重叠,激活后可能会发生比较鬼畜的情况。
选择Tree
对象,解压预制件。可以看到该对象由一些小块模型组成,因此无需二次处理,我们只需要调整层级顺序如下,然后添加RayFire Rigid
组件并设置参数即可:
记得激活伤害检测。重复预制件制作步骤,并删除未处理的对象,使用处理后的对象代替。
找到Fence
对象,解压预制件,选择层级下的另一个Fence
。可以看到该对象同样无需切片处理,为其添加RayFire Rigid
组件并设置参数:
然后保存为预制件。
找到Plants
对象,解压预制件,该对象同样无需处理,添加RayFire Rigid
组件并设置参数:
然后保存为预制件。
找到House
对象,解压预制件,选择Body
房体、Roof
阁楼以及Window1
和Window2
两块玻璃,对它们进行切片并保存 Mesh :
可以发现对Window1
和Window2
切片时出现异常,这是因为 RayFire For Unity 无法很好地处理一些薄片物体,建议把这些物体的切片任务交给 3DSMAX 中的 RayFire 插件。删除这两个对象,或者保留它们(破坏时会显得很奇怪):
对于Roof
和Body
,建议产生较少碎片,或者干脆不处理,因为House
对象上的子对象已经足够多了,能让你的显卡渲染不过来。如果还是要进行切片,可以设置Center Bias
让更大的碎片集中于中心和底部以显得更真实:
保存 Mesh 到ShatteredObjects/Mesh/House
,删除原对象,将碎片至于House
层级下,然后设置RayFire Rigid
:
为刚才处理的所有预制件添加RayFire Sound
和RayFire Dust
组件,并指定效果素材。你可以在AudioClips
中找到一些用于建筑effect_rock
和树木effect_wood
的音效;烟尘粒子可以使用 RayFire 教程中的dust_m
材质,更改一下颜色即可(我选择的是棕色)。
现在我们已经处理完了所有环境对象,是时候来点大爆炸了。
之前的手榴弹太小了,我们需要把它放大!暂时禁用EventManager
对象以关闭调试控制台输出,设置RGD-5_roots
的RayFire Bomb
的属性:
设置大型爆炸的音效:
最后加上慢动作脚本,新建一个SlowMotion
空对象并挂载该脚本:
using UnityEngine;
public class SlowMotion : MonoBehaviour
{
public float slowMotionScale = 0.1f;
private void Awake()
{
Time.timeScale *= slowMotionScale;
Time.fixedDeltaTime *= slowMotionScale;
}
}
如果你不想等待太久的话,记得更改引信时间。
运行场景,按下G
,以一次华丽的大爆炸结束本次教程吧。
如果你在哪步中出了问题而没法解决,可以Finished
目录中找到已完成的Demo
场景进行对照。需要注意的是,如果场景需要的运算量过大,可能导致 RayFire 出现不一致的行为。