一切皆实体

目前十分流行ECS设计,主要是守望先锋的成功,引爆了这种技术。守望先锋采用了状态帧这种网络技术,客户端会进行预测,预测不准需要进行回滚,由于组件式的设计,回滚可以只回滚某些组件即可。ECS最重要的设计是逻辑跟数据的完全分离。即EC是纯数据,System实际上就是逻辑,由数据驱动逻辑。数据驱动逻辑是什么意思呢?很简单通过Update检测数据变化,通过事件机制来订阅数据变化,这就是所谓的数据驱动了。其它的特点例如缓存命中,在编写逻辑上来说并不太重要,现代游戏都用脚本,连脚本的性能都能容忍怎么会在乎缓存命中那点性能提升?ET在设计的时候吸收了这些想法,但是并不完全照搬,目前的设计是我经过长期的思考跟重构得来的,还是有些自己特色。

传统的ECS写逻辑作者看来存在不少缺陷,比如为了复用,数据必然要拆成非常小的颗粒,会导致组件非常非常多。但是游戏是多人合作开发的,每个人基本上只熟悉自己的模块,最后可能造成组件大量冗余。还有个问题,常见的ECS是扁平式的,Entity跟Component只有一层。组件一多,开发功能可能不知道该使用哪些Component。好比一家公司,最大的是老板,老板手下带几百个人,老板不可能认识所有的人,完成一项任务,老板没法挑出自己需要的人。合理的做法是老板手下应该有几个经理,每个经理手下应该有几个主管,每个主管管理几个工人,这样形成树状的管理结构才会容易管理。这类似ET的做法,Entity可以管理Component,Component管理Entity,甚至Component还可以挂载Component。例如:人由头,身体,手,脚组成,而头又由眼睛,耳朵,鼻子,嘴巴组成。

    Head head = human.AddComponent<Head>();
head.AddComponent<Eye>();
head.AddComponent<Mouse>();
head.AddComponent<Nose>();
head.AddComponent<Ear>();
human.AddComponent<Body>();
human.AddComponent<Hand>();
human.AddComponent<Leg>();

ET中,所有数据都是Entity,包括Entity,Entity既可以当成组件使用,也可以当做其它Entity的孩子。通用的数据放在Entity身上作为成员,不太通用的数据可以作为组件挂在Entity身上。比如物品的设计,所有物品都有配置id,数量,等级的字段,这些字段没有必要做成组件,放在Entity身上使用会更加方便。

    class Item: Entity
{
// 道具的配置Id
public int ConfigId { get; set; }
// 道具的数量
public int Count { get; set; }
// 道具的等级
public int Level { get; set; }
}

ET的这种设计数据是一种树状的结构,非常有层次,能够非常轻松的理解整个游戏的架构。顶层Game.Scene,不同模块的数据都挂载在Game.Scene上面,每个模块自身下面又可以挂载很多数据。每开发一个新功能不用思考太多,类该怎么设计,数据放在什么地方,挂载这里会不会导致冗余等等。比如我玩家需要做一个道具系统,设计一个ItemsComponent挂在Player身上即可,需要技能开发一个SpellComponent挂在Player身上。全服需要做一个活动,搞个活动组件挂在Game.Scene上面。这种设计任务分派会很简单,十分的模块化。

组件的一些细节

1.组件的创建

组件的创建不要自己去new,应该统一使用ComponentFactory创建。ComponentFactory提供了三组方法用来创建组件Create,CreateWithParent,CreateWithId。Create是最简单的创建方式,它做了几个处理
a. 根据组件类型构造一个组件
b. 将组件加入事件系统,并且抛出一个AwakeSystem
c. 是否启用对象池
CreateWithParent在Create的基础上提供了一个Parent对象,设置到Component.Parent字段上。CreateWithId是用来创建ComponentWithId或者其子类的,在Create的基础上可以自己设置一个Id, Component在创建的时候可以选择是否使用对象池。三类工厂方法都带有一个fromPool的参数,默认是true。

2.组件的释放

Component都继承了一个IDisposable接口,需要注意,Component有非托管资源,删除一个Component必须调用该接口。该接口做了如下的操作
a. 抛出Destroy System
b. 如果组件是使用对象池创建的,那么在这里会放回对象池
c. 从全局事件系统(EventSystem)中删除该组件,并且将InstanceId设为0
如果组件挂载Entity身上,那么Entity调用Dispose的时候会自动调用身上所有Component的Dispose方法。

3.InstanceId的作用

任何Component都带有一个InstanceId字段,这个字段会在组件构造,或者组件从对象池取出的时候重新设置,这个InstanceId标识这个组件的身份。为什么需要这么一个字段呢?有以下几个原因

  1. 对象池的存在,组件未必会释放,而是回到对象池中。在异步调用中,很可能这个组件已经被释放了,然后又被重新利用了起来,这样我们需要一种方式能区分之前的组件对象是否已经被释放,例如下面这段代码:
        public static async ETVoid UpdateAsync(this ActorLocationSender self)
{
try
{
long instanceId = self.InstanceId;
while (true)
{
if (self.InstanceId != instanceId)
{
return;
}
ActorTask actorTask = await self.GetAsync(); if (self.InstanceId != instanceId)
{
return;
}
if (actorTask.ActorRequest == null)
{
return;
} await self.RunTask(actorTask);
}
}
catch (Exception e)
{
Log.Error(e);
}
}

while (true)中是段异步方法,await self.GetAsync()之后很可能ActorLocationSender对象已经被释放了,甚至有可能这个对象又被其它逻辑从对象池中再次利用了起来。我们这时候可以通过InstanceId的变化来判断这个对象是否已经被释放掉。
2. InstanceId是全局唯一的,并且带有位置信息,可以通过InstanceId来找到对象的位置,将消息发给对象。这个设计将会Actor消息中利用到。这里暂时就不讲了。

ET开源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com)   qq群:474643097

ET介绍—— 一切皆实体的设计的更多相关文章

  1. Unity3d 引擎原理详细介绍、Unity3D引擎架构设计

    体系结构 为了更好地理解游戏的软件架构和对象模型,它获得更好的外观仅有一名Unity3D的游戏引擎和编辑器是非常有用的,它的主要原则. Unity3D 引擎 Unity3D的是一个屡获殊荣的工具,用于 ...

  2. Unity3d 引擎原理详细介绍、Unity3D引擎架构设计 - zhibolife

    时间 2014-03-24 11:18:00  博客园-所有随笔区原文  http://www.cnblogs.com/zhibolife/p/3620440.html 体系结构 为了更好地理解游戏的 ...

  3. MyEclipse的实体关系设计

    原文地址:http://www.myeclipsecn.com/learningcenter/database-development/myeclipse-entity-relation-design ...

  4. Rafy 领域实体框架设计 - 重构 ORM 中的 Sql 生成

    前言 Rafy 领域实体框架作为一个使用领域驱动设计作为指导思想的开发框架,必然要处理领域实体到数据库表之间的映射,即包含了 ORM 的功能.由于在 09 年最初设计时,ORM 部分的设计并不是最重要 ...

  5. HIS系统患者实体OO设计的一点思考

    软件开发的生命周期中,数据库建模后,在某个数据库系统中形成相对应的表,之后再根据数据库模型设计相关的业务对象及其关系.这其实是进行了两次设计,一次是数据库模型设计,数据库模型设计是根据现实业务提取出来 ...

  6. 13.Quick QML-RowLayout、ColumnLayout、GridLayout布局管理器介绍、并通过GridLayout设计的简易网站导航界面

    上章我们学习了:12.Quick QML-QML 布局(Row.Column.Grid.Flow和嵌套布局) .Repeater对象,本章我们继续来学习布局管理器 1.RowLayout.Column ...

  7. Productivity Improvements for the Entity Framework(实体框架设计)【转】

    Background We’ve been hearing a lot of good feedback on the recently released update to the Entity F ...

  8. MySQL记录异常实体类设计

    public class LogInfo { /// <summary> /// 应用名 /// </summary> public string AppName { get; ...

  9. Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  10. DDD 领域驱动设计-两个实体的碰撞火花

    上一篇:<DDD 领域驱动设计-领域模型中的用户设计?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 在 ...

随机推荐

  1. 详解数仓中sequence的应用场景及优化

    摘要:本文简单介绍sequence的使用场景及如何修改sequence的cache值提高性能. 本文分享自华为云社区<GaussDB(DWS)关于sequence的那些事>,作者:Arro ...

  2. Java笔记第四弹

    File类创建功能 public boolean createNewFile();//当该文件名不存在时,创建一个由该抽象路径名命名的新空文件 public boolean mkdir();//创建由 ...

  3. BL808:【M1s DOCK开发板】与LVGL 使用体验

    前言 念春时已夏,恋冬雪已融. 总是感叹时光匆匆,便努力在在平凡中挣扎,在平庸中努力,在平淡中积累.奈何时代飞速发展,时间又被工作占用,外加生活中的诱惑又太多了,很多想学.想做.想超越的事,都被抛之一 ...

  4. 人人皆可虚拟,直播还能这么玩?声网推出 MetaLive 元直播解决方案

    视频群聊.在线社交.电商带货.游戏竞技.......越来越多的场景融入了直播这一功能.无可厚非,直播可以拉近人与人间的距离,让彼此间的交流更具象.但传统直播场景更为强调主播个人的表现,用户多以围观.刷 ...

  5. 内核不中断前提下,Gaussdb(DWS)内存报错排查方法

    摘要:本文主要讲解如何在内核保证操作不能中断采取的特殊处理,理论上用户执行的sql使用的内存(dynamic_used_memory) 是不会大范围的超过max_dynamic_memory的内存的 ...

  6. 浅谈Array --JavaScript内置对象

    Array --JavaScript内置对象 描述 可以用一个变量存储多种数据类型的Array对象,Array不是关联数组,不能使用字符串作为索引访问数组元素,需要使用非负整数的下标访问数组中的元素. ...

  7. solidity中的mapping

    mapping可以理解为python中对字典的键值遍历,键是唯一的而值是可以重复的 mapping函数的构造: mapping(_KeyType => _ValueType)  mapping ...

  8. python程序,实现以管理员方式运行程序,也就是提升程序权限

    quest UAC elevation from within a Python script? 我希望我的Python脚本能够在Vista上复制文件. 当我从普通的cmd.exe窗口运行它时,不会生 ...

  9. python之PySimpleGUI(一)元素

    1themesg.theme_previewer()获取所有主题颜色sg.preview_all_look_and_feel_themes()同上theme_name_list = sg.theme_ ...

  10. CLion在工程中添加目录&新文件

    1.将新建文件夹添加到cmake.txt文件里 include_directories(Core/新建文件夹 Core/UserInc Drivers/STM32L4xx_HAL_Driver/Inc ...