这是一个无依赖、轻量级、快速且易于使用的 Swift 实体组件系统 实现。它作为 Fireblade 游戏引擎项目 的一部分进行开发和维护。
请查看 Fireblade ECS 演示应用 或浏览 wiki 中的文档 以开始使用。
以下说明将帮助你在本地机器上启动并运行该项目,并提供代码示例。
Fireblade ECS 可用于所有支持 Swift 5.8 及更高版本以及 Swift 包管理器 (SPM) 的平台。
在你的 Package.swift
文件中扩展以下行,或者使用它创建一个新项目。
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "YourPackageName",
dependencies: [
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.17.5")
],
targets: [
.target(
name: "YourTargetName",
dependencies: ["FirebladeECS"])
]
)
Fireblade-ECS 中的核心元素是 Nexus (枢纽)。它充当存储、访问和管理实体及其组件的中心化方式。单个 Nexus
可以(理论上)同时容纳最多 4294967295 个 Entities
(实体)。
你可以同时使用多个 Nexus
。
使用以下代码初始化一个 Nexus
:
let nexus = Nexus()
然后通过让 Nexus
生成它们来创建实体。
// an entity without components
let newEntity = nexus.createEntity()
要定义组件,请使你的类符合 Component
协议:
final class Position: Component {
var x: Int = 0
var y: Int = 0
}
并将它的实例分配给一个 Entity
,代码如下:
let position = Position(x: 1, y: 2)
entity.assign(position)
通过在创建实体时分配组件可以提高效率。
// an entity with two components assigned.
nexus.createEntity {
Position(x: 1, y: 2)
Color(.red)
}
// bulk create entities with multiple components assigned.
nexus.createEntities(count: 100) { _ in
Position()
Color()
}
此 ECS 使用分组方法,将具有相同组件类型的实体分组,以优化缓存局部性并简化对它们的访问。
具有相同组件类型的实体可能属于一个 Family
(群组)。一个 Family
拥有实体作为成员,以及组件类型作为群组特征。
通过在 Nexus 上使用一组特征调用 .family
来创建一个群组。以下代码创建了一个仅包含具有 Movement
和 PlayerInput
组件,但没有 Texture
组件的实体的群组:
let family = nexus.family(requiresAll: Movement.self, PlayerInput.self,
excludesAll: Texture.self)
这些实体缓存在 Nexus 中,以便高效访问和迭代。群组符合 Sequence 协议,因此可以像 Swift 中的任何其他序列一样迭代和访问成员(组件)。
直接在群组实例上访问群组的组件。要获取群组实体并同时访问组件,请调用 family.entityAndComponents
。如果你只对群组的实体感兴趣,请调用 family.entities
。
class PlayerMovementSystem {
let family = nexus.family(requiresAll: Movement.self, PlayerInput.self,
excludesAll: Texture.self)
func update() {
family
.forEach { (mov: Movement, input: PlayerInput) in
// position & velocity component for the current entity
// get properties
_ = mov.position
_ = mov.velocity
// set properties
mov.position.x = mov.position.x + 3.0
...
// current input command for the given entity
_ = input.command
...
}
}
func update2() {
family
.entityAndComponents
.forEach { (entity: Entity, mov: Movement, input: PlayerInput) in
// the current entity instance
_ = entity
// position & velocity component for the current entity
// get properties
_ = mov.position
_ = mov.velocity
}
}
func update3() {
family
.entities
.forEach { (entity: Entity) in
// the current entity instance
_ = entity
}
}
}
另一方面,Single
(单例)是一种特殊的群组,它在 Nexus 的整个生命周期内仅持有一个实体,并且该实体仅拥有一个组件。如果你有具有 Singleton(单例)特征的组件,这可能会派上用场。单例组件必须符合 SingleComponent
协议,并且不能通过常规群组迭代访问。
final class GameState: SingleComponent {
var quitGame: Bool = false
}
class GameLogicSystem {
let gameState: Single<GameState>
init(nexus: Nexus) {
gameState = nexus.single(GameState.self)
}
func update() {
// update your game sate here
gameState.component.quitGame = true
// entity access is provided as well
_ = gameState.entity
}
}
要序列化/反序列化实体,你必须使分配给它们的组件符合 Codable
协议。
然后可以像这样按群组序列化符合要求的组件:
// MyComponent and YourComponent both conform to Component and Codable protocols.
let nexus = Nexus()
let family = nexus.family(requiresAll: MyComponent.self, YourComponent.self)
// JSON encode entities from given family.
var jsonEncoder = JSONEncoder()
let encodedData = try family.encodeMembers(using: &jsonEncoder)
// Decode entities into given family from JSON.
// The decoded entities will be added to the nexus.
var jsonDecoder = JSONDecoder()
let newEntities = try family.decodeMembers(from: jsonData, using: &jsonDecoder)
请查看 Fireblade ECS 演示应用 以开始使用。
请查阅 在线文档,或者在本地预览它:
make preview-docs
如果你想贡献,请先查看 贡献指南。
要在命令行中运行这些命令以开始你的项目贡献:
git clone git@github.com:fireblade-engine/ecs.git fireblade-ecs
cd fireblade-ecs
make setupEnvironment
在提交代码之前,请务必运行:
make precommit
此项目目前由 Christian Treffs 维护。
另请参阅参与此项目的 贡献者 列表。
此项目已获得 MIT 许可证的许可 - 有关详细信息,请参阅 LICENSE 文件
灵感来自: