Unity ECS+Jobs System笔记 案例解析1(十)

本章节为大家解析ECS案例,资源来自,大家自行获取:
https://github.com/Unity-Technologies/EntityComponentSystemSamples

ECS案例解析

1、ForEach——ECS入门案例

Unity ECS+Jobs System笔记 案例解析1(十)_第1张图片
在该案例中有两个组件,一个是RotationSpeedSystem_ForEach,还有一个是ConvertToEntity

1.1、ConvertToEntity——Entity

这个组件的作用就是:将Inspector面板的GameObject从根节点开始,将整个GameObject(包括子物体)全部转成Entity,并且将所有现有的、能转化的Component转换成ECS的Component

在ConvertToEntity代码中,执行了GameObjectConversionUtility.Convert()函数,而"能转化"的四亿是只有当该组件实现了接口IConvertGameObjectToEntity的Convert()函数,才会根据Convert()函数产生作用

最后把原本的GameObject销毁掉

1.2、RotationSpeedAuthoring_ForEach——Convert

using System;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[RequiresEntityConversion]
public class RotationSpeedAuthoring_ForEach : MonoBehaviour, IConvertGameObjectToEntity
{
    public float DegreesPerSecond;
    //那么这里就是实现了1.1中的Convert函数,在这个Convert函数中
    //将MonoBehaviour的参数作为RotationSpeed_ForEach组件的参数传入
    //并且从角度制变为弧度制
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var data = new RotationSpeed_ForEach { RadiansPerSecond = math.radians(DegreesPerSecond) };
        dstManager.AddComponentData(entity, data);
    }
}

1.3、RotationSpeed_ForEach——Component

ECS中的组件继承IComponentData,是struct类型,而在这个结构体中只保存了数据

using System;
using Unity.Entities;
// Serializable属性是为了支持编辑器
[Serializable]
//记住IComponentData是struct类型
public struct RotationSpeed_ForEach : IComponentData//首先要继承IComponentData
{
    public float RadiansPerSecond;//组件中的数据
}

1.4、RotationSpeedSystem_ForEach——System

这部分内容,我已经在我的ECS(七)中介绍过了,这里使用ComponentSystem处理数据,ComponentSystem在主线程上运行,因此不利用多个CPU内核,一般而言不推荐使用
ComponentSystem提供了Entities.ForEach函数,在系统的OnUpdate()函数中调用ForEach,传入一个lambda函数,该函数会将相关组件作为参数并执行必要的工作
再次就是为具有RotationQuaternion和RotationSpeed组件的实体设置旋转动画

using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
//这个系统会更新场景中所有RotationSpeed_ForEach和Rotation组件的实体
public class RotationSpeedSystem_ForEach : ComponentSystem
{
    protected override void OnUpdate()
    {
        // Entities.ForEach会在主线程中运行(没有用到Job),但是为了更好的性能,不推荐这么使用
        // 这边只是作为一个案例为大家介绍ECS,并且能够快速入门
        Entities.ForEach((ref RotationSpeed_ForEach rotationSpeed, ref Rotation rotation) =>
        {
            var deltaTime = Time.deltaTime;
            rotation.Value = math.mul(math.normalize(rotation.Value),
                quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
        });
    }
}

1.5、小结

那么我们总结一下最简单的ECS使用步骤:

  1. 参考1.1为你需要转换的ECS实体挂上ConvertToEntity脚本,它会自动把能转换的GameObject Component转换为Entity Component
  2. 如果想要自己的组件,就参考1.2实现Convert接口
    2.1 组件的写法参考1.3
  3. 参考1.4通过最简单的ComponentSystem来遍历所有实体,找出符合要求的实体,然后对它们进行更新
  4. 在Foreach中加入你想要的更新函数

那么到此为止,我们就学会了最基本也是最简单的ECS的用法
Unity ECS+Jobs System笔记 案例解析1(十)_第2张图片


2、ForEachWithEntityChanges——Prefabs To Entity

为了看上去方便,我们将代码分隔开

    public class Spawner_ForEachWithEntityChanges : MonoBehaviour
    {
        public GameObject Prefab;
        public int CountX = 100;
        public int CountY = 100;

这里都是基本写法

        void Start()
        {
            Entity prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, World.Active);
            EntityManager entityManager = World.Active.EntityManager;

在这一步,我们做了两件事:

  1. 获取当前World(默认World)中的EntityManager
  2. 使用GameObjectConversionUtility.ConvertGameObjectHierarchy函数将Prefab转化成Entity
            for (int x = 0; x < CountX; x++)
            {
                for (int y = 0; y < CountX; y++)
                {
                	Entity instance = entityManager.Instantiate(prefab);

                    float3 position = transform.TransformPoint(new float3(x - CountX/2, noise.cnoise(new float2(x, y) * 0.21F) * 10, y - CountY/2));
                    entityManager.SetComponentData(instance, new Translation(){ Value = position });
                    entityManager.AddComponentData(instance, new MoveUp_ForEachWithEntityChanges());
                    entityManager.AddComponentData(instance, new MovingCube_ForEachWithEntityChanges());
                }
            }
        }
    }

那么最后就是利用已经转化成Entity的prefab为原型来创建更多的Entity
position的位置是随机生成的,然后我们通过SetComponentData来修改Translation(类似原本的Transform)该Entity的位置
然后通过AddComponentData为Entity添加MoveUp_ForEachWithEntityChanges和MovingCube_ForEachWithEntityChanges这两个自定义的组件

这两个组件为空,它们只是作为Tag方便后续的处理

2.1、ComponentSystem解析

为了看上去方便,我们将代码分隔开

public class MovementSystem_ForEachWithEntityChanges : ComponentSystem
    {
        protected override void OnUpdate()
        {

同样在这里用到的是ComponentSystem,用法已经在上面说过了

            Entities.WithAllReadOnly<MovingCube_ForEachWithEntityChanges, MoveUp_ForEachWithEntityChanges>().ForEach(
                (Entity id, ref Translation translation) =>
                {
                    var deltaTime = Time.deltaTime;
                    translation = new Translation()
                    {
                        Value = new float3(translation.Value.x, translation.Value.y + deltaTime, translation.Value.z)
                    };

                    if (translation.Value.y > 10.0f)
                        EntityManager.RemoveComponent<MoveUp_ForEachWithEntityChanges>(id);
                }
            );

与之前不同的是,这次我们使用了WithAllReadOnly函数来获取具有组件的实体——显而易见,这两个组件中没有数据,因此只读相较于读/写可以更加节省性能(还有其他的优势,请参考之前的文章)
那么,现在我们已经获取到了所有有这两个组件(MovingCube和MoveUp) 的实体,然后让他们上升,直到高度大于10之后,就移除MoveUp_ForEachWithEntityChanges组件

            Entities.WithAllReadOnly<MovingCube_ForEachWithEntityChanges>().WithNone<MoveUp_ForEachWithEntityChanges>().ForEach(
                (Entity id, ref Translation translation) =>
                {
                    translation = new Translation()
                    {
                        Value = new float3(translation.Value.x, -10.0f, translation.Value.z)
                    };

                    EntityManager.AddComponentData(id, new MoveUp_ForEachWithEntityChanges());
                }
            );

那么在下面这个遍历中,我们获得了有MovingCube_ForEachWithEntityChanges组件,但是没有MoveUp_ForEachWithEntityChanges组件的实体——由于上一次循环,高度大于10的实体的该组件都被益处了——因此,在这个循环中,我们需要为这些组件重新设置高度,然后重新为它们添加MoveUp_ForEachWithEntityChanges组件,那么在下个循环,它们又能被上面那个循环遍历到,继续向上移动

        }
    }

效果如下

2.2、小结

在这个案例中,我们可以将Prefab转化成Entity,然后将其作为原型生成更多的Entity,具体步骤如下:

  1. 创建或获取到一个EntityManager(用于管理Entity)
  2. 通过GameObjectConversionUtility.ConvertGameObjectHierarchy将Prefab转化成Entity
  3. 为Entity设置或添加组件(也可以和案例1中的方式一样,通过实现IConvertGameObjectToEntity接口来添加或设置组件)

你可能感兴趣的:(Unity笔记)