Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Entity Component System - for App developers

586 views

Published on

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.

Published in: Education

Entity Component System - for App developers

  1. 1. Entity Component System for App developers @iceX33
  2. 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
  3. 3. DEMO
  4. 4. Entity | Component | System ----> State <------> Behaviour
  5. 5. What is a Component public protocol Component {} public protocol UniqueComopnent : Component {}
  6. 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. 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)
  8. 8. Components = Lego pieces Entites = things you build with them
  9. 9. let ctx = Context() let e = ctx.createEntity() e.set(NameComponent(value:"Maxim")) let g = ctx.getEntityGroup(NameComponent.matcher) assert(g.count == 1) e.set(NameComponent(value:"Leo"), overwrite:true) assert(g.count == 1) e.remove(NameComponent.self) assert(g.count == 0)
  10. 10. What about behaviour
  11. 11. 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() }
  12. 12. 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)) } }
  13. 13. Reactive System
  14. 14. 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 } }
  15. 15. 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)) } }
  16. 16. What about UI
  17. 17. UI Action @IBAction func pauseResume(_ sender: UIButton) { if ctx.hasUniqueComponent(PauseComponent.self) { ctx.destroyUniqueEntity(PauseComponent.matcher) } else { ctx.setUniqueEntityWith(PauseComponent()) } } @IBAction func consumeAction(_ sender: UIButton) { ctx.createEntity().set(ConsumeElixirComponent(value: sender.tag)) } @IBAction func timeTravel(_ sender: UISlider) { ctx.setUniqueEntityWith(JumpIntTimeComponent(value: UInt64(sender.value))) }
  18. 18. What about Reactive UI
  19. 19. protocol TickListener { func tickChanged(tick : UInt64) } struct TickListenerComponent : Component { let ref : TickListener } protocol PauseListener { func pauseStateChanged(paused : Bool) } struct PauseListenerComponent : Component { let ref : PauseListener } protocol ElixirListener { func elixirChanged(amount : Float) } struct ElixirListenerComponent : Component { let ref : ElixirListener }
  20. 20. ViewController is a listener class ViewController: UIViewController, TickListener, ElixirListener, PauseListener { ... override func viewDidLoad() { ctx.createEntity() .set(TickListenerComponent(ref: self)) .set(ElixirListenerComponent(ref: self)) .set(PauseListenerComponent(ref: self)) } ... }
  21. 21. Or if you like it less monolithic struct ConsumeButtonController : PauseListener, ElixirListener { let consumeButton : UIButton let consumeButtonProgress: UIProgressView let ctx : Context func pauseStateChanged(paused: Bool) { consumeButton.isEnabled = !paused } func elixirChanged(amount: Float) { let paused = ctx.hasUniqueComponent(PauseComponent.self) consumeButton.isEnabled = consumeButton.tag <= Int(amount) && !paused consumeButtonProgress.progress = 1 - min(1, (amount / Float(consumeButton.tag))) } }
  22. 22. Reactive System + UIKit class NotifyPauseListenersSystem : ReactiveSystem { var collector: Collector! let ctx : Context var limit: Int = 1 let listeners : Group init(ctx : Context) { self.ctx = ctx collector = Collector(group: ctx.entityGroup(PauseComponent.matcher), changeType: .addedAndRemoved) listeners = ctx.entityGroup(PauseListenerComponent.matcher) } func execute(input: ArraySlice<Entity>) { let paused = ctx.hasUniqueComponent(PauseComponent.self) for e in listeners { e.get(PauseListenerComponent.self)? .ref.pauseStateChanged(paused: paused) } } }
  23. 23. Show me the code & tests
  24. 24. ECS vs. MVC | MVVM | Rx | ReSwift
  25. 25. Bonus material
  26. 26. Questions? @iceX33
  27. 27. Thank you!

×