-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
directly porting README getting started into an Essentials article
- Loading branch information
Showing
2 changed files
with
215 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
Sources/FirebladeECS/Documentation.docc/GettingStartedWithFirebladeECS.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
# Getting started with Fireblade ECS | ||
|
||
Learn the API and key types Fireblade provides to compose your game or app logic. | ||
|
||
## Overview | ||
|
||
Fireblade ECS is a dependency free, Swift language implementation of an Entity-Component-System ([ECS](https://en.wikipedia.org/wiki/Entity_component_system)). | ||
An ECS comprises entities composed from components of data, with systems which operate on the components. | ||
|
||
Extend the following lines in your `Package.swift` file or use it to create a new project. | ||
|
||
```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"]) | ||
] | ||
) | ||
|
||
``` | ||
|
||
This article introduces you to the key concepts of Fireblade ECS's API. | ||
For a more detailed example, see the [Fireblade ECS Demo App](https://github.com/fireblade-engine/ecs-demo). | ||
|
||
### 🏛️ Nexus | ||
|
||
The core element in the Fireblade-ECS is the [Nexus](https://en.wiktionary.org/wiki/nexus#Noun). | ||
It acts as a centralized way to store, access and manage entities and their components. | ||
A single `Nexus` may (theoretically) hold up to 4294967295 `Entities` at a time. | ||
You may use more than one `Nexus` at a time. | ||
|
||
Initialize a `Nexus` with | ||
|
||
```swift | ||
let nexus = Nexus() | ||
``` | ||
|
||
### 👤 Entities | ||
|
||
then create entities by letting the `Nexus` generate them. | ||
|
||
```swift | ||
// an entity without components | ||
let newEntity = nexus.createEntity() | ||
``` | ||
|
||
To define components, conform your class to the `Component` protocol | ||
|
||
```swift | ||
final class Position: Component { | ||
var x: Int = 0 | ||
var y: Int = 0 | ||
} | ||
``` | ||
and assign instances of it to an `Entity` with | ||
|
||
```swift | ||
let position = Position(x: 1, y: 2) | ||
entity.assign(position) | ||
``` | ||
|
||
You can be more efficient by assigning components while creating an entity. | ||
|
||
```swift | ||
// 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 | ||
|
||
This ECS uses a grouping approach for entities with the same component types to optimize cache locality and ease up access to them. | ||
Entities with the __same component types__ may belong to one `Family`. | ||
A `Family` has entities as members and component types as family traits. | ||
|
||
Create a family by calling `.family` with a set of traits on the nexus. | ||
A family that contains only entities with a `Movement` and `PlayerInput` component, but no `Texture` component is created by | ||
|
||
```swift | ||
let family = nexus.family(requiresAll: Movement.self, PlayerInput.self, | ||
excludesAll: Texture.self) | ||
``` | ||
|
||
These entities are cached in the nexus for efficient access and iteration. | ||
Families conform to the [Sequence](https://developer.apple.com/documentation/swift/sequence) protocol so that members (components) | ||
may be iterated and accessed like any other sequence in Swift. | ||
Access a family's components directly on the family instance. To get family entities and access components at the same time call `family.entityAndComponents`. | ||
If you are only interested in a family's entities call `family.entities`. | ||
|
||
```swift | ||
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 | ||
|
||
A `Single` on the other hand is a special kind of family that holds exactly **one** entity with exactly **one** component for the entire lifetime of the Nexus. This may come in handy if you have components that have a [Singleton](https://en.wikipedia.org/wiki/Singleton_(mathematics)) character. Single components must conform to the `SingleComponent` protocol and will not be available through regular family iteration. | ||
|
||
```swift | ||
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 | ||
|
||
|
||
To serialize/deserialize entities you must conform their assigned components to the `Codable` protocol. | ||
Conforming components can then be serialized per family like this: | ||
|
||
```swift | ||
// 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) | ||
|
||
``` | ||
|