rgame 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +228 -0
  3. data/README.md +142 -243
  4. data/docs/api/README.md +141 -90
  5. data/docs/api/app.md +125 -68
  6. data/docs/api/assets.md +203 -166
  7. data/docs/api/audio.md +130 -89
  8. data/docs/api/cli.md +259 -0
  9. data/docs/api/components.md +1045 -254
  10. data/docs/api/drawing.md +187 -119
  11. data/docs/api/examples.md +263 -0
  12. data/docs/api/game.md +165 -45
  13. data/docs/api/images.md +39 -39
  14. data/docs/api/input.md +344 -85
  15. data/docs/api/internals.md +241 -62
  16. data/docs/api/localization.md +285 -0
  17. data/docs/api/scene_graph.md +503 -123
  18. data/docs/api/signals.md +81 -76
  19. data/docs/api/systems.md +197 -64
  20. data/docs/api/text.md +58 -41
  21. data/docs/api/tile_maps.md +237 -0
  22. data/docs/api/toolbox.md +400 -147
  23. data/docs/api/ui.md +927 -0
  24. data/docs/api/values.md +234 -19
  25. data/examples/assets/README.md +322 -0
  26. data/examples/assets/blip.ogg +0 -0
  27. data/examples/assets/glyphs.json +5 -0
  28. data/examples/assets/glyphs.png +0 -0
  29. data/examples/assets/hero.json +12 -0
  30. data/examples/assets/hero.png +0 -0
  31. data/examples/assets/icons.json +13 -0
  32. data/examples/assets/icons.png +0 -0
  33. data/examples/assets/music.ogg +0 -0
  34. data/examples/assets/skills.json +10 -0
  35. data/examples/assets/skills.png +0 -0
  36. data/examples/assets/tileset.png +0 -0
  37. data/examples/assets/tileset.tsx +65 -0
  38. data/examples/assets/town.tmx +26 -0
  39. data/examples/assets/ui.json +11 -0
  40. data/examples/assets/ui.png +0 -0
  41. data/examples/collision/locales/en.yml +8 -0
  42. data/examples/collision/main.rb +316 -0
  43. data/examples/collision_tiles/locales/en.yml +9 -0
  44. data/examples/collision_tiles/main.rb +274 -0
  45. data/examples/fullscreen/locales/en.yml +10 -0
  46. data/examples/fullscreen/main.rb +216 -0
  47. data/examples/game_menu/locales/en.yml +8 -0
  48. data/examples/game_menu/main.rb +170 -0
  49. data/examples/input_glyphs/locales/en.yml +14 -0
  50. data/examples/input_glyphs/main.rb +213 -0
  51. data/examples/jump_topdown/locales/en.yml +9 -0
  52. data/examples/jump_topdown/main.rb +178 -0
  53. data/examples/localization/locales/de.yml +12 -0
  54. data/examples/localization/locales/en.yml +13 -0
  55. data/examples/localization/main.rb +158 -0
  56. data/examples/menu_navigation/locales/en.yml +23 -0
  57. data/examples/menu_navigation/main.rb +365 -0
  58. data/examples/music/locales/en.yml +7 -0
  59. data/examples/music/main.rb +134 -0
  60. data/examples/pathfinding/locales/en.yml +17 -0
  61. data/examples/pathfinding/main.rb +298 -0
  62. data/examples/pooling/locales/en.yml +7 -0
  63. data/examples/pooling/main.rb +259 -0
  64. data/examples/quick_wheel/locales/en.yml +16 -0
  65. data/examples/quick_wheel/main.rb +184 -0
  66. data/examples/radial_menu/locales/en.yml +16 -0
  67. data/examples/radial_menu/main.rb +184 -0
  68. data/examples/save_load/locales/en.yml +11 -0
  69. data/examples/save_load/main.rb +207 -0
  70. data/examples/save_load_ids/locales/en.yml +11 -0
  71. data/examples/save_load_ids/main.rb +322 -0
  72. data/examples/scroll_map/locales/en.yml +4 -0
  73. data/examples/scroll_map/main.rb +140 -0
  74. data/examples/signals/locales/en.yml +6 -0
  75. data/examples/signals/main.rb +278 -0
  76. data/examples/skill_bar/locales/en.yml +14 -0
  77. data/examples/skill_bar/main.rb +159 -0
  78. data/examples/sound/locales/en.yml +6 -0
  79. data/examples/sound/main.rb +122 -0
  80. data/examples/split_screen/locales/en.yml +9 -0
  81. data/examples/split_screen/main.rb +304 -0
  82. data/examples/sprite/locales/en.yml +8 -0
  83. data/examples/sprite/main.rb +180 -0
  84. data/examples/timer/locales/en.yml +12 -0
  85. data/examples/timer/main.rb +273 -0
  86. data/examples/velocity/locales/en.yml +6 -0
  87. data/examples/velocity/main.rb +196 -0
  88. data/examples/walk/locales/en.yml +4 -0
  89. data/examples/walk/main.rb +99 -0
  90. data/exe/rgame +9 -0
  91. data/ext/README.md +6 -5
  92. data/ext/rgame_core/app/app.c +215 -11
  93. data/ext/rgame_core/app/locale.c +67 -0
  94. data/ext/rgame_core/app/locale.h +28 -0
  95. data/ext/rgame_core/audio/audio.c +113 -2
  96. data/ext/rgame_core/example.rb +18 -56
  97. data/ext/rgame_core/extconf.rb +16 -113
  98. data/ext/rgame_core/graphics/canvas.c +45 -4
  99. data/ext/rgame_core/graphics/canvas.h +65 -10
  100. data/ext/rgame_core/graphics/clip.c +22 -13
  101. data/ext/rgame_core/include/rgame/core.h +151 -5
  102. data/ext/rgame_core/input/gamepad.c +57 -3
  103. data/ext/rgame_core/ruby/audio_ext.c +10 -5
  104. data/ext/rgame_core/ruby/core_ext.c +46 -7
  105. data/ext/rgame_core/ruby/core_ext.h +3 -0
  106. data/ext/rgame_core/ruby/locale_ext.c +44 -0
  107. data/ext/rgame_core/ruby/recording_ext.c +1 -1
  108. data/ext/rgame_core/ruby/renderer_ext.c +42 -19
  109. data/ext/rgame_util/color_ext.c +12 -3
  110. data/ext/rgame_util/extconf.rb +2 -20
  111. data/ext/rgame_util/route_search.c +305 -0
  112. data/ext/rgame_util/route_search.h +86 -0
  113. data/ext/rgame_util/route_search_ext.c +150 -0
  114. data/ext/rgame_util/solid_grid.c +58 -0
  115. data/ext/rgame_util/solid_grid.h +49 -0
  116. data/ext/rgame_util/solid_grid_ext.c +161 -0
  117. data/ext/rgame_util/tile_sweep.c +164 -0
  118. data/ext/rgame_util/tile_sweep.h +62 -0
  119. data/ext/rgame_util/tile_sweep_ext.c +155 -0
  120. data/ext/rgame_util/util_ext.c +3 -0
  121. data/ext/rgame_util/util_ext.h +15 -0
  122. data/lib/rgame/boot.rb +0 -10
  123. data/lib/rgame/cli/new_project.rb +139 -0
  124. data/lib/rgame/cli/templates/Gemfile.tt +23 -0
  125. data/lib/rgame/cli/templates/README.md.tt +93 -0
  126. data/lib/rgame/cli/templates/Rakefile.tt +9 -0
  127. data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
  128. data/lib/rgame/cli/templates/game.rb.tt +23 -0
  129. data/lib/rgame/cli/templates/gitignore.tt +12 -0
  130. data/lib/rgame/cli/templates/main.rb.tt +11 -0
  131. data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
  132. data/lib/rgame/cli/templates/rspec.tt +2 -0
  133. data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
  134. data/lib/rgame/cli/templates/ruby-version.tt +1 -0
  135. data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
  136. data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
  137. data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
  138. data/lib/rgame/cli.rb +66 -0
  139. data/lib/rgame/core/app.rb +6 -42
  140. data/lib/rgame/core/asset_manager.rb +13 -31
  141. data/lib/rgame/core/audio.rb +37 -16
  142. data/lib/rgame/core/font.rb +0 -3
  143. data/lib/rgame/core/input.rb +35 -41
  144. data/lib/rgame/core/locale.rb +22 -0
  145. data/lib/rgame/core/nine_slice.rb +0 -21
  146. data/lib/rgame/core/recording.rb +3 -1
  147. data/lib/rgame/core/renderer.rb +75 -84
  148. data/lib/rgame/core/sprite_sheet.rb +0 -3
  149. data/lib/rgame/core/tile_map_renderer.rb +77 -65
  150. data/lib/rgame/core/ui_atlas.rb +28 -13
  151. data/lib/rgame/core.rb +1 -8
  152. data/lib/rgame/engine/actor_blockers.rb +131 -0
  153. data/lib/rgame/engine/animation_set.rb +1 -0
  154. data/lib/rgame/engine/audio_director.rb +36 -6
  155. data/lib/rgame/engine/bounds_blockers.rb +74 -0
  156. data/lib/rgame/engine/camera.rb +55 -10
  157. data/lib/rgame/engine/circle_collider.rb +4 -2
  158. data/lib/rgame/engine/collision_box.rb +26 -1
  159. data/lib/rgame/engine/collision_system.rb +110 -22
  160. data/lib/rgame/engine/component.rb +35 -1
  161. data/lib/rgame/engine/components/action_trigger.rb +0 -1
  162. data/lib/rgame/engine/components/animated_sprite.rb +31 -23
  163. data/lib/rgame/engine/components/box_collider.rb +99 -0
  164. data/lib/rgame/engine/components/camera_follow.rb +45 -0
  165. data/lib/rgame/engine/components/character_body.rb +21 -41
  166. data/lib/rgame/engine/components/circle_collider.rb +47 -11
  167. data/lib/rgame/engine/components/collision_world.rb +159 -31
  168. data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
  169. data/lib/rgame/engine/components/feet_collider.rb +61 -0
  170. data/lib/rgame/engine/components/hop.rb +76 -0
  171. data/lib/rgame/engine/components/identity.rb +73 -0
  172. data/lib/rgame/engine/components/mover.rb +285 -0
  173. data/lib/rgame/engine/components/navigator.rb +145 -0
  174. data/lib/rgame/engine/components/path_follow.rb +123 -31
  175. data/lib/rgame/engine/components/player_controller.rb +5 -2
  176. data/lib/rgame/engine/components/pool.rb +1 -1
  177. data/lib/rgame/engine/components/screen_wrap.rb +33 -11
  178. data/lib/rgame/engine/components/sprite.rb +22 -6
  179. data/lib/rgame/engine/components/targeting.rb +9 -11
  180. data/lib/rgame/engine/components/thrust_controller.rb +1 -1
  181. data/lib/rgame/engine/components/tile_world.rb +70 -29
  182. data/lib/rgame/engine/components/timer.rb +1 -1
  183. data/lib/rgame/engine/components/velocity.rb +23 -7
  184. data/lib/rgame/engine/components/wander_controller.rb +6 -2
  185. data/lib/rgame/engine/components/world.rb +133 -0
  186. data/lib/rgame/engine/contact_set.rb +74 -0
  187. data/lib/rgame/engine/culling.rb +45 -0
  188. data/lib/rgame/engine/debug_overlay.rb +23 -18
  189. data/lib/rgame/engine/i18n/plural.rb +45 -0
  190. data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
  191. data/lib/rgame/engine/i18n/template.rb +59 -0
  192. data/lib/rgame/engine/i18n.rb +276 -51
  193. data/lib/rgame/engine/input/action_mapper.rb +76 -22
  194. data/lib/rgame/engine/input/actions.rb +63 -12
  195. data/lib/rgame/engine/input/input_map.rb +196 -0
  196. data/lib/rgame/engine/layout.rb +82 -0
  197. data/lib/rgame/engine/nav_grid.rb +87 -0
  198. data/lib/rgame/engine/node2d.rb +364 -80
  199. data/lib/rgame/engine/path.rb +4 -6
  200. data/lib/rgame/engine/player.rb +69 -0
  201. data/lib/rgame/engine/player_layer.rb +70 -0
  202. data/lib/rgame/engine/players.rb +205 -0
  203. data/lib/rgame/engine/presentation.rb +171 -0
  204. data/lib/rgame/engine/scene/scene_stack.rb +29 -7
  205. data/lib/rgame/engine/sealed_privates.rb +54 -0
  206. data/lib/rgame/engine/spatial_hash.rb +53 -8
  207. data/lib/rgame/engine/text.rb +194 -0
  208. data/lib/rgame/engine/tile_blockers.rb +63 -0
  209. data/lib/rgame/engine/tile_map.rb +2 -3
  210. data/lib/rgame/engine/tile_map_layer.rb +82 -0
  211. data/lib/rgame/engine/tileset.rb +2 -4
  212. data/lib/rgame/engine/timer.rb +2 -2
  213. data/lib/rgame/engine/ui/button.rb +248 -0
  214. data/lib/rgame/engine/ui/column.rb +20 -0
  215. data/lib/rgame/engine/ui/icon_button.rb +93 -0
  216. data/lib/rgame/engine/ui/menu.rb +290 -0
  217. data/lib/rgame/engine/ui/navigation.rb +57 -0
  218. data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
  219. data/lib/rgame/engine/ui/option_button.rb +163 -0
  220. data/lib/rgame/engine/ui/panel_button.rb +32 -0
  221. data/lib/rgame/engine/ui/panel_menu.rb +36 -0
  222. data/lib/rgame/engine/ui/pointing.rb +146 -0
  223. data/lib/rgame/engine/ui/radial_menu.rb +85 -0
  224. data/lib/rgame/engine/ui/ring.rb +55 -0
  225. data/lib/rgame/engine/ui/row.rb +21 -0
  226. data/lib/rgame/engine/ui/shape_style.rb +102 -0
  227. data/lib/rgame/engine/ui/stack.rb +58 -0
  228. data/lib/rgame/engine/ui/stepping.rb +93 -0
  229. data/lib/rgame/engine/ui/text_button.rb +59 -0
  230. data/lib/rgame/engine/view.rb +76 -0
  231. data/lib/rgame/engine/viewports.rb +171 -0
  232. data/lib/rgame/engine/world_view.rb +71 -0
  233. data/lib/rgame/engine.rb +43 -25
  234. data/lib/rgame/game.rb +164 -22
  235. data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
  236. data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
  237. data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
  238. data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
  239. data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
  240. data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
  241. data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
  242. data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
  243. data/lib/rgame/rubocop/default.yml +39 -0
  244. data/lib/rgame/rubocop/plugin.rb +45 -0
  245. data/lib/rgame/rubocop.rb +11 -0
  246. data/lib/rgame/util/color.rb +20 -24
  247. data/lib/rgame/util/controls.rb +106 -44
  248. data/lib/rgame/util/route_search.rb +27 -0
  249. data/lib/rgame/util/save_file.rb +107 -0
  250. data/lib/rgame/util/solid_grid.rb +37 -0
  251. data/lib/rgame/util/tensor.rb +0 -9
  252. data/lib/rgame/util/tile_sweep.rb +36 -0
  253. data/lib/rgame/util/z.rb +123 -0
  254. data/lib/rgame/util.rb +5 -3
  255. data/lib/rgame/version.rb +1 -1
  256. data/lib/rgame.rb +0 -15
  257. metadata +176 -20
  258. data/lib/rgame/engine/actor.rb +0 -53
  259. data/lib/rgame/engine/body.rb +0 -49
  260. data/lib/rgame/engine/cached_label.rb +0 -33
  261. data/lib/rgame/engine/camera_view.rb +0 -28
  262. data/lib/rgame/engine/input/player_controller.rb +0 -14
  263. data/lib/rgame/engine/matrix.rb +0 -32
  264. data/lib/rgame/engine/resettable.rb +0 -67
  265. data/lib/rgame/engine/tile_collision.rb +0 -78
data/docs/api/drawing.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Drawing
2
2
 
3
- `RGame::Core::Renderer` is what a game draws with. It is created from an app and
4
- used inside `draw`:
3
+ Everything on screen goes through `RGame::Core::Renderer`. **In an
4
+ `RGame::Game`, you never build one.** `Game` builds it and passes it to every
5
+ node's `on_draw(renderer, view)`. The examples on this page build one on a plain
6
+ `App` instead, to show the calls without a scene graph:
5
7
 
6
8
  ```ruby
7
9
  require 'rgame'
@@ -26,15 +28,16 @@ end
26
28
  MyGame.new.run
27
29
  ```
28
30
 
29
- Two things about that are worth knowing before anything else.
31
+ Know two rules before anything else.
30
32
 
31
- **Drawing is only legal inside `draw`.** Calling one of these from `update` or
32
- from a constructor raises. The frame is not open at those times, so the call
33
- would be silently discarded — and an invisible failure is worse than a loud one.
33
+ **Draw only inside `draw`**, or a node's `on_draw`. A drawing call from `update`
34
+ or from a constructor raises. The frame is not open then, so the call would vanish without a trace.
35
+ A loud failure beats an invisible one. `renderer.drawing?` returns whether a frame
36
+ is open.
34
37
 
35
- **Nothing is drawn immediately.** Calls accumulate, and the frame is sorted and
36
- sent to the GPU once, after `draw` returns. So the order you make calls in does
37
- not decide what ends up on top `z:` does.
38
+ **The renderer draws nothing immediately.** It collects calls, sorts the frame
39
+ and sends it to the GPU once, after `draw` returns. So call order does not decide
40
+ what ends up on top; the scene tree does. See "Draw order" below.
38
41
 
39
42
  ## Coordinates, colours and z
40
43
 
@@ -42,21 +45,80 @@ not decide what ends up on top — `z:` does.
42
45
  |---|---|
43
46
  | Origin | Top-left. x grows right, y grows **down**. |
44
47
  | Angles | Degrees. A **positive angle turns clockwise** on screen. |
45
- | `z:` | Higher is nearer the viewer. Equal z keeps call order. |
48
+ | `z:` | Where this call sits among **this node's own** drawing. −512…511. |
46
49
  | `color:` | `nil` (white), `[r, g, b]`, `[r, g, b, a]`, or a `RGame::Util::Color`. |
47
50
 
48
- `z` defaults to `50` for shapes and `0` for images, so a debug box or a health
49
- bar drawn without a `z:` lands on top of the scene rather than under it.
51
+ **`z:` is an offset inside the current layer**, not a global number. It puts a
52
+ node's panel under its label and its shadow under its sprite. It cannot reach
53
+ anything else. A value outside −512…511 raises.
50
54
 
51
- Equal-z stability matters more than it sounds: without it, two sprites on the
52
- same layer would swap places whenever the sort felt like it, which reads as
53
- flicker.
55
+ Shapes default to `50`, text to `10` and images to `0`. A debug box or a health
56
+ bar without a `z:` therefore lands on top of *that node's* sprite. Calls with
57
+ equal z keep their call order. Without that rule, two sprites on one layer could
58
+ swap places from frame to frame, and players would see flicker.
59
+
60
+ ## Draw order
61
+
62
+ **The renderer orders a frame in three steps, coarsest first.** A drawing call
63
+ passes a number only for the last step.
64
+
65
+ 1. **The band**: `:world` (the default), `:hud`, `:overlay` or `:debug`.
66
+ Everything in one band lies under everything in the next.
67
+ 2. **The slot.** The traversal walks the scene tree depth-first, siblings in `z`
68
+ order. Each node takes the next slot in its band when the walk reaches it.
69
+ Draw order is therefore **tree order**. A node's subtree forms one unbroken
70
+ run and cannot straddle a sibling.
71
+ 3. **The offset**: the `z:` above, inside one node's slot.
72
+
73
+ A scene graph arranges all of this. `RGame::Engine::Node2D#draw` opens a layer per
74
+ node, so a game sets `z` on nodes and a `band` on the few that start one. See
75
+ [scene_graph.md](scene_graph.md), "Draw order".
76
+
77
+ ### Opening a layer by hand
78
+
79
+ Code that draws outside the scene tree opens its own layer. The debug overlay, a
80
+ spec and a script all do:
81
+
82
+ ```ruby
83
+ renderer.layered(:hud) do
84
+ renderer.nine_slice(:panel, x, y, w, h)
85
+ renderer.text(score, x + 8, y + 6, z: 1) # above this layer's own panel
86
+ end
87
+ ```
88
+
89
+ `layered` takes the next slot in that band and measures every `z:` inside the
90
+ block from it. Afterwards it restores the previous base, even when the block
91
+ raises. Nesting *replaces* the base; it does not add to it. A node's slot depends
92
+ on where the traversal reached it, not on its ancestors' picks. Outside any block
93
+ the base is 0, so a bare script gets exactly the z it passes.
94
+
95
+ `renderer.layer` returns the base in effect.
96
+
97
+ ### Why bands exist
98
+
99
+ A frame holds three kinds of content:
100
+
101
+ - **World**: inside a `WorldView`, drawn once per viewport, under a camera.
102
+ - **A player's own screen space**: their HUD and menu, drawn once and clipped to
103
+ their viewport (`PlayerLayer`).
104
+ - **Global screen space**: a cutscene or a results panel, drawn once across the
105
+ whole window.
106
+
107
+ The world is a different *space* from the other two, and the tree enforces that:
108
+ `WorldView` draws its subtree once per viewport. The other two share one space,
109
+ and the band tells them apart.
110
+
111
+ Two viewports may interleave in the sort without harm. Their commands carry
112
+ different clips and land on different pixels. Order *within* one viewport
113
+ matters, and drawing a HUD after the world does not put it on top. Its band
114
+ does. Bands lie `2**40` apart and a `z:` spans 1024, so no offset can carry a
115
+ call into the next band. See `RGame::Util::Z`.
54
116
 
55
117
  ### Colours and allocation
56
118
 
57
- Passing a `Color` allocates nothing it is a frozen value, and the same one can
58
- be shared by every sprite that uses it. Passing an array allocates a colour per
59
- call, which is fine at setup and wasteful sixty times a second:
119
+ **Passing a `Color` allocates nothing.** A `Color` is a frozen value, so every
120
+ sprite can share the same one. Passing an array allocates a colour on every
121
+ call. That is fine at setup and wasteful sixty times a second:
60
122
 
61
123
  ```ruby
62
124
  RED = RGame::Util::Color.new(224, 64, 64) # once
@@ -77,20 +139,17 @@ renderer.circle(cx, cy, radius, z: 50, color: nil, segments: 64)
77
139
  renderer.debug_box(x, y, width, height, z: 50)
78
140
  ```
79
141
 
80
- A **quad's** four points are taken in loop order top-left, top-right,
81
- bottom-right, bottom-left for a rectangle. Listing them in Z order gives an
82
- hourglass.
142
+ A **quad** takes its four points in loop order: top-left, top-right,
143
+ bottom-right, bottom-left for a rectangle. Points in Z order give an hourglass.
83
144
 
84
- A **line** has real thickness because it is drawn as a quad. OpenGL's own line
85
- width is a suggestion drivers are free to ignore above one pixel, so a line
86
- worth seeing has to be a shape.
145
+ A **line** has real thickness, because the renderer draws it as a quad. Drivers
146
+ may ignore OpenGL's own line width above one pixel.
87
147
 
88
- A **circle** is a fan of triangles, and the whole fan is one batch there is no
89
- cached circle texture to warm up and nothing to configure. `segments:` is there
90
- for the rare case where 64 is too many or too few.
148
+ A **circle** is a fan of triangles in one batch. It needs no cached texture and
149
+ no configuration. Adjust `segments:` if 64 is too many or too few.
91
150
 
92
- `debug_box` is a translucent red rectangle for visualising a collision box, so a
93
- scene can ask for one without deciding what colour "debug" is.
151
+ `debug_box` draws a translucent red rectangle to show a collision box. A scene
152
+ can ask for one without choosing a debug colour.
94
153
 
95
154
  ## Images
96
155
 
@@ -100,64 +159,65 @@ renderer.image_at(image, x, y, scale_x: 1, scale_y: 1, z: 0, color: nil)
100
159
  renderer.background(image, x = 0, y = 0, z: 0, color: nil)
101
160
  ```
102
161
 
103
- Three anchors for three jobs. `image` **centres** on the position given and
104
- rotates about that centre — the sprite case. `image_at` places the **top-left**
105
- corner and scales each axis on its own tiles, nine-slice corners, sheet
106
- frames. `background` is `image_at` at natural size, named for its usual job.
162
+ Each method anchors the image differently:
163
+
164
+ - `image` **centres** the image on the position and rotates it about that
165
+ centre. Use it for sprites.
166
+ - `image_at` places the **top-left** corner and scales each axis separately. Use
167
+ it for tiles, nine-slice corners and sheet frames.
168
+ - `background` is `image_at` at natural size.
107
169
 
108
170
  ### Mirroring
109
171
 
110
- A negative scale on `image_at` mirrors the image **inside the same rectangle**.
111
- It does not move it:
172
+ **A negative scale on `image_at` mirrors the image inside the same rectangle.**
173
+ It does not move the image:
112
174
 
113
175
  ```ruby
114
176
  renderer.image_at(frame, x, y, scale_x: facing_left ? -1 : 1)
115
177
  ```
116
178
 
117
- Both calls cover the same pixels; only the picture is reversed. That is worth
118
- knowing if you are coming from Gosu, where a negative scale mirrors *about* the
119
- anchor and the caller adds a width back to compensate. Here `(x, y)` is the
120
- top-left corner whatever the sign, so there is nothing to compensate for and
121
- nothing to forget.
179
+ Both calls cover the same pixels; only the picture is reversed. `(x, y)` stays
180
+ the top-left corner whatever the scale's sign, so a mirrored sprite stays put.
181
+ Mirroring about the anchor would shift the image one width to the left. Every
182
+ flipped draw would then have to add that width back.
122
183
 
123
184
  A scale of `0` draws nothing.
124
185
 
125
- `color:` tints: the image's pixels are multiplied by it, so white leaves the
126
- image alone and a colour with alpha fades it.
186
+ `color:` tints the image by multiplying its pixels. White leaves the image
187
+ unchanged, and a colour with alpha fades it.
127
188
 
128
- See [Images](images.md) for loading files and slicing sprite sheets.
189
+ [Images](images.md) covers loading files and slicing tiles; [Assets](assets.md#sprite-sheets) covers sprite sheets.
129
190
 
130
- **An image can only be drawn by the app that loaded it.** GPU textures belong to
131
- one window's OpenGL context and are not shared with another, so drawing another
132
- app's image would sample nothing and paint a plain white rectangle. Rather than
133
- let that happen quietly, it raises `ArgumentError`. In a one-window game which
134
- is nearly all of them — this never comes up.
191
+ **Only the app that loaded an image can draw it.** A GPU texture belongs to one
192
+ window's OpenGL context. Drawing another app's image would sample nothing and
193
+ paint a plain white rectangle, so the renderer raises `ArgumentError` instead. A
194
+ one-window game never meets this.
135
195
 
136
196
  ## Drawing by id
137
197
 
138
- Game logic names an asset; it does not hold one. That is not a convenience —
139
- the scene layer may hold `RGame::Util` values but no `RGame::Core` handle at
140
- all, so a Symbol or a path is the only thing a node *can* carry.
198
+ **Game logic names an asset; it does not hold one.** The scene layer may hold
199
+ `RGame::Util` values but no `RGame::Core` handle. A Symbol or a path is the only
200
+ thing a node *can* carry.
141
201
 
142
- An id is normally a **root-relative path**, resolved through the app's
143
- [asset manager](assets.md) and then remembered:
202
+ An id is normally a **root-relative path**. The renderer resolves it through the
203
+ app's [asset manager](assets.md) and remembers the result:
144
204
 
145
205
  ```ruby
146
- renderer.sprite('example 09/player.json', row, col, x, y, flip_x: false, z: 0)
206
+ renderer.sprite('hero.json', row, col, x, y, flip_x: false, z: 0)
147
207
  renderer.image('space.png', cx, cy, angle: 0, scale: 1)
148
208
  renderer.background('space.png')
149
- renderer.tilemap('map/island.tmx', camera_x, camera_y, viewport_w, viewport_h)
150
- renderer.tilemap_overlay('map/island.tmx', camera_x, camera_y, viewport_w, viewport_h, z: 20)
209
+ renderer.tilemap('map/island.tmx', layer, cull_x, cull_y, cull_w, cull_h, elapsed: 0.0) # draws in world coordinates
210
+ renderer.nine_slice(:panel, x, y, width, height, z: 0, tint: nil)
151
211
  ```
152
212
 
153
- Nothing has to be set up for that: `Renderer.new(app)` takes the app's own
154
- manager, so a path just works. `Renderer.new(app, assets: other)` overrides it.
213
+ Paths need no setup. `Renderer.new(app)` uses the app's own manager.
214
+ `Renderer.new(app, assets: other)` uses a different one.
155
215
 
156
216
  ### Registering
157
217
 
158
- `register_*` pre-binds an id to an object you chose, and wins over the asset
159
- manager. It is for the two things a path cannot name: an id that is not a file,
160
- and an object the game assembled itself.
218
+ `register_*` binds an id to an object you choose, and takes priority over the
219
+ asset manager. Use it for what a path cannot name: an id that is not a file, and
220
+ an object the game built itself.
161
221
 
162
222
  ```ruby
163
223
  renderer.register_image(:space, app.assets.image('space.png'))
@@ -169,10 +229,10 @@ renderer.register_ui_atlas(atlas) # every element under its own name
169
229
  renderer.image(:space, 100, 100)
170
230
  ```
171
231
 
172
- **Nine-slices are registration-only.** Their ids name an *element of an atlas*,
173
- not a file, so there is nothing for a manager to resolve them to.
232
+ **Nine-slices must be registered.** Their ids name an *element of an atlas*, not
233
+ a file, so an asset manager has nothing to resolve.
174
234
 
175
- ### What resolution does
235
+ ### How the renderer resolves an id
176
236
 
177
237
  | Given | |
178
238
  |---|---|
@@ -182,28 +242,28 @@ not a file, so there is nothing for a manager to resolve them to.
182
242
  | A `Symbol` that is not registered | `KeyError`, naming the id and the type |
183
243
  | `nil` | `TypeError` |
184
244
 
185
- A Symbol is never offered to the asset manager, because only a String can be a
186
- path. So a typo'd Symbol says "no sheet registered for `:heor`" rather than
187
- whatever a loader makes of being handed a Symbol for a filename — and a broken
188
- *file* still raises its own `LoadError` naming it, which is a different bug
189
- wanting a different fix.
245
+ The renderer never offers a Symbol to the asset manager, because only a String
246
+ can be a path. A mistyped Symbol therefore raises "no sheet registered for
247
+ `:heor`". A broken *file* raises its own `LoadError` naming the file. The two
248
+ errors point at two different fixes.
190
249
 
191
- Resolution happens once per id and the answer is kept, so per-frame drawing
192
- neither re-resolves nor allocates a lookup key.
250
+ The renderer resolves each id once and keeps the answer. Per-frame drawing
251
+ neither resolves again nor allocates a lookup key.
193
252
 
194
253
  ## Transform blocks
195
254
 
196
- Each of these applies to everything drawn inside it, and undoes itself
197
- afterwards — including when the block raises.
255
+ Each block applies to everything drawn inside it and undoes itself afterwards,
256
+ even when the block raises.
198
257
 
199
258
  ```ruby
200
259
  renderer.translated(dx, dy) { ... }
201
260
  renderer.rotated(angle, pivot_x, pivot_y) { ... }
202
261
  renderer.scaled(sx, sy = sx) { ... }
203
262
  renderer.clipped(x, y, width, height) { ... }
263
+ renderer.layered(band) { ... } # see "Draw order" above
204
264
  ```
205
265
 
206
- They nest, and they compose in the order they are opened:
266
+ Blocks nest and compose in the order you open them:
207
267
 
208
268
  ```ruby
209
269
  renderer.translated(-camera.x, -camera.y) do # world space -> screen space
@@ -213,18 +273,24 @@ renderer.translated(-camera.x, -camera.y) do # world space -> screen space
213
273
  end
214
274
  ```
215
275
 
216
- `translated` is how a camera works, and the reason it is a *draw-time* transform
217
- rather than something baked into positions is that the same world can then be
218
- drawn twice, under two different offsets — which is what split-screen is.
276
+ **A camera is a `translated` block.** Because the offset applies at draw time,
277
+ the same world can be drawn twice under two different offsets. That is
278
+ split-screen.
279
+
280
+ `rotated(0, …)`, `translated(0, 0)` and `scaled(1)` cost nothing. They skip the
281
+ transform and run the block, so unrotated drawing pays nothing.
219
282
 
220
- `rotated(0, …)`, `translated(0, 0)` and `scaled(1)` are free: they skip the
221
- transform entirely and just run the block, so unrotated drawing pays nothing.
283
+ **Inside a scene graph you rarely open a transform block yourself.** The examples
284
+ on this page drive the renderer from an `App`, in window coordinates. For a
285
+ `Node2D`, the traversal pushes the node's transform before it calls `on_draw`. A
286
+ node therefore draws at *its own* origin, and passing its position would apply it
287
+ twice. See [Scene graph](scene_graph.md#drawing-happens-in-local-space).
222
288
 
223
289
  ### Clipping and split-screen
224
290
 
225
- A clip **narrows**. Nesting one inside another intersects them, so a child can
226
- never draw outside the region its parent allowed. Two clipped blocks are a
227
- split screen:
291
+ **A clip narrows.** A nested clip intersects with its parent, so a child never
292
+ draws outside the region its parent allowed. Two clipped blocks make a split
293
+ screen:
228
294
 
229
295
  ```ruby
230
296
  def draw
@@ -238,11 +304,17 @@ def draw
238
304
  end
239
305
  ```
240
306
 
307
+ **A game does not write that.**
308
+ [`RGame::Engine::WorldView`](scene_graph.md#view-transforms-and-the-camera) does it
309
+ once per active player, with the layout's rectangles and each player's camera.
310
+ Call `clipped` directly for a region of your own, such as a minimap or a
311
+ scrolling list.
312
+
241
313
  ## Recordings: bake once, replay cheaply
242
314
 
243
- A tile layer is a couple of thousand quads that have not changed since the level
244
- loaded. `record` bakes a block of drawing so that replaying it costs one call
245
- per texture, however many draws went into it:
315
+ **`record` bakes a block of drawing, and a replay costs one call per texture.** A
316
+ tile layer holds a few thousand quads that stay the same once the level loads, so
317
+ it is the typical case:
246
318
 
247
319
  ```ruby
248
320
  def draw
@@ -254,58 +326,58 @@ def draw
254
326
  end
255
327
  ```
256
328
 
257
- Nothing is drawn at bake time — the block's output goes into the recording
258
- instead of into the frame. `record` must be called inside `draw` like everything
259
- else, which is why the example bakes on the first frame rather than in
260
- `initialize`.
329
+ Baking draws nothing; the block's output goes into the recording, not the frame.
330
+ `record` must run inside `draw` like every other call. The example therefore
331
+ bakes on the first frame, not in `initialize`.
261
332
 
262
333
  ```ruby
263
334
  baked.draw(x = 0, y = 0, z: 0, color: nil)
264
335
  baked.batch_count # GL calls one replay costs
265
- baked.width # the size of what was baked
336
+ baked.vertex_count # vertices baked in
337
+ baked.width # the size of what was baked, with #height
266
338
  baked.empty?
267
339
  ```
268
340
 
269
- **Positions, texture coordinates, colours and any transforms inside the block
270
- are baked in.** The transform in effect when the recording is *drawn* applies on
271
- top, so a baked layer scrolls under a camera without being rebuilt, and the same
272
- recording can be stamped in several places:
341
+ **A recording bakes in positions, texture coordinates, colours and any
342
+ transforms inside the block.** The transform in effect at replay applies on top.
343
+ A baked layer scrolls under a camera without a rebuild, and one recording can be
344
+ stamped in several places:
273
345
 
274
346
  ```ruby
275
347
  5.times { |i| @bush.draw(i * 120, 300) }
276
348
  ```
277
349
 
278
- **`color:` tints the replay** each recorded colour is multiplied by it, so a
279
- whole baked layer can be faded out at once.
350
+ **`color:` tints the replay.** The renderer multiplies each recorded colour by
351
+ it, so you can fade a whole baked layer at once.
280
352
 
281
- **Clipping cannot be baked.** Clipping happens when pixels are rasterised, so a
282
- clip rectangle captured in one place would be wrong everywhere else the
283
- recording is drawn. Pushing a clip inside a `record` block raises; clip the
284
- replay instead, which is what was meant anyway:
353
+ **A recording cannot contain a clip.** Clipping happens at rasterisation, so a
354
+ clip captured in one place would be wrong everywhere else the recording is drawn.
355
+ Pushing a clip inside a `record` block raises. Clip the replay instead:
285
356
 
286
357
  ```ruby
287
358
  @renderer.clipped(0, 0, 400, 600) { @ground.draw(-@camera.x, -@camera.y) }
288
359
  ```
289
360
 
290
- Recordings do not nest, and a block that raises leaves nothing half-recorded
291
- behind. A recording keeps the images baked into it alive, so a sprite sheet
292
- dropped after baking does not take its texture with it.
361
+ Recordings do not nest. A block that raises leaves no half-built recording
362
+ behind. A recording keeps its baked images alive, so dropping a sprite sheet
363
+ after baking does not free its texture.
293
364
 
294
365
  ## Testing what a scene draws
295
366
 
296
- The renderer is an interface, not a class your game should name. Game logic
297
- receives one and calls methods on it; a headless spec passes a recording fake
298
- instead and asserts on the calls:
367
+ **Treat the renderer as an interface, not a class your game names.** Game logic
368
+ receives a renderer and calls its methods. A headless spec passes a recording
369
+ fake instead and asserts on the calls. rgame's own suite uses `FakeRenderer`
370
+ from `spec/support/`:
299
371
 
300
372
  ```ruby
301
373
  renderer = FakeRenderer.new
302
- health_bar.draw(renderer)
374
+ health_bar.on_draw(renderer, nil)
303
375
 
304
376
  expect(renderer.calls_to(:rect).map(&:args)).to eq([[10, 10, 64, 8]])
305
377
  ```
306
378
 
307
- Recordings are faked too, and the fake keeps the two questions apart what was
308
- baked, and where it was replayed:
379
+ The fake also records recordings, and keeps two questions apart: what was baked,
380
+ and where it was replayed.
309
381
 
310
382
  ```ruby
311
383
  ground = renderer.record { ... } # => a FakeRecording
@@ -314,17 +386,13 @@ expect(ground.calls.size).to eq(tiles.size) # baked once, not per frame
314
386
  expect(ground.draws.map(&:args)).to eq([[-camera.x, -camera.y]])
315
387
  ```
316
388
 
317
- That runs with no window, no GPU and no clock. The fake and the real renderer
318
- are both checked against one shared contract (`spec/support/shared_examples/
319
- a_renderer.rb`), so the fake cannot drift into describing a renderer that does
320
- not exist — which would leave a green test suite and a game that no longer runs.
389
+ These specs run with no window, no GPU and no clock. One shared contract,
390
+ `spec/support/shared_examples/a_renderer.rb`, checks both the fake and the real
391
+ renderer. A fake that drifted from the real renderer would keep the suite green
392
+ while the game stopped drawing.
321
393
 
322
394
  ## Text
323
395
 
324
396
  `renderer.text(string, x, y)` draws a line of text, and `text_width` measures
325
- one. See [Text](text.md) for fonts, the shipped default and what it covers.
326
-
327
- ## What is not here yet
328
-
329
- Audio and drawing by asset id (`sprite(:hero, row, col, …)`) are still to come.
330
- Today an image is passed as an object rather than looked up in a registry.
397
+ one. Both take a String or an [`Engine::Text`](toolbox.md#text--the-string-a-node-draws). [Text](text.md) covers fonts, the shipped default and the characters it
398
+ covers.