Fireblade ECS (实体组件系统)

license macOS Linux Windows WASM documentation
codecov spi-swift-versions spi-swift-platforms

这是一个无依赖轻量级快速易于使用的 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"])
    ]
)

📝 代码示例

🏛️ Nexus (枢纽)

Fireblade-ECS 中的核心元素是 Nexus (枢纽)。它充当存储、访问和管理实体及其组件的中心化方式。单个 Nexus 可以(理论上)同时容纳最多 4294967295 个 Entities(实体)。
你可以同时使用多个 Nexus

使用以下代码初始化一个 Nexus

let nexus = Nexus()

👤 实体 (Entities)

然后通过让 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()
}

👪 群组 (Families)

此 ECS 使用分组方法,将具有相同组件类型的实体分组,以优化缓存局部性并简化对它们的访问。
具有相同组件类型的实体可能属于一个 Family(群组)。一个 Family 拥有实体作为成员,以及组件类型作为群组特征。

通过在 Nexus 上使用一组特征调用 .family 来创建一个群组。以下代码创建了一个仅包含具有 MovementPlayerInput 组件,但没有 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
		}
	}
}

🧑 单例 (Singles)

另一方面,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
    }
}

🔗 序列化 (Serialization)

要序列化/反序列化实体,你必须使分配给它们的组件符合 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)

🧪 演示 (Demo)

请查看 Fireblade ECS 演示应用 以开始使用。

📖 文档 (Documentation)

请查阅 在线文档,或者在本地预览它:

💁 如何贡献 (How to contribute)

如果你想贡献,请先查看 贡献指南

要在命令行中运行这些命令以开始你的项目贡献:

  1. git clone git@github.com:fireblade-engine/ecs.git fireblade-ecs
  2. cd fireblade-ecs
  3. make setupEnvironment

在提交代码之前,请务必运行:

此项目目前由 Christian Treffs 维护。
另请参阅参与此项目的 贡献者 列表。

🔏 许可证 (License)

此项目已获得 MIT 许可证的许可 - 有关详细信息,请参阅 LICENSE 文件

🙏 鸣谢 (Acknowledgments)

灵感来自: