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.

ECS architecture with Unity by example - Unite Europe 2016

3,882 views

Published on

Simon Schmid (Wooga) and Maxim Zaks explain how the introduction of strict ECS architecture in Unity helped them to achieve easy to test, robust and scalable game logic. It also helped them to extract this logic and run it on a server. At Unite Europe 2015 they introduced their Open Source project Entitas-CSharp (https://github.com/sschmid/Entitas-CSharp), which helped them achieve all the benefits they listed before. This year they present an example which explains how ECS and Unity can co-exist and empower developers to have a clean, scalable and testable architecture. They cover the following topics: User Input, Integration with Unity Collision System, Reactive UI, Re-Playable games

Published in: Software
  • Be the first to comment

ECS architecture with Unity by example - Unite Europe 2016

  1. 1. ECS Architecture with Unity // by Example Entitas // @entitas_csharp // github.com/sschmid/Entitas-CSharp Maxim Zaks // @icex33 // github.com/mzaks Simon Schmid // @s_schmid // github.com/sschmid
  2. 2. Unity pain points • Testability • Code sharing • Co-dependent logic • Querying • Deleting code
  3. 3. YouTube search Entitas Unitytinyurl.com/entitas
  4. 4. Entitas is open source github.com/sschmid/Entitas-CSharp
  5. 5. Agenda: • Introduction to Entitas • User Input • Integration with Unity Collision System • Replayable games • Integration with Unity UI System • Quizz
  6. 6. Unity Components: Data + Behaviour Entitas Components Data only
  7. 7. // Data only - no behaviour public class PositionComponent : IComponent { public Vector3 value; } public class MovableComponent : IComponent { }
  8. 8. +------------------+ | Pool | |------------------| | e e | +-----------+ | e e---|----> | Entity | | e e | |-----------| | e e e | | Component | | e e | | | +-----------+ | e e | | Component-|----> | Component | | e e e | | | |-----------| | e e e | | Component | | Data | +------------------+ +-----------+ +-----------+ | | | +-------------+ | | e | Groups | | e e | +---> | +------------+ | e | | | | e | e | e | +--------|----+ e | | e | | e e | +------------+
  9. 9. Logic?
  10. 10. public class VelocitySystem : IExecuteSystem { public void Execute() { var movables = Pools.pool.GetGroup( Matcher.AllOf( Matcher.Velocity, Matcher.Position )); foreach (var e in movables.GetEntities()) { var pos = e.position.value; e.ReplacePosition(pos + e.velocity.value); } } }
  11. 11. Systems Chain of Responsibility +------------+ +------------+ +------------+ +------------+ | | | | | | | | | System | +---> | System | +---> | System | +---> | System | | | | | | | | | +------------+ +------------+ +------------+ +------------+
  12. 12. Systems in Entitas • Initialize System • Execute System • Reactive System • only processes changed entities
  13. 13. Agenda: • Introduction to Entitas ✅ • User Input • Integration with Unity Collision System • Replayable games • Integration with Unity UI System • Quizz
  14. 14. Demo Shmup Shoot ’em up • Entitas Visual Debugging • Blueprints
  15. 15. public class InputController : MonoBehaviour { void Update() { var inputPool = Pools.input; // Move var moveX = Input.GetAxisRaw("Horizontal"); var moveY = Input.GetAxisRaw("Vertical"); inputPool.CreateEntity() .AddMoveInput(new Vector3(moveX, moveY)) .AddInputOwner("Player1"); // Shoot var fire = Input.GetAxisRaw("Fire1"); if (fire != 0) { inputPool.CreateEntity() .IsShootInput(true) .AddInputOwner("Player1"); } } }
  16. 16. public class CollisionEmitter : MonoBehaviour { public string targetTag; void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag(targetTag)) { var link = gameObject.GetEntityLink(); var targetLink = collision.gameObject.GetEntityLink(); Pools.input .CreateEntity() .AddCollision(link.entity, targetLink.entity); } } }
  17. 17. Great, but Performance?
  18. 18. Unity vs Entitas 1000 objects with 2 components • Memory: 9x (2.9 MB vs 0.32 MB) • CPU: 17x (105ms vs 6ms)
  19. 19. Entitas... • reuses Entities • reuses Components • caches Groups • can index Components
  20. 20. Entitas... • reuses Entities • reuses Components • caches Groups • can index Components so nice
  21. 21. Agenda: • Introduction to Entitas ✅ • User Input ✅ • Integration with Unity Collision System ✅ • Replayable games • Integration with Unity UI System • Quizz
  22. 22. Demo Reactive UI
  23. 23. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious. — Fred Brooks
  24. 24. [SingleEntity] public class TickComponent : IComponent { public long currentTick; } [SingleEntity] public class ElixirComponent : IComponent { public float amount; } public class ConsumeElixirComponent : IComponent { public int amount; } [SingleEntity] public class PauseComponent : IComponent {}
  25. 25. public class TickUpdateSystem : IInitializeSystem, IExecuteSystem, ISetPool { Pool _pool; public void SetPool(Pool pool) { _pool = pool; } public void Initialize() { _pool.ReplaceTick(0); } public void Execute() { if (!_pool.isPause) { _pool.ReplaceTick(_pool.tick.currentTick + 1); } } }
  26. 26. public class ProduceElixirSystem : IInitializeSystem, IReactiveSystem, ISetPool { public TriggerOnEvent trigger { get { return Matcher.Tick.OnEntityAdded(); }} public void Initialize() { _pool.ReplaceElixir(0); } public void Execute(List<Entity> entities) { var newAmount = _pool.elixir.amount + ProductionStep; newAmount = Math.Min(ElixirCapacity, newAmount); _pool.ReplaceElixir(newAmount); } }
  27. 27. public class ConsumeButtonBehaviour : MonoBehaviour, IPauseListener, IElixirListener { //... public void ButtonPressed() { if(Pools.pool.isPause) return; Pools.pool.CreateEntity().AddConsumeElixir(consumptionAmount); } }
  28. 28. [SingleEntity] public class ConsumptionHistoryComponent : IComponent { public List<ConsumptionEntry> entries; } public class ConsumptionEntry { public readonly long tick; public readonly int amount; public ConsumptionEntry(long tick, int amount) { this.tick = tick; this.amount = amount; } }
  29. 29. public class JumpInTimeComponent : IComponent { public long targetTick; } public class TimePickerBehaviour : MonoBehaviour, IPauseListener { // ... public void ChangedValue() { Pools.pool.ReplaceJumpInTime((long)GetComponent<Slider>().value); } }
  30. 30. ReplaySystem for tick: 0..< targetTick
  31. 31. How do we get a reference to Logic Systems?
  32. 32. Dependency managment with Entitas [SingleEntity] public class LogicSystemsComponent : IComponent { public Systems systems; } pool.SetLogicSystems(new Systems() .Add(pool.CreateSystem<TickUpdateSystem>()) .Add(pool.CreateSystem<ProduceElixirSystem>()) .Add(pool.CreateSystem<ConsumeElixirSystem>()) .Add(pool.CreateSystem<PersistConsumeElixirSystem>()) .Add(pool.CreateSystem<ConsumeElixirCleanupSystem>()));
  33. 33. UI notification systems • NotifyTickListenersSystem • NotifyPauseListenersSystem • NotifyElixirListenersSystem
  34. 34. public class NotifyTickListenersSystem : IReactiveSystem, ISetPool { Group listeners; public TriggerOnEvent trigger { get { return Matcher.Tick.OnEntityAddedOrRemoved(); } } public void SetPool(Pool pool) { listeners = pool.GetGroup(Matcher.TickListener); } public void Execute(List<Entity> entities) { var e = entities[0]; foreach (var entity in listeners.GetEntities()) { entity.tickListener.value.TickChanged(e.tick.currentTick); } } }
  35. 35. public interface ITickListener { void TickChanged(long currentTick); } public interface IPauseListener { void PauseStateChanged(bool isPaused); } public interface IElixirListener { void ElixirAmountChanged(float amount); }
  36. 36. Quiz!
  37. 37. Tick Listener?
  38. 38. Tick Listener!
  39. 39. Elixir Listener?
  40. 40. Elixir Listener!
  41. 41. Pause Listener?
  42. 42. Pause Listener!
  43. 43. public class ConsumeButtonBehaviour : MonoBehaviour, IPauseListener, IElixirListener { void Awake() { Pools.pool.CreateEntity() .AddPauseListener(this) .AddElixirListener(this); } public void PauseStateChanged (bool isPaused) { GetComponent<Button>().enabled = !isPaused; } public void ElixirAmountChanged (float amount) { var ratio = 1 - Mathf.Min(1f, (amount / (float)consumptionAmount)); progressBox.fillAmount = ratio; GetComponent<Button>().enabled = (System.Math.Abs (ratio - 0) < Mathf.Epsilon); } }
  44. 44. Recap • Control your input, even if it is time • Input is a state change • Simulation is state change over time • State change drives UI updates • Persist only relevant parts of state for replay • Use Entitas to manage dependencies
  45. 45. Agenda: • Introduction to Entitas ✅ • User Input ✅ • Integration with Unity Collision System ✅ • Replayable games ✅ • Integration with Unity UI System ✅ • Quizz ✅
  46. 46. Bonus Thought How hard is it to implement: • Multiplayer game • BackEnd validation • Tutorial and Quest systems • Any kind of Logging
  47. 47. Questions? #Entitas @entitas_csharp github.com/sschmid/Entitas-CSharp

×