笨木头  2019-12-05 14:27     ECS入门     阅读(9023)     评论(6)
转载请注明,原文地址: http://www.benmutou.com/archives/2815
文章来源:笨木头与游戏开发
大家会不会很疑惑,创建实体都需要将场景里的GameObject进行转换,太麻烦了吧?

正常情况下我们都是从prefab(预制体)实例化游戏对象的,这种在场景里转换实体的方式总感觉哪里不对,是这样吗?

没错,其实还有更方便的转换实体方式,一起来看看吧。

1.世界(World

新的转换实体的代码其实很简单,但在这之前,我们需要先了解一些基础概念。

 

ECS中有一个很基本的东西——世界(World)。

 

整个游戏过程中可以有多个世界,而ECS会自动为我们创建一个默认世界(可以关闭自动创建,这里不做过多的介绍)。

 

默认创建的世界(World)包含了实体管理器(EntityManager)以及项目中所有可用的系统(System)。

这也就解释了为什么我们的System类会自动被执行,这因为它们被添加到了默认的世界中。至于怎么添加的,我们就先不管了(用了反射)。

2.实体管理器(EntityManager

这个就很好理解了,EntityManager负责管理某个世界(World)下的所有实体,理论上来说我们可以直接从EntityManager获取到某个实体的组件。

3.我们所喜爱的实体创建方式

直接看代码吧:
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

public class Spawner_FromMonoBehaviour : MonoBehaviour { public GameObject Prefab; public int CountX = 10; public int CountY = 10;

void Start () { /* 创建实体时需要指定配置,这里涉及到World的概念,可以先不管,照抄就是了 */ var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);

/* 从我们的prefab中创建一个实体对象 */ var entityFromPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, settings);

/* 实体管理器 */ var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

/* 循环创建多个实体 */ for (var x = 0; x < CountX; x++) { for (var y = 0; y < CountY; y++) { /* 赋值新的实体 */ var instance = entityManager.Instantiate(entityFromPrefab);

/* 修改实体的Translation组件 */ var position = transform.TransformPoint(new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F)); entityManager.SetComponentData(instance, new Translation { Value = position }); } } } }
 

a.首先,这是一个MonoBehaviour

b.通过调用GameObjectConversionUtility.ConvertGameObjectHierarchy函数从我们的Prefab中创建了一个实体对象

c.调用EntityManager的Instantiate函数,从现有的实体创建新的实体,然后循环创建多个相同的实体。

d.接下来就是创建组件,通过调用EntityManager.SetComponentData给实体修改组件。同样的,也可以调用EntityManager.AddComponentData给实体添加任意多个组件。

 

代码很简单,相信大家看看就懂了。

这种创建实体的方式,我个人认为,是非常贴近实际开发情况了。

4.运行

至于效果,当然是先创建一个场景,然后创建一个空的GameObject,然后把Spawner_FromMonoBehaviour挂到GameObject上。

另外,自己创建一个Prefab(预制体)拖到Spawner_FromMonoBehaviour的Prefab字段上,然后运行,就能看到创建了一堆对象了。



 

注意,本系列教程基于DOTS相关预览版的Package包,是预览版,不代表正式版的时候也适用。

转发本系列文章的朋友请注意,带上原文链接和原文日期,避免误导未来使用正式版的开发者。

6 条评论
  • Brian 2020-10-27 17:01:19

    使用Entity Version 0.14.0版本运行以上代码发现有报错,错误信息如下:
    ArgumentNullException: A valid BlobAssetStore must be passed to construct a BlobAssetComputationContext
    Parameter name: blobAssetStore
    解决方式:
    /* 创建实体时需要指定配置,这里涉及到World的概念,可以先不管,照抄就是了 */
    var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, new BlobAssetStore());
    0回复
  • Ace 2020-02-14 17:31:39

    在Spawner里CountX,CountY都填0,0,等于下面生成都没执行,但是打开Entity Debugger 看居然有3个Entity,
    都填1,1的话,有5个Entity。想不通这默认的3个是哪来的。。。
    0回复
    • 博主 笨木头 2020-02-15 08:22:35

      我这边看了,和你的情况不太一样,也许是你的版本和我不一样,或者你有其他地方创建了Entity。

      我这边看到的是,Entities默认会创建一个Entity,这是在World的源码里看到的:
      protected Entity TimeSingleton
      {
      get
      {
      if (m_TimeSingletonQuery.IsEmptyIgnoreFilter)
      EntityManager.CreateEntity(typeof(WorldTime), typeof(WorldTimeQueue));
      return m_TimeSingletonQuery.GetSingletonEntity();
      }
      }

      所以,即使我们不创建任何Entity,也会有一个Entity存在。

      如果是本篇文章的代码,在CountX和CountY都为0的情况下,会有两个Entity,一个是刚刚说的World里创建的,一个是我们的代码创建的,如下代码:
      /* 从我们的prefab中创建一个实体对象 */
      var entityFromPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, settings);

      所以,实际上会有两个Entity。
      但是,你看到的是3个,和我判断的2个有区别,我暂时也不清楚是哪里多出的一个。
      0回复
  • Damian 2020-01-17 11:16:55

    Translation 组件一定要new出来吗?如果是组件,数据类型应该是strut结构体,直接声明变量的话会被分配在栈上,这样运行效率不会更高吗?
    0回复
    • 博主 笨木头 2020-01-18 07:40:17

      如果我没弄错的话,结构体new和不new都可以,new的话会对结构体的属性初始化为默认值,但是存放的内存位置是一样的。
      0回复
      • K 2020-09-28 17:49:47

        Unity 2020.1 我做了一个Prefab预制体,通过这种方式生成的Entity 死活无法显示Sprite 怀疑是Translation组件的问题 但是Debugger发现这个组件是有的
        0回复
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发