Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing Games Using Data not Trees

4b138a56fa1e625a8b59a128519c7b64?s=47 Andrew Petersen
September 13, 2014
660

Developing Games Using Data not Trees

Focusing on the data of your game more than the object hierarchy allows you to more quickly modify core behavior without completely rewriting everything. In this talk, I show a few examples of how and why.

Keynote download (for animations/videos): https://github.com/kirbysayshi/pocket-ces/blob/master/doc/jsconfeu-2014/developing-games-using-data-not-trees.key.zip

4b138a56fa1e625a8b59a128519c7b64?s=128

Andrew Petersen

September 13, 2014
Tweet

Transcript

  1. Developing Games Using Data not Trees Drew Petersen @KirbySaysHi

  2. ONE MAN

  3. VS HIS OWN MIND

  4. 24 HOURS

  5. THIS

  6. IS

  7. A GAME JAM

  8. There is just one problem…

  9. ASTEROIDS:
 TOTALLY DIFFERENT THIS TIME

  10. is not actually different.

  11. Prepare for IMMEDIATE Internal Monologue

  12. Oh no! It’s not different at all! But it uses

    typed arrays and verlet physics and all this stuff that I had a lot of fun making!
  13. But the players don’t actually care about that… What am

    I going to do? There are only a few hours left before the deadline!
  14. –Every programmer under a deadline, ever. “Maybe I can quickly

    change it.”
  15. Maybe if the ship is huge?

  16. Maybe if the asteroids are tiny?

  17. –Me, to myself “Boring.”

  18. None
  19. What if…

  20. you…

  21. …control the asteroids?

  22. None
  23. None
  24. None
  25. How will I have time?

  26. Oh right. I modeled everything as data.

  27. Wait, what does that mean?

  28. Is that like a database?

  29. Databases are fun!

  30. SELECT * FROM boring LIMIT fun

  31. Databases are not a game.

  32. –Me in middle school “I heard you can capture a

    Databasei with a Master Ball”
  33. How does one data?

  34. Does data you?

  35. Data Matthews Band really replicates.

  36. Base into data without remorse

  37. OH GAWD I’VE DEVOLVED TO MEME

  38. None
  39. None
  40. None
  41. But seriously, how?

  42. Approach: Object Oriented

  43. Objects • Ship • Asteroid • Bullet

  44. ├ WorldEntity ├ PhysicsEntity ├ CollidableEntity ├ RenderableEntity ├ AsteroidEntity

    ├ WorldEntity ├ PhysicsEntity ├ CollidableEntity ├ RenderableEntity ├ BulletEntity
  45. ├ WorldEntity ├ PhysicsEntity ├ CollidableEntity ├ RenderableEntity ├ KeyboardCtrlEntity

    ├ ShipEntity update() (WorldE) handleCollisions() (CE) draw() (RenderableE) handleInput() (KBCE) shoot()
  46. What about controllable asteroids?

  47. ├ WorldEntity ├ PhysicsEntity ├ CollidableEntity ├ RenderableEntity ├ KeyboardCtrlEntity

    ├ ShipEntity update() (WorldE) handleCollisions() (CE) draw() (RenderableE) handleInput() (KBCE) shoot() AsteroidEntity
  48. DISCLAIMER: THE REAL WORLD IS WORSE

  49. ProCon: Professional Conventions for Professionals • Logic appears to be

    local • State is spread throughout the hierarchy • Hard to optimize (batch all physics, batch draw, etc)
  50. Approach: Data Oriented

  51. • Functions operate on data, not Objects. • There will

    always be more than one. • Don't inhibit optimization (batching).
  52. DISCLAIMER: IT’S NOT PERFECT But Might Be Better.

  53. Starts with a Pocket

  54. var pkt = new Pocket() pkt.tick(16); // ms

  55. We add Component Types to the Pocket

  56. –Ben Newman, probably “Components like in ReactJS?”

  57. NOPE

  58. pkt.componentType('rotation', function(cmp, opts) { cmp.angle = opts.angle || 0; cmp.rate

    = opts.rate || 0; }) ! // shortcut / alias pkt.cmpType('rotation', function(cmp, opts) { cmp.angle = opts.angle || 0; cmp.rate = opts.rate || 0; })
  59. pkt.cmpType('drag', function(cmp, opts) { cmp.percentage = opts.percentage || 0.99; })

  60. We request a Key from the Pocket

  61. var key = pkt.key({ 'rotation': { rate: 0.9 }, 'drag':

    null }) ! console.log(key); // 1 console.log(typeof key); // number
  62. pkt.key({ 'ship': null, 'human-controlled-01': null, 'verlet-position': { x: pkt.firstData('ctx-2d').center.x, y:

    pkt.firstData('ctx-2d').center.y }, 'rotation': { rate: 0.1 }, 'thrust': null, 'drag': null, 'projectile-launcher': { launchForce: 10 }, 'point-shape': { points: [ { x: size, y: 0 }, { x: -size, y: -size / 2 }, { x: -size, y: size / 2 } ]}, 'bbox': null })
  63. We write Systems to modify Component Data

  64. pkt.system('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, keys, positions, rotations, thrusts)

    { for (var i = 0; i < keys.length; i++) { // Do something } })
  65. pkt.system('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, keys, positions, rotations, thrusts)

    { for (var i = 0; i < keys.length; i++) { // Do something } }) Name
  66. pkt.system('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, keys, positions, rotations, thrusts)

    { for (var i = 0; i < keys.length; i++) { // Do something } }) Name Component Type Requirements
  67. pkt.system('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, keys, positions, rotations, thrusts)

    { for (var i = 0; i < keys.length; i++) { // Do something } }) Name Component Type Requirements Action
  68. pkt.system('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, keys, positions, rotations, thrusts)

    { for (var i = 0; i < keys.length; i++) { // Do something } }) Name Component Type Requirements Action Array of Keys that each have the required Component Datas
  69. pkt.system('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, keys, positions, rotations, thrusts)

    { for (var i = 0; i < keys.length; i++) { // Do something } }) Name Component Type Requirements Action Global Collection of Component Data by Key Array of Keys that each have the required Component Datas
  70. pkt.system( 'render-point-shape', ['verlet-position', 'point-shape', 'rotation'], function(pkt, keys, positions, shapes, rotations)

    { var ctx2d = pkt.firstData('ctx-2d') ! for (var i = 0; i < keys.length; i++) { var k = keys[i]; var position = positions[k]; var shape = shapes[k]; var rotation = rotations[k]; } } )
  71. pkt.system( 'render-point-shape', ['verlet-position', 'point-shape', 'rotation'], function(pkt, keys, positions, shapes, rotations)

    { var ctx2d = pkt.firstData('ctx-2d') ! for (var i = 0; i < keys.length; i++) { var k = keys[i]; var position = positions[k]; var shape = shapes[k]; var rotation = rotations[k]; } } ) Manually grab each component data from the global collections.
  72. pkt.systemForEach('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, key, position, rotation, thrust)

    { // Do Something })
  73. pkt.systemForEach('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, key, position, rotation, thrust)

    { // Do Something }) Individual Key
  74. pkt.systemForEach('input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, key, position, rotation, thrust)

    { // Do Something }) Component Data Matching the Key Individual Key
  75. –Maybe one person in the audience? “But what about player

    controlled asteroids?”
  76. Oh right.

  77. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } )
  78. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } )
  79. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } ) ?
  80. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } ) ? Why no human-controlled-01 component data?
  81. THERE IS NO human-controlled-01 COMPONENT

  82. We use Labels as anonymous components

  83. pkt.key({ 'asteroid': null, 'verlet-position': { x: x, y: y, acel:

    v2(acelX, acelY) }, 'point-shape': { points: points }, 'bbox': null, 'rotation': null }) There is no 'asteroid' component
  84. pkt.system( 'asteroid-ship-collider', ['point-shape', 'verlet-position', 'rotation', 'asteroid'], function(pkt, keys, shapes, positions,

    rotations) { })
  85. pkt.system( 'asteroid-ship-collider', ['point-shape', 'verlet-position', 'rotation', 'asteroid'], function(pkt, keys, shapes, positions,

    rotations) { }) We only want asteroids in here. Ship also has point-shape, verlet-position, and rotation
  86. None
  87. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } )
  88. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } )
  89. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } ) ?
  90. What happens if we add that label to the asteroids?

  91. pkt.key({ 'asteroid': null, 'human-controlled-01': null, 'verlet-position': { x: x, y:

    y, acel: v2(acelX, acelY) }, 'point-shape': { points: points }, 'bbox': null, 'rotation': null })
  92. Nothing?

  93. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } )
  94. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } )
  95. pkt.systemForEach( 'input-rotation', ['rotation', 'human-controlled-01'], function(pkt, key, rotation) { var input

    = pkt.firstData('keyboard-state'); ! if (input.down.RIGHT) { rotation.angle += rotation.rate; } else if (input.down.LEFT) { rotation.angle -= rotation.rate; } } )
  96. pkt.cmpType('rotation', function(cmp, opts) { cmp.angle = opts.angle || 0; cmp.rate

    = opts.rate || 0; })
  97. pkt.cmpType('rotation', function(cmp, opts) { cmp.angle = opts.angle || 0; cmp.rate

    = opts.rate || 0; })
  98. pkt.key({ 'asteroid': null, 'human-controlled-01': null, 'verlet-position': { x: x, y:

    y, acel: v2(acelX, acelY) }, 'point-shape': { points: points }, 'bbox': null, 'rotation': null })
  99. pkt.key({ 'asteroid': null, 'human-controlled-01': null, 'verlet-position': { x: x, y:

    y, acel: v2(acelX, acelY) }, 'point-shape': { points: points }, 'bbox': null, 'rotation': { rate: 0.1 } })
  100. They rotate!

  101. Let's make them thrust.

  102. pkt.systemForEach( 'input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, key, position, rotation,

    thrust) { var input = pkt.firstData('keyboard-state'); ! if (input.down.UP) { var x = Math.cos(rotation.angle) * thrust.force; var y = Math.sin(rotation.angle) * thrust.force; position.acel.x += x; position.acel.y += y; } } )
  103. pkt.systemForEach( 'input-thrust', ['verlet-position', 'rotation', 'thrust', 'human-controlled-01'], function(pkt, key, position, rotation,

    thrust) { var input = pkt.firstData('keyboard-state'); ! if (input.down.UP) { var x = Math.cos(rotation.angle) * thrust.force; var y = Math.sin(rotation.angle) * thrust.force; position.acel.x += x; position.acel.y += y; } } )
  104. 'human-controlled-01': null, 'verlet-position': { x: pkt.firstData('ctx-2d').center.x, y: pkt.firstData('ctx-2d').center.y }, 'rotation':

    { rate: 0.1 }, 'thrust': null, 'drag': null, 'projectile-launcher': { launchForce: 10 }, 'point-shape': { points: [ { x: size, y: 0 }, { x: -size, y: -size / 2 }, { x: -size, y: size / 2 } ]},
  105. pkt.key({ 'asteroid': null, 'human-controlled-01': null, 'thrust': null, 'verlet-position': { x:

    x, y: y, acel: v2(acelX, acelY) }, 'point-shape': { points: points }, 'bbox': null, 'rotation': { rate: 0.1 }, })
  106. Let's make them shoot!

  107. pkt.key({ 'asteroid': null, 'human-controlled-01': null, 'thrust': null, 'projectile-launcher': { launchForce:

    10 }, 'verlet-position': { x: x, y: y, acel: v2(acelX, acelY) }, 'point-shape': { points: points }, 'bbox': null, 'rotation': { rate: 0.1 }, })
  108. Let's make the ship get hit!

  109. pkt.system( 'asteroid-projectile-collider', ['point-shape', 'verlet-position', 'rotation', 'asteroid'], function(pkt, keys, shapes, positions,

    rotations) { var projectiles = pkt.keysMatching( 'point-shape', 'verlet-position', 'rotation', 'projectile'); // Actual collision code } ) This label is the only "asteroidish" thing about this system
  110. pkt.system( 'asteroid-projectile-collider', ['point-shape', 'verlet-position', 'rotation', 'ship'], function(pkt, keys, shapes, positions,

    rotations) { var projectiles = pkt.keysMatching( 'point-shape', 'verlet-position', 'rotation', 'projectile'); // Actual collision code } )
  111. THE HUNTER HAS BECOME THE HUNTED

  112. There is still more work to do!

  113. But not today.

  114. ProCon: Professional Conferences for Professionals • Logic is spread out

    • State is concentrated in components • Easier to optimize for batch drawing, batch collisions, etc. • Extremely modular
  115. I mentioned databases a long time ago.

  116. • ComponentTypes — Tables (properties are columns) • Components /

    Component Data — Rows • Keys — Primary Keys • Systems — Stored Procedures? (not really)
  117. • ComponentTypes — Tables (properties are columns) • Components /

    Component Data — Rows • Keys — Primary Keys • Systems — Stored Procedures? (not really)
  118. • ComponentTypes — Tables (properties are columns) • Components /

    Component Data — Rows • Keys — Primary Keys • Systems — Queries + Business Logic
  119. console.log(JSON.stringify(pkt.components))

  120. { "ctx-2d": { "1": { "cvs": {}, "ctx": {}, "center":

    { "x": 719.5, "y": 200.5 }, "width": 1439, "height": 401 } }, "game-config": {
  121. }, "verlet-position": { "4": { "cpos": { "x": 719.5, "y":

    397.5 }, "ppos": { "x": 719.5, "y": 397.5 }, "acel": { "x": 0, "y": 0 }
  122. } } }, "rotation": { "4": { "angle": 0, "rate":

    0.1 }, "5": { "angle": 0, "rate": 0.1 }, "6": { "angle": 0, "rate": 0.1
  123. None
  124. –Everyone? “But did you finish the game jam?”

  125. Prepare for IMMEDIATE Truth

  126. Sorry.

  127. There was no jam.

  128. It was a conceit of the talk.

  129. I hope that's alright.

  130. Thank You! ! @KirbySaysHi http://github.com/ kirbysayshi/pocket-ces

  131. Thank You! ! @KirbySaysHi http://github.com/ kirbysayshi/pocket-ces