Entity Component System is a software development pattern which has its roots in game development. The first use of this pattern dates to 1998. Even though it is almost 20 years old, the pattern is not widely known by non game programmers. I implemented two iOS Apps based on the pattern and would like to share my experiences and compare it to a more widely known patterns like MVC, MVVM, Rx and ReSwift.
2. History & Facts
4 First known game 1998 Thief: The Dark Project
4 My first encounter 2012 With ECS
4 Articles that convinced me "What is an entity system
framework for game development?" by Richard Lord
4 ObjectiveC library I used: Entitas
4 Two Apps I built with Entitas UIKonf-App & Resi App
5. What is a Component
public protocol Component {}
public protocol UniqueComopnent : Component {}
6. Component is a value type
struct TickComponent : UniqueComopnent{
let value : UInt64
}
struct ElixirComponent : UniqueComopnent{
let value : Float
}
struct ConsumeElixirComponent : Component{
let value : Int
}
struct PauseComponent : UniqueComopnent{}
struct JumpIntTimeComponent : UniqueComopnent{
let value : UInt64
}
7. Entity is a collection of components
public final class Entity {
private var _components : [ComponentId:Component]
...
}
e.set(ConsumeElixirComponent(value:2))
let amount = e.get(ConsumeElixirComponent.self)?.value
e.remove(ConsumeElixirComponent.self)
17. What is a System
public protocol ASystem : class {}
public protocol InitialiseSystem : ASystem {
func initialize()
}
public protocol ExecuteSystem : ASystem {
func execute()
}
public protocol CleanupSystem : ASystem {
func cleanup()
}
18. System Example
class TickUpdateSystem : InitialiseSystem, ExecuteSystem {
let ctx : Context
init(ctx : Context) {
self.ctx = ctx
}
func initialize() {
ctx.setUniqueEntityWith(TickComponent(value: 0))
}
func execute() {
guard ctx.hasUniqueComponent(PauseComponent.self) == false,
let currentTick = ctx.uniqueComponent(TickComponent.self)?.value else {
return
}
ctx.setUniqueEntityWith(TickComponent(value: currentTick + 1))
}
}
20. Reactive System
public protocol ReactiveSystem : ExecuteSystem {
var collector : Collector! {get set}
var limit : Int {get}
func execute(input : ArraySlice<Entity>)
}
public extension ReactiveSystem {
func execute(){
if let collector = collector {
let entities = collector.pull(limit)
if entities.count > 0 {
execute(input: entities)
}
}
}
var limit : Int { return -1 }
}
21. Reactive System Example
class ElixirProduceSystem : InitialiseSystem, ReactiveSystem {
var collector: Collector!
let ctx : Context
private let productionFrequency = 3
private let elixirCapacity : Float = 10
private let productionStep : Float = 0.01
private(set) var limit: Int = 1
init(ctx : Context) {
self.ctx = ctx
collector = Collector(group: ctx.entityGroup(TickComponent.matcher), changeType: .added)
}
func initialize() {
ctx.setUniqueEntityWith(ElixirComponent(value: 0))
}
func execute(input: ArraySlice<Entity>) {
guard let tick = input.first?.get(TickComponent.self)?.value,
(tick % UInt64(productionFrequency)) == 0,
let elixirAmount = ctx.uniqueComponent(ElixirComponent.self)?.value else{
return
}
let newAmount = min(elixirCapacity, elixirAmount + productionStep)
ctx.setUniqueEntityWith(ElixirComponent(value: newAmount))
}
}