一切皆实体

目前十分流行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. 文件上传靶场 upload-labs搭建 Pass 1-4

    upload-labs是一个练习文件上传的靶场 我们需要先安装中间件和PHP,推荐使用小皮面板,如何安装使用见sqli-labs搭建前部分 upload-labs下载:https://gitcode. ...

  2. 网络安全(中职组)-B模块:Web安全渗透测试

    Web安全渗透测试任务环境说明: 服务器场景:PYsystem003(关闭链接)服务器操作系统:未知用户名:未知 密码:未知 1.    通过浏览器访问http://靶机服务器IP/1,对该页面进行渗 ...

  3. MD5 简介 以及 C# 和 js 实现【加密知多少系列】

    〇.简介 MD5 是哈希算法(散列算法)的一种应用.Hash 算法虽然被称为算法,但实际上它更像是一种思想.Hash 算法没有一个固定的公式,只要符合散列思想的算法都可以被称为是 Hash 算法. 算 ...

  4. Springboot 结合 Netty 实战聊天系统

    音视频技术为什么需要微服务 微服务,英文名:microservice,百度百科上将其定义为:SOA 架构的一种变体.微服务(或微服务架构)是一种将应用程序构造为一组低耦合的服务. 微服务有着一些鲜明的 ...

  5. python安装robotframework的一些常见的错误

    python安装robotframework的一些常见的错误 首先的电脑环境是x86的,然后下载的python版本起初是3.10.1的 在cmd 中出入pip install robotframwor ...

  6. 30张图说清楚 TCP 协议

    大家好,我是风筝 前两天分享了 20张图说清楚 IP 协议 今天,继续来网管的自我修养之TCP协议,这可是除 IP 协议外另一个核心协议了. TCP 协议是网络传输中至关重要的一个协议,它位于传输层. ...

  7. 使用requests发送post请求登录

    post请求 语法结构 requests.post(url,data=None,json=None) 参数说明 url:需要爬取的网址 data:请求数据 json:json格式的数据 案例:登录小说 ...

  8. 二进制安装Kubernetes(k8s)IPv4/IPv6双栈 v1.24.0

    二进制安装Kubernetes(k8s) v1.24.0 IPv4/IPv6双栈 介绍 kubernetes二进制安装 1.23.3 和 1.23.4 和 1.23.5 和 1.23.6 和 1.24 ...

  9. kubernetes核心实战(二)---Pod+ReplicaSet

    3.pod Pod 是可以在 Kubernetes 中创建和管理的.最小的可部署的计算单元. Pod (就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个) 容器:这些容器共享存储.网络.以及怎样运行这些容 ...

  10. python入门教程之二十二网络编程

    Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法. 高级别的网络 ...