Entity
请大家关注一下我的微博 @NormanLin_BadPixel坏像素
一般来说,一个类的名字就能大概知道这个类是干啥的,比如ObjectEvent,很容易就知道是跟事件相关的。再来看我们今天要学的Entity,意思是实体。有经验的人应该对Entity不陌生(就连我都不陌生了)。这里希望不知道MVC框架的同学先去看一下MVC框架的介绍。我之前也只是了解了一下,也没有深入学习,也没有真正应用过。Unity3d之MVC框架的使用
那这个Entity就是用了MVC框架设计吗? 不是的,这里只是想引出一个概念,就是数据跟模型的分开管理。
flashyiyi : MVC是UI专用的模式,适用于横向铺量的项目。游戏(非UI部分)显然不是这样的项目。
这个Entity,就是数据。(我猜的哈哈)
使用BsonKnownTypes 属性指定已知类型,以便在将内容反序列化为对象时创建正确的类型。
不知道为什么要这样写。
可见这个类继承了Disposer,实现了ISupportInitialize接口
public interface ISupportInitialize
{
void BeginInit();
void EndInit();
}
接下来我们看到
private HashSet<Component> components;
之前没用过HashSet,就去百度了一下。发现HashSet是一个很有趣的类型,它不支持通过下标或者键来获取值,但是它在判断是否存在某个值上很快,对集合的运算也很快。但是我并没有发现哪里用到了这些特点。这里又出现了新的东西,那就是Component
Component
关于前面序列化的设置,我不是很理解。
接下来的很好理解,我希望大家自己能看懂。
值的一提的是,每个Entity里面都含有好多Component,而每个Component都有一个Entity。我想,Component的Entity应该是记录自己所属的实体。
顺便说一下,这里
public override void Dispose()
{
if (this.Id == 0)
{
return;
}
base.Dispose();
this.Entity?.RemoveComponent(this.GetType());
}
?. 是空条件运算符
空条件运算符:在之前的版本中对于 可空类型 或 动态类型 ,获取子元素往往较为复杂:
if(someSchool != null && someSchool.someGrade != null && someSchool.someGrade.someClass != null)
{
return someSchool.someGrade.someClass.someOne;
}
在 C# 6 中,引入了新的运算符
:return someSchool?.someGrade?.someClass?.someOne;
//也可以使用下标运算,例如
//return someArray?[0];
如果 ?. 运算符的左项为 null ,则直接返回 null 。对于方法或者委托的执行,可以使用 Invoke:someMethod?.Invoke(args);
作者:Trotyl Yu
链接:https://www.zhihu.com/question/27421302/answer/36589976
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这里的Component只是一个基类,之后我们再通过它的子类来具体了解一下这个类。
Entity
private Dictionary<Type, Component> componentDict = new Dictionary<Type, Component>();
回到Entity,我们又看到了一个字典,通过Type为键值储存了Component,现在这个componentDict和之前的components都有什么用我们后面揭晓
protected Entity()
{
this.Id = IdGenerater.GenerateId();
}
这个IdGenerater应该是自动生成一个独一无二ID的工具。用来标识场景里的实体。
后面则是一个有指定ID的构造方法。
这里重写了Dispose方法,多实现了对自己所有的组件的处理。
foreach (Component component in this.GetComponents())
{
try
{
component.Dispose();
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
public K AddComponent<K>() where K : Component, new()
这是一个添加组件的方法,之后的几个只是多了几个参数。
K component = ComponentFactory.Create<K>(this);
这里不难猜出这只是一个组件工厂,提供创建组件的方法。里面应该有用对象池来进行管理。
public static T Create<T>(Entity entity) where T : Component
{
T disposer = ObjectPool.Instance.Fetch<T>();
disposer.Entity = entity;
ObjectEvents.Instance.Awake(disposer);
return disposer;
}
不出所料。另外在创建组件的时候,调用 ObjectEvents.Instance.Awake(disposer); 我们之前学过,这个方法会在ObjectEvents里调用disposer所实现的IAwake接口(如果有的话)
这里把组件加入Entity,组件会被存放在componentDict里面,如果组件是ComponentDB类型的,则会额外放入components,这是什么用?我也不是很清楚。不过CompoenntDB是通过Bson反序列化来的,可能是为了区分吗?之后再看看。
public void RemoveComponent<K>() where K : Component
public void RemoveComponent(Type type)
这两个则是移除组件的方法,第一个是直接指定一个Component来移除,第二个则是通过组件的Type来移除。但我们仔细看代码,其实第一个只是通过指定的Component来获取到指定的Type再来通过Type来移除。所以第一个方法可以改成:
public void RemoveComponent<K>() where K : Component
{
RemoveComponent(typeof(K));
}
我猜的。如果作者有什么深意我就不知道了。
这里值得一提的是,在移除组件的时候,我们调用了component的Dispose方法。这里结合我们之前ObjectEvents的学习,我们知道,到这里component的Id才变成0,也就是说,如果component里面有Update的方法,则Update方法会在Component存在的时候随ObjectEvents的Update而调用。不过ObjectEvents应该不会直接调用Component,而是先通知Entity,再由Entity来调用Component。其实跟Unity自带的Update一样,只不过作者自己实现了一套,牛逼。这在网络同步上特别是帧同步上是很重要的。
后面的两个获取实体上的组件方法我就不多说了,大家自己看看就明白了。
public virtual void BeginInit()
public virtual void EndInit()
这就是 ISupportInitialize 需要实现的接口了。这个接口应该会在哪个地方统一调用,来统一管理需要初始化的东西。我们用Shift+F12来看看哪里掉用了,发现在ConfigComponent里调用了,另外还在BsonClassMapSerializer里面看到了。第一个看起来简单点,我们后面应该会讲到,但是第二个太长了,还是在Plugins里面的,这个水平不是现在的我能够窥视的,我只能猜了。既然这是Bson序列化反序列化的东西,那我们就姑且当它是在把数据反序列化成对象的时候调用的就行了。这样一想,就简单多了,连前面components和componentDict有什么区别都解决了。
因为我们序列化的时候,可能储存了一个实体的所有component信息,当反序列化的时候,components里放的就是我们储存好的component信息,会在EndInit这里把本有的component加入到componentDict里面。
至此,我们讲完了Entity顺便讲了它和Component的关系。大家可以继续回到最初的地方接着学习了。