- 概述
- 概念
- 原型Archetypes
- 内存块
- 实体查询EntityQuery
- 任务Jobs
- 系统组织
- 优化地图系统
- 主世界
- 六边形单元生成系统
- 六边形单元系统
- 更新计划
- 作者的话
- 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主世界
- UnityMMO网络同步
- 用ECS做HexMap:自动生成地图系统
- 用ECS做HexMap:利用RenderMesh绘制六边形
- 用ECS做HexMap:利用RenderMesh为六边形涂色
- 用ECS做HexMap:六边形单元的颜色混合
- 用ECS做HexMap:重构地图系统
- 用ECS做HexMap:鼠标点击六边形单元涂色
花了两天时间想要精简冗余的代码,结果都以失败告终,而且我很怀疑自己对ECS的理解和使用方式是否真的正确。很多地方虽然这样写了,但是却觉得有些本末倒置,究竟怎样做才是合理的设计? 抱着困惑,我决定沉下心来好好看看UnityECS官方文档,也许可以从中得到一些更深层次的理解。
ECS全称”实体组件系统“(Entity Component System,简称ECS),是Unity面向数据堆栈技术(Data-Oriented Tech Stack,简称DOTS)的核心。ECS顾名思义由以下三部分组成:
- 实体,一切构成你游戏世界的事物都是实体,相对于面向对象编程(Object Oriented Programming,简称OOP)中的对象(Object),在ECS之前,Unity中的一切都是OOP架构的,所以贯彻的是GameObject游戏对象这个概念,所有的代码都是围绕Object对象展开的,不仅如此,我所接触到的编程思想都秉持着一切皆是对象(Everything is Object)的原则来设计。夸张的说,一个程序员无论走到哪里,无论在做什么,他的大脑中始终无法释怀的就是对象。
- 组件,和实体相关的数据就是组件,在OOP中也有数据,但是这些数据都是通过对象来调用的,例如你想知道玩家这个对象的名字/血量/力量/暴击等等,一切数据都是这个对象的属性,通过访问对象来获取,也可以通过对象的公共方法来影响这些数据,总而言之,对象和实体是关联的。而实体和数据则完全是解耦的,数据由组件自身来组织和管理。OOP也有MVC这样的设计模式来解决耦合问题,但是和ECS的设计思想又不一样,具体的区别后面再详细看吧。
- 系统,System才是用来操作数据的,它以实体为索引,然后操作与其关联的所有数据。这么看来,我把六边形单元作为实体,用系统来操作其数据似乎并没有错。比较使我感到困惑的是,如果你的数据量比较大时,当涉及到数组/列表时,你无法在组件中进行定义,虽然有DynamicBuffer这种类似于数组/列表的定义,可是一点都不好用。
ECS的架构分为索引(实体)、数据(组件)和行为(系统),这样的架构是聚焦于数据的。系统通过读取由实体索引的组件数据流,将数据从输入状态转换为输出状态。 下面的图表将表明三者是如何一起运作的: 在上图中,系统读取了Translation(用来把OOP的信息转化成ECS的数据)和Rotation组件,将两者相乘,然后更新LocalToWorld组件,即公式:L2W=T*R。 实际上,实体A和B有Render组件,实体C却没有。这里吐槽一下,目前ECS封装了相对于OOP的MeshRender,它改了个名字叫RenderMesh,这是个共享组件,不能在Job中使用,我觉得用起来不爽。 实体上的组件可以作为系统刷选实体的依据,在Job执行的过程中,它可以指定组件。系统在查询实体时,也是通过组件来刷选的,就像图中所示,系统如果指定了Render组件,那么就只有AB受影响。
一句话解释就是组件的排列组合。 组件的数量越多,你的实体原型将几何倍增。 这无疑可以大大扩展游戏实体的丰富性,就像杂交培育一样,DNA作为数据,将其拆分成很多个组件,通过排列组合,嘣,物种大爆炸。这个世界就是这样,猫和老虎,豹子和狮子,狗和狼,猩猩、猴子和人…… 原型就是组件的组合。
实体的原型决定了实体在内存中储存的位置。一块内存由一个原型块对象来代表,原型块中总是包含了相同原型的对象。 内存块不是动态扩容的,当一块内存满了,新的内存块会被分配来装新的同种原型。 如果你改变实体的组件构成,新增或删除组件,都会导致实体从一个内存块移动到对应实体原型的内存块。 这种一对多的设计机制有利于查询实体,实体的数量很多,但是原型的数量相对较少。 组件在内存块中是无序的,当某个原型的实体被新建时,它将被放到第一个有空间的对应原型内存块中。 存放方式也不是按照顺序来的,而是见缝插针的方式,以保持内存块的紧密性。
你可以通过实体查询来帮助系统识别哪些实体是你想操作的,实体查询会搜索满足条件的实体,还可以根据需要使用下列条件:
- All,实体必须满足所有你需要的组件。
- Any,只要有任何一个满足条件都可以。
- None,一个都不包含。
任务系统就是用来利用多线程的,这个可以这样理解,当你在跑步的时候,也可以同时听音乐;当我想写一部小说的时候,我先不动笔,而是尽情去玩耍,等玩累了,小说的情节也有了,其实我们的大脑也是多线程的。 详细怎么使用后面有代码示例,只要知道这个概念即可。
系统组织我大概画了一下系统的组织架构,如下所示: 后面的内容我就不翻译了,毕竟现在只是半成品,等时机成熟了,官方更新正式版了再研究研究。 看完官方文档后,我觉得自己的理解有些问题,于是开始重构代码。
这是我简单绘制的架构图,服务器端暂时还没有做,客户端分为ECS世界和OOP世界,因为ECS还没有物理引擎支撑,所以现在只能进行混合开发。也有大神做Pure ECS的纯粹开发,我大概看了下物理的写法,很复杂,需要很多运算。我物理不是很好,数学也很勉强,还是等官方出ECS的物理组件吧。
于是我把地图的渲染权还给OOP,地图的计算仍然交给ECS来做,详细的还是看代码吧:
///
/// 主世界
///
public class MainWorld : MonoBehaviour
{
///
/// 地图材质
///
//public Material material;
///
/// 地图宽度(以六边形为基本单位)
///
[SerializeField]
private int MapWidth = 6;
///
/// 地图长度(以六边形为基本单位)
///
[SerializeField] private int MapHeight = 6;
///
/// 地图颜色
///
[SerializeField] private Color defaultColor = Color.white;
//单例模式
public static MainWorld Instance = null;
private World m_HexMapWorld;
private CellSpawnSystem m_CellSpawnSystem;
private EntityManager m_EntityManager;
//private Entity m_Mesh;
private Entity m_Builder;
private Mesh m_Mesh;
private MeshCollider m_MeshCollider;
#region Mono
//Make this single
private void Awake()
{
Instance = this;
//初始化
Initialize();
}
// Update is called once per frame
void Update()
{
m_CellSpawnSystem.Update();
}
#endregion
#region Init初始化
///
/// 构造函数
///
private MainWorld() { }
///
/// 初始化
///
private void Initialize()
{
//0.get the active world or new one
m_HexMapWorld = World.Active != null ? World.Active : new World("HexMap");
//1.get the entity Manager
m_EntityManager = m_HexMapWorld.EntityManager;
//2.Create Builder Entity;
EntityArchetype builderArchetype = m_EntityManager.CreateArchetype(typeof(Data),typeof(OnCreateTag));
m_Builder = m_EntityManager.CreateEntity(builderArchetype);
//3.Setup Map; Todo:get map data from server and SetupMap,now we just use default data
SetupMap(MapWidth, MapHeight, defaultColor);
//4.Create Mesh entity for map and setup RenderMesh
GetComponent().mesh = m_Mesh = new Mesh();
m_Mesh.name = "Hex Mesh";
m_MeshCollider=gameObject.AddComponent();
//5.Create System to spawn cells
m_CellSpawnSystem = m_HexMapWorld.CreateSystem();
}
#endregion
#region Public Function公共方法
///
/// 渲染地图
///
public void RenderMesh()
{
//暴力获取所有实体,如果有系统外的实体就糟糕了,Todo:只获取Cell单元实体
NativeArray entities = m_EntityManager.GetAllEntities();
if (entities.Length
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【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脚手架写一个简单的页面?