Skip to content

ECS系统

Fantasy的ECS系统与传统ECS系统相比,具备显著的差异与优势。首先,Fantasy的ECS系统在架构设计上更加灵活,它采用了树形结构,不同于传统ECS的线性结构。这种设计使得系统在组件之间的数据处理和资源管理更加高效。此外,Fantasy的ECS系统与数据库深度集成,实现了数据的快速存取与同步,从而提升了整体性能和扩展性,特别适用于大规模数据驱动的应用场景。这种紧密结合的设计不仅简化了开发流程,还大大提升了系统的响应速度与稳定性。

Entity

在ECS(实体-组件-系统)架构中,所有的实体类都必须继承自 Entity 基类。只有在继承了 Entity 后,实体才能具备使用ECS框架提供的全部功能,如组件的注册、系统的调度和与其他实体的交互。通过这种继承关系,ECS确保了实体与组件及系统之间的有机结合,从而实现了灵活的游戏对象管理和性能优化。

重要的参数:

  • Id:Entity的Id。
  • RunTimeId:Entity的RunTimeId,这个Id是唯一的,其他服务器可以用这个Id当RouteId使用。
  • Scene:所属于的Scene。
  • Parent:组件的父亲。
  • IsDisposed:Entity是否销毁。

实现一个Entity

public class User : Entity
{
    public string Name;
    public string Sex;
}

创建Entity

创建一个Entity。该方法接受一个泛型参数,表示要创建的实体类型。

该方法有一个重载:

  • T Create(Scene scene, bool isPool, bool isRunEvent)
  • T Create(Scene scene, long id, bool isPool, bool isRunEvent)

参数说明:

  • scene:当前场景的Scene。
  • id:指定Entity的Id,不指定。
  • isPool:是否通过对象池创建,如果是在对象池创建的Entity销毁的时候回会到对象池。
  • isRunEvent:是否执行ECS事件。

创建Entity

var user = Entity.Create<User>(scene, false, true);
var user = Entity.Create<User>(scene, 1, false, true);

Entity的接口

ISupportedMultiEntity

支持在一个组件里添加多个同类型组件

ISupportedDataBase

让Entity支持数据库,如果不实现这个接口是不支持数据的相关的功能。

ISingleCollectionRoot

Entity保存到数据库的时候会根据子组件设置分离存储特性分表存储在不同的集合表中,现在看不明白没关系,后面会有单独的章节来介绍。

ISupportedSingleCollection

定义实体支持单一集合存储的接口。当实体需要单独存储在一个集合中,并且在保存到数据库时不会与父组件一起保存在同一个集合中时,应实现此接口,后面会有单独的章节来介绍。

Entity的API

GetParent<T>

获取Entity的父Entity,该方法接收一个泛型参数,表示父Entity的类型。

GetParent

var userManager = user.GetParent<UserManager>();

AddComponent

添加一个组件到当前Entity上。该方法接收一个泛型参数,表示添加的Entity的类型。

该方法有三个重载:

  • T AddComponent<T>(bool isPool = true)
  • T AddComponent<T>(long id, bool isPool = true)
  • AddComponent(Entity component)

参数说明:

  • T:要添加的Entity类型。
  • id:添加的Entity的Id。
  • isPool:是否从对象池中创建。
  • component:添加到当前Entity的实体。

AddComponent

var userManager = Entity.Create<UserManager>(scene, false, true);
// 添加User组件到userManager上
var user = userManager.AddComponent<User>();
// 添加User组件到userManager上,并且设置user组件的Id为1
var user = userManager.AddComponent<User>(1);
// 添加User组件到userManager上,设置不通过对象池创建
var user = userManager.AddComponent<User>(false);
// 添加已经存在的user到userManager上
var user = Entity.Create<User>(scene, false, true);
userManager.AddComponent(user);

GetComponent

在当前Entity上获取一个组件。该方法接收一个泛型参数,表示获取的Entity的类型。

该方法有四个重载:

  • T GetComponent<T>()
  • T GetComponent<T>(long id)
  • T GetOrAddComponent<T>(bool isPool = true)
  • Entity GetComponent(Type type)

参数说明:

  • T:Entity类型。
  • id:查询的Entity的Id。
  • isPool:是否从对象池中创建。
  • type:Entity的Type。

GetComponent

var userManager = Entity.Create<UserManager>(scene, false, true);
// userManager上获取User组件
var user = userManager.GetComponent<User>();
// userManager上根据Id获取User组件,只有实现了ISupportedMultiEntity接口的组件才可以使用
var user = userManager.AddComponent<User>(1);
// userManager上获取User组件,如果没有就创建一个
var user = userManager.GetOrAddComponent<User>();
// userManager上获取User组件
var user = userManager.GetComponent(typeof(User));

RemoveComponent

在当前Entity上移除一个组件。该方法接收一个泛型参数,表示移除的Entity的类型

该方法有四个重载:

  • RemoveComponent<T>(bool isDispose = true)
  • RemoveComponent<T>(long id, bool isDispose = true)
  • RemoveComponent<T>(T component, bool isDispose = true)
  • RemoveComponent(Entity component, bool isDispose = true)

参数说明:

  • T:Entity类型。
  • id:移除的Entity的Id,只对实现了ISupportedMultiEntity接口的Entity有效
  • isDispose:是否在移除后执行Dispose方法。
  • component:需要移除的component。

RemoveComponent

// 在当前Scene下移除了一个UserManager组件,并执行UserManager的Dispose方法
scene.RemoveComponent<UserManager>();
// 不执行UserManager的Dispose方法,仅移除
scene.RemoveComponent<UserManager>(false);
// 在当前Scene下移除了一个实现了ISupportedMultiEntity接口的组件
scene.RemoveComponent<UserManager>(123);
// 在当前Scene下移除了一个userManagerComponent组件
scene.RemoveComponent<UserManager>(userManagerComponent);
// 在当前Scene下移除了一个userManagerComponent组件
scene.RemoveComponent(userManagerComponent);

Deserialize

当一个组件被反序列化为实体(Entity)时,必须调用该方法,才能在框架中正常运行和使用。

该方法有一个重载:

  • Deserialize(Scene scene, bool resetId = false)

参数说明:

  • scene:要注册的目标scene。
  • resetId:是否重新生成该实体的Id。

Deserialize

// 将userManager实体注册到scene下面
userManager.Deserialize(scene);    

Dispose

销毁当前实体,销毁后会自动销毁当前实体下的所有实体。

Dispose

// 将userManager实体销毁掉
userManager.Dispose();  

Entity事件系统

为了简化逻辑开发流程,Entity 同样提供了多种事件系统的支持,允许根据不同的需求和场景灵活创建事件。通过这一设计,开发者可以在项目中根据实际需要选择适合的事件机制,从而更高效地管理和触发事件。

Awake

在Entity创建后执行。

需要定义一个类来继承AwakeSystem,并接受一个泛型类型:

  • 泛型: T 指定了需要订阅事件的Entity类型。

在继承了 AwakeSystem 类之后,必须实现一个名为 Awake 的方法。这个方法接受一个参数:

  • self: 订阅Entity的实例。

Awake的示例

public sealed class OnSceneAwakeSystem : AwakeSystem<Scene>
{
   protected override void Awake(Scene self)
   {
       Log.Debug($"Scene {self.SceneType} is awake");
   }
}

Destroy

在Entity执行Dispose后执行。

需要定义一个类来继承DestroySystem,并接受一个泛型类型:

  • 泛型: T 指定了需要订阅事件的Entity类型。

在继承了 DestroySystem 类之后,必须实现一个名为 Destroy 的方法。这个方法接受一个参数:

  • self: 订阅Entity的实例。

Destroy的示例

public sealed class OnSceneDestroySystem : DestroySystem<Scene>
{
   protected override void Destroy(Scene self)
   {
       Log.Debug($"Scene {self.SceneType} is Destroy");
   }
}

Update

订阅该事件后,它将以帧率为基础进行执行,类似于Unity中的Update方法。在服务器端,事件的执行频率将根据当前服务器的运行速度自动调整,与Unity中游戏引擎的帧率同步更新效果相似。也就是说,事件执行的频率将与服务器的运算能力和负载情况挂钩,确保在不同硬件条件下都能平稳运行,而不受具体时间间隔的严格限制。这种机制类似于Unity的Update函数,该函数每帧调用一次,根据渲染循环不断更新游戏逻辑。在服务器环境中,虽然没有图形渲染的概念,但这种基于服务器计算速率的调度方式能够有效地模拟帧更新的逻辑,使事件在计算强度或负载波动时仍能保持动态调整与平滑执行。

需要定义一个类来继承UpdateSystem,并接受一个泛型类型:

  • 泛型: T 指定了需要订阅事件的Entity类型。

在继承了 UpdateSystem 类之后,必须实现一个名为 Update 的方法。这个方法接受一个参数:

  • self: 订阅Entity的实例。

Update的示例

public sealed class OnSceneUpdateSystem : UpdateSystem<Scene>
{
   protected override void Update(Scene self)
   {
       Log.Debug($"Scene {self.SceneType} is Update");
   }
}

Deserialize

在Entity执行Deserialize后执行。

需要定义一个类来继承DeserializeSystem,并接受一个泛型类型:

  • 泛型: T 指定了需要订阅事件的Entity类型。

在继承了 DeserializeSystem 类之后,必须实现一个名为 Deserialize 的方法。这个方法接受一个参数:

  • self: 订阅Entity的实例。

Deserialize的示例

public sealed class OnSceneDeserializeSystem : DeserializeSystem<Scene>
{
   protected override void Deserialize(Scene self)
   {
       Log.Debug($"Scene {self.SceneType} is Deserialize");
   }
}