1个不稳定版本
0.1.0 | 2024年1月20日 |
---|
#470 在 游戏开发
46KB
855 行
moecs
moecs
(微ECS)是用Rust编写的轻量级ECS库。
专为与轻量级基于Rust的游戏引擎配合使用而构建,如ggez。
请参阅示例实现此处。
功能
- 简单用户界面API。
- 实体查询缓存,以实现高效的重复查找。
- 可配置的并行性(由rayon提供)
- 实体查询并行执行(当未缓存时)。
- 系统执行可以配置为并行运行。
- 系统参数被包裹在
Arc<RwLock>
中,因此可以在System
中轻松实现并行性。
文档
组件
组件
Component
是高度可配置的数据包,可以将任意组合在一起以形成一个实体。例如,一个狗实体可能由位置组件(它在世界中的位置)和状态组件(它正在做什么)组成。在moecs
中,这将像这样实现
#[derive(Component)]
struct PositionComponent {
x: f32,
y: f32,
}
#[derive(Component)]
struct DogStateComponent {
state: DogState,
}
enum DogState {
Sleeping,
Playing,
Barking,
}
请注意,必须为每个Component
定义#[derive(Component)]
属性。
组件包
可以使用ComponentBundle
轻松地将组件打包在一起(这在创建新实体时非常有用,稍后将要讨论)。使用我们的狗示例,我们可以使用以下方法将PositionComponent
和DogStateComponent
分组
let bundle = ComponentBundle::new()
.add_component(PositionComponent {
x: 0,
y: 0,
})
.add_component(DogStateComponent {
state: Sleeping,
});
实体
实体管理器
如组件部分所述,实体仅仅是相关组件的集合。通过EntityManager
执行实体操作。
注意:将EntityManager
直接传递给定义的System
。因此,所有与实体相关的更改都只能在System
中发生。
EntityManager
的职责包括
- 创建新实体。
通过提供要关联到该实体的初始 Component
的 ComponentBundle
来创建实体(注意:可以提供空的 ComponentBundle
)。将返回一个 u32
,表示实体的唯一标识符,可以用它来稍后检索该实体的 Component
。
注意:严格的要求是,实体在任何给定时间只能注册一个特定类型的组件。如果违反此规则,moecs
将引发恐慌。
entity_manager.create_entity(
ComponentBundle::new()
.add_component(PositionComponent { x: 0, y: 0 })
);
- 删除现有实体。
给定某个实体 ID,可以通过以下方式完全删除该实体(包括删除所有相关 Component
):
entity_manager.delete_entity(entity_id);
- 向现有实体添加组件。
可以通过以下方式向现有实体添加额外的 Component
:
entity_manager.add_components_to_entity(
&entity_id,
ComponentBundle::new()
.add_component(VelocityComponent { x_vel: 0, y_vel: 0 })
);
注意:上述规则仍适用,即实体只能有一个特定类型的组件。如果违反此规则,moecs
将引发恐慌。
- 从现有实体中删除组件。
同样,可以通过以下方式从现有实体中删除(删除)组件:
entity_manager.remove_component_from_entity::<PositionComponent>(&entity_id);
- 查询具有(或不具有)指定组件的实体。
查询是通过使用 Query
结构来完成的,该结构具有两种指定过滤条件的方式:with
、without
。这些用于遍历所有已注册实体的列表,以过滤出具有特定 Component
的实体,或者相应地过滤出没有其他组件的实体。
查询结果通过 QueryResult
结构返回,该结构包括过滤实体的实体 ID 以及相关的 Component
。
注意:查询结果会自动缓存。此外,查询处理是并行执行的(跨注册实体),以提高效率。
示例(简化)流程
entity_manager
.filter(
Query::new()
.with::<SomeComponent>()
.without::<SomeOtherComponent>(),
)
.iter()
.for_each(|result: QueryResult| {
let component = result.get_component::<SomeComponent>();
println!(
"Entity: {} has component {:?}.",
result.entity_id(),
component
);
});
系统
系统
System
是属于某些实体的 Component
发生变化并与彼此交互的地方。换句话说,System
包含了程序的逻辑。例如,一个基本的 PhysicsSystem
可以这样实现:
#[derive(System)]
struct PhysicsSystem;
impl System for PhysicsSystem {
fn execute(entity_manager: Arc<RwLock<EntityManager>>, params: Arc<SystemParamAccessor>) {
entity_manager
.read()
.unwrap()
.filter(
Query::new()
.with::<PositionComponent>()
.with::<VelocityComponent>(),
)
.iter()
.for_each(|result| {
let entity_id = result.entity_id();
let position = result.get_component::<PositionComponent>().unwrap();
let velocity = result.get_component::<VelocityComponent>().unwrap();
position.write().unwrap().x += velocity.read().unwrap().x_vel;
position.write().unwrap().y += velocity.read().unwrap().y_vel;
});
}
}
以下几点需要注意
- 所有
System
必须使用#[derive(System)]
属性。 - 所有
System
必须类似地实现System
特性,这基本上意味着实现execute
fn
。这使您能够访问EntityManager
(如上所述),以及SystemParamAccessor
(如下所述)。
系统参数
将数据从 moecs
生态系统外部传递进来是有益的(例如,一个输入处理程序或渲染画布)。这些可以通过 SystemParam
来传递。以下是一个示例定义:
#[derive(SystemParam)]
struct CanvasParam<'a> {
canvas: &'a mut Canvas,
}
SystemParam
通过 SystemParamAccessor
收集和访问,它基本上只是提供了一个方便的从 System
中查找 SystemParam
的方式。例如:
#[derive(System)]
struct RenderSystem;
impl System for RenderSystem {
fn execute(entity_manager: Arc<RwLock<EntityManager>>, params: Arc<SystemParamAccessor>) {
let canvas_param = params.get_param::<CanvasParam>().unwrap();
let canvas_param = &mut canvas_param.write().unwrap();
let canvas = &mut canvas_param.canvas;
// etc.
}
}
系统组
SystemGroup
是用户定义的类似 System
的分组。实际上,只能通过 SystemGroup
与 System
交互。
SystemGroup
中的 System
可以根据用户的配置注册为顺序执行或并行执行。例如:
let group_1 = SystemGroup::new_sequential_group()
.register::<PhysicsSystem>()
.register::<CollisionSystem>();
let group_2 = SystemGroup::new_parallel_group().register::<RenderSystem>();
这里的并行是水平的。也就是说,System
本身是彼此并行运行的。一个 System
内部的并行是分开的(手动)进行的。
引擎
Engine
是外部进程(即某些中央游戏循环)如何与 moecs
交互的方式。它有以下职责:
- 注册/注销
SystemGroup
。 - 执行已注册的
SystemGroup
。
一般的流程如下:
- 在程序启动时,使用
SystemGroup
(如上所述)定义并分组相似类型的System
。 - 然后,在主循环中,以某种顺序执行
SystemGroup
。
一个基本示例
fn main() {
let mut engine = Engine::new();
let update_systems = engine.register_system_group(
SystemGroup::new_sequential_group()
.register::<PhysicsSystem>(),
);
let render_systems = engine.register_system_group(
SystemGroup::new_sequential_group()
.register::<DrawShapeSystem>(),
);
loop {
engine.execute_group(update_systems, SystemParamAccessor::new());
engine.execute_group(render_systems, SystemParamAccessor::new());
}
}
依赖项
~1.4–2MB
~42K SLoC