Creating games with entities and components

Creating games with entities and components

Ruby is an object-oriented language, and that is the paradigm we mainly use to write software.

Object orientation is not always the best solution for some situations. In real-time simulations, such as real-time games, techniques such as inheritance, modules, traits, etc break down.

This talk explains the entity-component-system architecture, a common pattern in modern game architecture, which explicitly breaks away from object orientation to achieve large gains in flexibility and speed.

Be732ee41fd3038aa98a0a7e7b7be081?s=128

Denis Defreyne

December 04, 2014
Tweet

Transcript

  1. 3.

    3

  2. 4.

    4

  3. 5.

    5

  4. 6.

    6

  5. 9.

    9

  6. 11.

    If all you have is the data,
 you know what

    it’s about.
 If all you have is the behavior,
 you’re still in the dark. 11
  7. 23.

    class Spaceship def update # Move @position_x += @velocity_x @position_y

    += @velocity_y # Calculate acceleration @acceleration = 0.0 if Keyboard.key_down?('w') @acceleration = 10.0 # TODO: Play accelerate sound elsif Keyboard.key_down?('s') @acceleration = -3.0 end # … end end 23
  8. 24.

    class Spaceship def update # Move @position_x += @velocity_x @position_y

    += @velocity_y # Calculate acceleration @acceleration = 0.0 if Keyboard.key_down?('w') @acceleration = 10.0 # TODO: Play accelerate sound elsif Keyboard.key_down?('s') @acceleration = -3.0 end # Rotate if Keyboard.key_down?('a') @rotation -= 5 elsif Keyboard.key_down?('d') @rotation += 5 end # TODO: Add rotational velocity # … end end 24
  9. 25.

    class Spaceship def update # Move @position_x += @velocity_x @position_y

    += @velocity_y # Calculate acceleration @acceleration = 0.0 if Keyboard.key_down?('w') @acceleration = 10.0 # TODO: Play accelerate sound elsif Keyboard.key_down?('s') @acceleration = -3.0 end # Rotate if Keyboard.key_down?('a') @rotation -= 5 elsif Keyboard.key_down?('d') @rotation += 5 end # TODO: Add rotational velocity # Accelerate @velocity_x += Math.cos(@rotation) * @acceleration @velocity_y += Math.sin(@rotation) * @acceleration # … end end 25
  10. 26.

    class Spaceship def update # Move @position_x += @velocity_x @position_y

    += @velocity_y # Calculate acceleration @acceleration = 0.0 if Keyboard.key_down?('w') @acceleration = 10.0 # TODO: Play accelerate sound elsif Keyboard.key_down?('s') @acceleration = -3.0 end # Rotate if Keyboard.key_down?('a') @rotation -= 5 elsif Keyboard.key_down?('d') @rotation += 5 end # TODO: Add rotational velocity # Accelerate @velocity_x += Math.cos(@rotation) * @acceleration @velocity_y += Math.sin(@rotation) * @acceleration # Cap speed @velocity_x = [@velocity_x, @max_velocity_x].min @velocity_y = [@velocity_y, @max_velocity_y].min # … end end 26
  11. 27.

    class Spaceship def update # Move @position_x += @velocity_x @position_y

    += @velocity_y # Calculate acceleration @acceleration = 0.0 if Keyboard.key_down?('w') @acceleration = 10.0 # TODO: Play accelerate sound elsif Keyboard.key_down?('s') @acceleration = -3.0 end # Rotate if Keyboard.key_down?('a') @rotation -= 5 elsif Keyboard.key_down?('d') @rotation += 5 end # TODO: Add rotational velocity # Accelerate @velocity_x += Math.cos(@rotation) * @acceleration @velocity_y += Math.sin(@rotation) * @acceleration # Cap speed @velocity_x = [@velocity_x, @max_velocity_x].min @velocity_y = [@velocity_y, @max_velocity_y].min # Render Graphics.translate(@position_x, @position_y) do Graphics.rotate(@rotation) do @sprite.draw end end end end 27
  12. 28.

    class Spaceship def update # Move @position_x += @velocity_x @position_y

    += @velocity_y # Calculate acceleration @acceleration = 0.0 if Keyboard.key_down?('w') @acceleration = 10.0 # TODO: Play accelerate sound elsif Keyboard.key_down?('s') @acceleration = -3.0 end # Rotate if Keyboard.key_down?('a') @rotation -= 5 elsif Keyboard.key_down?('d') @rotation += 5 end # TODO: Add rotational velocity # Accelerate @velocity_x += Math.cos(@rotation) * @acceleration @velocity_y += Math.sin(@rotation) * @acceleration # Cap speed @velocity_x = [@velocity_x, @max_velocity_x].min @velocity_y = [@velocity_y, @max_velocity_y].min # Render Graphics.translate(@position_x, @position_y) do Graphics.rotate(@rotation) do if @acceleration > 0.0 @flame_animation.step @flame_sprite = @flame_animation.sprite @flame_sprite.draw end @sprite.draw end end end end 28
  13. 31.

    31

  14. 35.

    class Spaceship attr_reader :position_x, :position_y attr_reader :velocity_x, :velocity_y attr_reader :acceleration_x,

    :acceleration_y attr_reader :rotation attr_reader :shield_cur, :shield_max, :shield_rate attr_reader :armor_cur, :armor_max def initialize(params = {}) … end end 35
  15. 39.

    A system is essentially a procedure* that is called thirty

    times per second. 39 * A function with only side effects
  16. 41.

    spaceship = Spaceship.new(
 position_x: 200,
 position_y: 150,
 velocity_x: 10,
 velocity_y:

    -5,
 armor_cur: 100,
 armor_max: 100)
 movement_system = MovementSystem.new movement_system.update(spaceship)
 p [spaceship.position_x, spaceship.position_y] # => [210, 145] 41
  17. 46.

    class Spaceship attr_reader :position_x, :position_y attr_reader :velocity_x, :velocity_y attr_reader :acceleration_x,

    :acceleration_y attr_reader :rotation attr_reader :shield_cur, :shield_max, :shield_rate attr_reader :armor_cur, :armor_max def initialize(params = {}) … end end 46
  18. 47.

    class Spaceship attr_reader :position attr_reader :velocity attr_reader :acceleration attr_reader :rotation

    attr_reader :shield attr_reader :armor def initialize(params = {}) … end end 47
  19. 48.

    Position = Struct.new(:x, :y) Velocity = Struct.new(:x, :y) Acceleration =

    Struct.new(:x, :y) Rotation = Struct.new(:rad) Shield = Struct.new(:cur, :max, :rate) Armor = Struct.new(:cur, :max) 48
  20. 51.

    class Spaceship attr_reader :position attr_reader :velocity attr_reader :acceleration attr_reader :rotation

    attr_reader :shield attr_reader :armor def initialize(params = {}) … end end 51
  21. 58.

    # Become invisible spaceship.remove(Sprite) # Become mortal angel.add(Health.new(100)) # Become

    a ghost asteroid.remove(CollisionShape) # Mind control enemy enemy.remove(AISteering) enemy.add(PlayerSteering) 58
  22. 60.
  23. 61.

    61

  24. 62.

    62

  25. 65.

    65 position velocity 14 used bytes / 32 total bytes

    = 44% efficiency (for movement system) rotation armor shield
  26. 66.

    66

  27. 67.

    67

  28. 69.

    positions[47] = Position.new(400, 200) velocities[47] = Velocity.new(0, 0) shields[47] =

    Shield.new(cur: 100, max: 100, rate: 2) armors[47] = Armor.new(cur: 50, max: 50) 69
  29. 70.

    Smart use of the CPU cache can lead to a

    50x speedup! 70 * Source: Game Programming Patterns by Robert Nystrom
  30. 74.

    { "position": [400, 200], "velocity": [0, 0], "shield": {"cur": 100,

    "max": 100, "rate": 2}, "armor": {"cur": 50, "max": 50} } 74
  31. 76.

    76

  32. 78.
  33. 80.

    80 This talk would not have been the same without

    some great assets that I could use, either for free or for a well-deserved donation. The fonts in this presentation are Clear Sans by Intel (01.org/clear-sans) and Ubuntu Mono by Canonical Ltd (font.ubuntu.com). Most of the sprites are by Kenney Vleugels (kenney.nl) and are part of the Kenney Donation Pack (kenney.itch.io/kenney-donation). The planet sprite in the 2D space shooter example is by Justin Nichol. Assets in the initial screenshot are by Sven Ahlgrimm.