Unity中实体组件系统ECS,Job System和Burst编译器,这些新工具的特点是让性能得到极高的提升。本文将由法国的开发工程师Léonardo Montes为我们分享如何将项目移植到ECS和Job system。
当我在巴黎独立游戏工作室Atomic Raccoon工作时,我希望了解如何在运行时模拟多个角色像流体一样互相交互。
Unity中实体组件系统ECS,Job System和Burst编译器,这些新工具的特点是拥有极快的速度,而且性能提升效果高达100倍。于是我向团队建议使用这些新工具来实现流体模拟,这将允许我们通过制作小型物理引擎的原型来熟悉新的编码方式。
我首先研究可以尝试实现的物理模拟效果,然后找到了软粒子流体力学SPH算法。本文我将介绍如何在Unity实现SPH算法,以及如何将项目移植到ECS和Job system。
学习准备
本文提供项目的源代码下载:
https://github.com/leonardo-montes/Unity-ECS-Job-System-SPH
进行开发前,我仔细观察了Unity的Boid演示项目,该示例展示了很多条鱼相交互的效果。下面是Unite大会中关于ECS和Job System的演讲。
单线程实现
我使用“老方法”实现了SPH算法,也就是使用Monobehaviours。
首先,我调用InitSPH() 来创建粒子的游戏对象,并初始化它们的属性,并给位置和引用设置了正确参数,以便为模拟使用,这将让我得到一组粘性较大的粒子。
然后,我在每一帧调用ComputeDensityPressure()、ComputeForces()和Integrate(),它们会处理粒子与粒子间的碰撞。
我添加了二个方法:ComputeColliders()和ApplyPosition()。ComputeColliders()可以处理粒子与标记为“SPHCollider”的墙体游戏对象之间的碰撞,ApplyPosition()方法用于将粒子游戏对象的位置设为和粒子位置相同。
void Start()
{
InitSPH();
}
void Update()
{
ComputeDensityPressure();
ComputeForces();
Integrate();
ComputeColliders();
ApplyPosition();
}
这样就完成了,效果如下图所示。
250个粒子
使用ECS和Job System实现
下面我们将使用ECS和Job System来实现流体模拟。
现在我们要改变思考方式,我们需要使用多个脚本,而不是只用一个脚本来初始化并更新粒子及其游戏对象。
初始化
首先,我将创建了一个SPHManager.cs脚本,它将处理新粒子和墙体的初始化过程。
void Start()
{
//导入
manager = World.Active.GetOrCreateManager();
// 设置
AddColliders();
AddParticles(amount);
}
在单线程版本中,我可以将游戏对象定义为碰撞体,然后运行碰撞求解算法。但在这里,我需要将粒子转为由系统进行处理实体。
我将粒子加入到NativeArray中,然后使用一个游戏对象预制件来实例化实体,该预制件将用作每个实体使用的所有组件的模版。
基于被定义为碰撞体的游戏对象,循环处理了所有实体来设置数值。
void AddColliders()
{
// 找到所有碰撞体
GameObject[] colliders = GameObject.FindGameObjectsWithTag("SPHCollider");
// 将它们转换为实体
NativeArray entities = new NativeArray(colliders.Length, Allocator.Temp);
manager.Instantiate(sphColliderPrefab, entities);
// 设置数据
for (int i = 0; i < colliders.Length; i++)
{
manager.SetComponentData(entities[i], new SPHCollider
{
position = colliders[i].transform.position,
right = colliders[i].transform.right,
up = colliders[i].transform.up,
scale = new float2(colliders[i].transform.localScale.x / 2f, colliders[i].transform.localScale.y / 2f)
});
}
// 完成
entities.Dispose();
}
sphColliderPrefab由二个组件构成:GameObjectEntity 组件和SPHCollider组件。
自定义组件保存了执行墙体碰撞后所需的数据,代码和单线程版本相同,只不过我使用了新的数学库和float3类型,而不是Vector3类型。ComponentDataWrapper部分允许组件在检视窗口添加给游戏对象。
using Unity.Entities;
using Unity.Mathematics;
[System.Serializable]
public struct SPHCollider : IComponentData
{
public float3 position;
public float3 right;
public float3 up;
public float2 scale;
}
public class SPHColliderComponent : ComponentDataWrapper { }
现在对粒子做同样的处理,我循环处理了所有粒子来设置它们的位置。
void AddParticles(int _amount)
{
NativeArray entities = new NativeArray(_amount, Allocator.Temp);
manager.Instantiate(sphParticlePrefab, entities);
for (int i = 0; i < _amo