- 官方ECS进阶案例解析之大群
- 开始之前的准备工作:
- ECS进阶:Boids
- 小结
- DOTS 逻辑图表
- 更新计划
- 作者的话
- ECS系列目录
- ECS官方示例1:ForEach
- ECS官方案例2:IJobForEach
- ECS官方案例3:IJobChunk
- ECS官方案例4:SubScene
- ECS官方案例5:SpawnFromMonoBehaviour
- ECS官方案例6:SpawnFromEntity
- ECS官方案例7:SpawnAndRemove
- ECS进阶:FixedTimestepWorkaround
- ECS进阶:Boids
- ECS进阶:场景切换器
- ECS进阶:MegaCity0
- ECS进阶:MegaCity1
- UnityMMO资源整合&服务器部署
- UnityMMO选人流程
- UnityMMO主世界
0下载Unity编辑器(2019.1.0f1 or 更新的版本),if(已经下载了)continue; 1下载官方案例,打开Git Shell输入: git clone https://github.com/Unity-Technologies/EntityComponentSystemSamples.git --recurse
or 点击Unity官方ECS示例下载代码 if(已经下载了)continue; 2用Unity Hub打开官方的项目:ECSSamples 3在Assets目录下找到Advanced/Boids,并打开其下的BoidExample场景
Boids其实是类鸟群的意思,在咱们IT领域叫做“集群模拟算法”,但我是个漫威粉,所以称之大群,LOL。 如上图所示,这个案例向我们展示了大量(50000)实体的风采,虽然鲨鱼的模型不怎么精致,但是大群巍巍壮观,这才是ECS的真正用法吧!下面一起来看看这样震撼的场面是如何实现的吧:
///
/// E:把大群的数据传递给C
///
[RequiresEntityConversion]
public class Boid : MonoBehaviour, IConvertGameObjectToEntity
{
public float CellRadius;
public float SeparationWeight;
public float AlignmentWeight;
public float TargetWeight;
public float ObstacleAversionDistance;
public float MoveSpeed;
// Lets you convert the editor data representation to the entity optimal runtime representation
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddSharedComponentData(entity, new Samples.Boids.Boid
{
CellRadius = CellRadius,
SeparationWeight = SeparationWeight,
AlignmentWeight = AlignmentWeight,
TargetWeight = TargetWeight,
ObstacleAversionDistance = ObstacleAversionDistance,
MoveSpeed = MoveSpeed
});
}
}
以上是大群的实体Entity(以后注释统一用E缩略,表明其身份)部分,E将数据传递给C保存,下面我们一起看C:
///
/// C:大群的共享数据
///
[Serializable]
[WriteGroup(typeof(LocalToWorld))]
public struct Boid : ISharedComponentData
{
///
/// 单元半径
///
public float CellRadius;
///
/// 间隔宽度
///
public float SeparationWeight;
///
/// 对齐宽度
///
public float AlignmentWeight;
///
/// 目标宽度
///
public float TargetWeight;
///
/// 排斥距离
///
public float ObstacleAversionDistance;
///
/// 移动速度
///
public float MoveSpeed;
}
C(Component组件,以后简写C,表明身份即可)和以前不一样了,C实现了空接口 ISharedComponentData,唯一目的只是为了表明其储存的数据是共享的,一直以来C都是最简单的脚本。
下面一起来看看最复杂的S(System系统,今后简写S),大群系统BoidSystem。 由于大群系统比较复杂,因此我画了一副图来描摹几个概念,如图: 解释下脚本中用到的几个定语: [RequireComponentTag(typeof(组件))]:这里的组件是指C [BurstCompile]:Burst编译,编译速度更快 [ReadOnly]:只读的方式访问,速度更快 [DeallocateOnJobCompletion]:在任务完成的时候释放内存(解除分配内存)
///
/// 麦克在GDC上面的讲话‘面向数据的方式来使用组件系统’对于开发大群的案例代码来说有非常大的参考价值
/// https://youtu.be/p65Yt20pw0g?t=1446 (这是油管链接,需要翻墙观看,有兴趣的朋友可以在参考)
/// 它解释了这个案例之前版本的实现,但是几乎所有的信息仍然具有参考价值。
/// 目标(2条红鱼)和对手(1条鲨鱼)基于Unity UI里的ActorAnimation(角色动画)栏移动
/// 这样它们才能基于关键帧动画来移动
///
namespace Samples.Boids
{
///
/// S:大群系统,在模拟系统组中更新,在Transform系统组之前更新
///
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
public class BoidSystem : JobComponentSystem
{
private EntityQuery m_BoidQuery;//大群实体查询缓存
private EntityQuery m_TargetQuery;//目标实体查询缓存
private EntityQuery m_ObstacleQuery;//对手实体查询缓存
///
/// 在这个案例中总共有3个独特的大群变体,各有各的共享组件值
/// (提示:这包含了在索引0上默认未初始化的值,该值并未被实际用在这个案例中,)
///
private List m_UniqueTypes = new List(3);
private List m_PrevFrameHashmaps = new List();
///
/// `CopyPositions`和`CopyHeadings`都是为了提取相对位置、导向组件到NativeArrays(原生数组)
/// 这样它们才能被`MergeCells`(合并单元)和`Steer`(导航)任务随机访问到 (下面是这两个结构体)
///
[BurstCompile]
struct CopyPositions : IJobForEachWithEntity
{
///
/// 位置,原生数组
///
public NativeArray positions;
///
/// 把本地位置放到原生数组里
///
/// 实体
/// 索引
/// 只读本地位置
public void Execute(Entity entity, int index, [ReadOnly]ref LocalToWorld localToWorld)
{
positions[index] = localToWorld.Position;
}
}
//同上
[BurstCompile]
struct CopyHeadings : IJobForEachWithEntity
{
public NativeArray headings;
public void Execute(Entity entity, int index, [ReadOnly]ref LocalToWorld localToWorld)
{
headings[index] = localToWorld.Forward;
}
}
///
/// 生成一个哈希表,每一栏里包含了所有大群的索引,其位置量化成同一个值,即提供的单元半径
/// 这样它们才能被`MergeCells`(合并单元)和`Steer`(导航)任务随机访问到
///
[BurstCompile]
[RequireComponentTag(typeof(Boid))]
struct HashPositions : IJobForEachWithEntity
{
public NativeMultiHashMap.ParallelWriter hashMap;
public float cellRadius;
public void Execute(Entity entity, int index, [ReadOnly]ref LocalToWorld localToWorld)
{
var hash = (int)math.hash(new int3(math.floor(localToWorld.Position / cellRadius)));
hashMap.Add(hash, index);
}
}
///
/// 合并所有单元
/// 这里收集了所有单元大群的位置和导向,为了做以下三件事情:
/// 1.计算每个单元的数量
/// 2.找到最近的对手并将互相标记为目标
/// 3.找到包含已收集每个大群单元值的数组索引
///
[BurstCompile]
struct MergeCells : IJobNativeMultiHashMapMergedSharedKeyIndices
{
public NativeArray cellIndices;//单元索引原生数组
public NativeArray cellAlignment;//单元对齐原生数组
public NativeArray cellSeparation;//单元间隔
public NativeArray cellObstaclePositionIndex;//单元对手位置索引
public NativeArray cellObstacleDistance;//单元对手距离
public NativeArray cellTargetPositionIndex;//单元目标位置索引
public NativeArray cellCount;//单元数量
[ReadOnly] public NativeArray targetPositions;//目标位置
[ReadOnly] public NativeArray obstaclePositions;//对手位置
///
/// 循环数组找到最近的位置
///
/// 目标
/// 位置
/// 最近位置索引
/// 最近距离
void NearestPosition(NativeArray targets, float3 position, out int nearestPositionIndex, out float nearestDistance )
{
nearestPositionIndex = 0;
nearestDistance = math.lengthsq(position-targets[0]);
for (int i = 1; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?