rgame 0.2.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 (253) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +135 -1
  3. data/README.md +67 -65
  4. data/docs/api/README.md +86 -82
  5. data/docs/api/app.md +125 -68
  6. data/docs/api/assets.md +203 -165
  7. data/docs/api/audio.md +130 -89
  8. data/docs/api/cli.md +259 -0
  9. data/docs/api/components.md +1032 -265
  10. data/docs/api/drawing.md +160 -160
  11. data/docs/api/examples.md +263 -0
  12. data/docs/api/game.md +149 -50
  13. data/docs/api/images.md +39 -39
  14. data/docs/api/input.md +226 -148
  15. data/docs/api/internals.md +241 -62
  16. data/docs/api/localization.md +285 -0
  17. data/docs/api/scene_graph.md +397 -244
  18. data/docs/api/signals.md +81 -76
  19. data/docs/api/systems.md +186 -73
  20. data/docs/api/text.md +58 -41
  21. data/docs/api/tile_maps.md +237 -0
  22. data/docs/api/toolbox.md +395 -146
  23. data/docs/api/ui.md +883 -54
  24. data/docs/api/values.md +216 -33
  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/rgame_core/app/app.c +33 -3
  92. data/ext/rgame_core/app/locale.c +67 -0
  93. data/ext/rgame_core/app/locale.h +28 -0
  94. data/ext/rgame_core/audio/audio.c +39 -2
  95. data/ext/rgame_core/example.rb +0 -49
  96. data/ext/rgame_core/extconf.rb +0 -125
  97. data/ext/rgame_core/include/rgame/core.h +38 -2
  98. data/ext/rgame_core/ruby/audio_ext.c +10 -5
  99. data/ext/rgame_core/ruby/core_ext.c +30 -7
  100. data/ext/rgame_core/ruby/core_ext.h +3 -0
  101. data/ext/rgame_core/ruby/locale_ext.c +44 -0
  102. data/ext/rgame_core/ruby/recording_ext.c +1 -1
  103. data/ext/rgame_core/ruby/renderer_ext.c +20 -20
  104. data/ext/rgame_util/extconf.rb +2 -20
  105. data/ext/rgame_util/route_search.c +305 -0
  106. data/ext/rgame_util/route_search.h +86 -0
  107. data/ext/rgame_util/route_search_ext.c +150 -0
  108. data/ext/rgame_util/solid_grid.c +58 -0
  109. data/ext/rgame_util/solid_grid.h +49 -0
  110. data/ext/rgame_util/solid_grid_ext.c +161 -0
  111. data/ext/rgame_util/tile_sweep.c +164 -0
  112. data/ext/rgame_util/tile_sweep.h +62 -0
  113. data/ext/rgame_util/tile_sweep_ext.c +155 -0
  114. data/ext/rgame_util/util_ext.c +3 -0
  115. data/ext/rgame_util/util_ext.h +15 -0
  116. data/lib/rgame/boot.rb +0 -10
  117. data/lib/rgame/cli/new_project.rb +139 -0
  118. data/lib/rgame/cli/templates/Gemfile.tt +23 -0
  119. data/lib/rgame/cli/templates/README.md.tt +93 -0
  120. data/lib/rgame/cli/templates/Rakefile.tt +9 -0
  121. data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
  122. data/lib/rgame/cli/templates/game.rb.tt +23 -0
  123. data/lib/rgame/cli/templates/gitignore.tt +12 -0
  124. data/lib/rgame/cli/templates/main.rb.tt +11 -0
  125. data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
  126. data/lib/rgame/cli/templates/rspec.tt +2 -0
  127. data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
  128. data/lib/rgame/cli/templates/ruby-version.tt +1 -0
  129. data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
  130. data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
  131. data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
  132. data/lib/rgame/cli.rb +66 -0
  133. data/lib/rgame/core/app.rb +6 -44
  134. data/lib/rgame/core/asset_manager.rb +13 -31
  135. data/lib/rgame/core/audio.rb +37 -16
  136. data/lib/rgame/core/font.rb +0 -3
  137. data/lib/rgame/core/locale.rb +22 -0
  138. data/lib/rgame/core/nine_slice.rb +0 -21
  139. data/lib/rgame/core/renderer.rb +6 -63
  140. data/lib/rgame/core/sprite_sheet.rb +0 -3
  141. data/lib/rgame/core/tile_map_renderer.rb +2 -19
  142. data/lib/rgame/core/ui_atlas.rb +28 -13
  143. data/lib/rgame/core.rb +1 -8
  144. data/lib/rgame/engine/actor_blockers.rb +131 -0
  145. data/lib/rgame/engine/animation_set.rb +1 -0
  146. data/lib/rgame/engine/audio_director.rb +36 -6
  147. data/lib/rgame/engine/bounds_blockers.rb +74 -0
  148. data/lib/rgame/engine/camera.rb +3 -3
  149. data/lib/rgame/engine/circle_collider.rb +4 -2
  150. data/lib/rgame/engine/collision_box.rb +26 -1
  151. data/lib/rgame/engine/collision_system.rb +110 -22
  152. data/lib/rgame/engine/component.rb +34 -10
  153. data/lib/rgame/engine/components/action_trigger.rb +0 -1
  154. data/lib/rgame/engine/components/animated_sprite.rb +27 -25
  155. data/lib/rgame/engine/components/box_collider.rb +99 -0
  156. data/lib/rgame/engine/components/camera_follow.rb +6 -5
  157. data/lib/rgame/engine/components/character_body.rb +20 -61
  158. data/lib/rgame/engine/components/circle_collider.rb +47 -11
  159. data/lib/rgame/engine/components/collision_world.rb +159 -31
  160. data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
  161. data/lib/rgame/engine/components/feet_collider.rb +61 -0
  162. data/lib/rgame/engine/components/hop.rb +76 -0
  163. data/lib/rgame/engine/components/identity.rb +73 -0
  164. data/lib/rgame/engine/components/mover.rb +285 -0
  165. data/lib/rgame/engine/components/navigator.rb +145 -0
  166. data/lib/rgame/engine/components/path_follow.rb +123 -31
  167. data/lib/rgame/engine/components/player_controller.rb +5 -2
  168. data/lib/rgame/engine/components/pool.rb +1 -1
  169. data/lib/rgame/engine/components/screen_wrap.rb +33 -11
  170. data/lib/rgame/engine/components/sprite.rb +12 -6
  171. data/lib/rgame/engine/components/targeting.rb +9 -11
  172. data/lib/rgame/engine/components/thrust_controller.rb +1 -1
  173. data/lib/rgame/engine/components/tile_world.rb +41 -13
  174. data/lib/rgame/engine/components/timer.rb +1 -1
  175. data/lib/rgame/engine/components/velocity.rb +23 -7
  176. data/lib/rgame/engine/components/wander_controller.rb +6 -2
  177. data/lib/rgame/engine/components/world.rb +133 -0
  178. data/lib/rgame/engine/contact_set.rb +74 -0
  179. data/lib/rgame/engine/culling.rb +4 -6
  180. data/lib/rgame/engine/debug_overlay.rb +5 -11
  181. data/lib/rgame/engine/i18n/plural.rb +45 -0
  182. data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
  183. data/lib/rgame/engine/i18n/template.rb +59 -0
  184. data/lib/rgame/engine/i18n.rb +276 -51
  185. data/lib/rgame/engine/input/action_mapper.rb +0 -26
  186. data/lib/rgame/engine/input/actions.rb +2 -8
  187. data/lib/rgame/engine/input/input_map.rb +39 -21
  188. data/lib/rgame/engine/nav_grid.rb +87 -0
  189. data/lib/rgame/engine/node2d.rb +248 -133
  190. data/lib/rgame/engine/path.rb +4 -6
  191. data/lib/rgame/engine/players.rb +6 -13
  192. data/lib/rgame/engine/presentation.rb +171 -0
  193. data/lib/rgame/engine/scene/scene_stack.rb +4 -4
  194. data/lib/rgame/engine/sealed_privates.rb +54 -0
  195. data/lib/rgame/engine/spatial_hash.rb +51 -19
  196. data/lib/rgame/engine/text.rb +194 -0
  197. data/lib/rgame/engine/tile_blockers.rb +63 -0
  198. data/lib/rgame/engine/tile_map.rb +2 -3
  199. data/lib/rgame/engine/tile_map_layer.rb +0 -2
  200. data/lib/rgame/engine/tileset.rb +2 -4
  201. data/lib/rgame/engine/timer.rb +2 -2
  202. data/lib/rgame/engine/ui/button.rb +248 -0
  203. data/lib/rgame/engine/ui/column.rb +20 -0
  204. data/lib/rgame/engine/ui/icon_button.rb +93 -0
  205. data/lib/rgame/engine/ui/menu.rb +246 -71
  206. data/lib/rgame/engine/ui/navigation.rb +57 -0
  207. data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
  208. data/lib/rgame/engine/ui/option_button.rb +163 -0
  209. data/lib/rgame/engine/ui/panel_button.rb +32 -0
  210. data/lib/rgame/engine/ui/panel_menu.rb +36 -0
  211. data/lib/rgame/engine/ui/pointing.rb +146 -0
  212. data/lib/rgame/engine/ui/radial_menu.rb +85 -0
  213. data/lib/rgame/engine/ui/ring.rb +55 -0
  214. data/lib/rgame/engine/ui/row.rb +21 -0
  215. data/lib/rgame/engine/ui/shape_style.rb +102 -0
  216. data/lib/rgame/engine/ui/stack.rb +58 -0
  217. data/lib/rgame/engine/ui/stepping.rb +93 -0
  218. data/lib/rgame/engine/ui/text_button.rb +59 -0
  219. data/lib/rgame/engine/viewports.rb +2 -5
  220. data/lib/rgame/engine/world_view.rb +5 -4
  221. data/lib/rgame/engine.rb +31 -25
  222. data/lib/rgame/game.rb +99 -27
  223. data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
  224. data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
  225. data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
  226. data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
  227. data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
  228. data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
  229. data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
  230. data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
  231. data/lib/rgame/rubocop/default.yml +39 -0
  232. data/lib/rgame/rubocop/plugin.rb +45 -0
  233. data/lib/rgame/rubocop.rb +11 -0
  234. data/lib/rgame/util/color.rb +20 -24
  235. data/lib/rgame/util/controls.rb +14 -28
  236. data/lib/rgame/util/route_search.rb +27 -0
  237. data/lib/rgame/util/save_file.rb +107 -0
  238. data/lib/rgame/util/solid_grid.rb +37 -0
  239. data/lib/rgame/util/tensor.rb +0 -9
  240. data/lib/rgame/util/tile_sweep.rb +36 -0
  241. data/lib/rgame/util/z.rb +4 -14
  242. data/lib/rgame/util.rb +4 -3
  243. data/lib/rgame/version.rb +1 -1
  244. data/lib/rgame.rb +0 -15
  245. metadata +152 -11
  246. data/lib/rgame/engine/actor.rb +0 -53
  247. data/lib/rgame/engine/body.rb +0 -49
  248. data/lib/rgame/engine/cached_label.rb +0 -33
  249. data/lib/rgame/engine/input/player_controller.rb +0 -14
  250. data/lib/rgame/engine/matrix.rb +0 -32
  251. data/lib/rgame/engine/resettable.rb +0 -67
  252. data/lib/rgame/engine/tile_collision.rb +0 -78
  253. data/lib/rgame/engine/ui/menu_item.rb +0 -84
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7eb60de3d59e1a7c55ad4261e68be3a9cdb1d8f478e56354eb655c2452fbc4d
4
- data.tar.gz: 73f0f53dec4c19b34bd32a80cfa86ac2c8068a80debbd32bc234c1dca4cbd0b5
3
+ metadata.gz: 54caa0772adc1eb3211edbd694540c196be0018d6482089447d727f5c871e19a
4
+ data.tar.gz: 16b3d1de8baefc2579bbf5a89fb7b42041d174567b6c7059d5c967f2598621f5
5
5
  SHA512:
6
- metadata.gz: f71a457f48b0f45206b03c68e8dfc8f222c9c2a87957ac2c44edd0f2330dc3412d82874544176379143a5ccf7e24596665253e1a75983d475fd8273a500bcdbe
7
- data.tar.gz: f3c7ac9d5663a9e0f0df3823be78c7cf7496176982e86933e5ad8c19293ade3c21320828127e4a31086f21b44a75096cde3b9b6a4695a51180517ba9f700313b
6
+ metadata.gz: ab441100a4d86c34d93efed615b350a7d65ad819d3b7929e868e709c6a9790b9c78a956b9860632e62750f0880ca9656a1f7f6c98cc3df6c1b19f7d229e40919
7
+ data.tar.gz: e98b7a96d21700bf0533f0155be99ffc9f480e35fda2375aaee4b8b2a7bb65b871d0a70a1f2486873b32e28a83aaa4abfc91a14991226153f57959b0d62237b4
data/CHANGELOG.md CHANGED
@@ -12,6 +12,139 @@ index, not the argument.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.3.0] - 2026-09-15
16
+
17
+ ### Added
18
+
19
+ - **`rgame new NAME`.** The gem installs an `rgame` command, and `rgame new`
20
+ generates a project: a game class, a root node, an English translation table,
21
+ and a spec suite and RuboCop config that pass. Only `game.rb` loads SDL, so the
22
+ generated specs run without a display. See [docs/api/cli.md](docs/api/cli.md).
23
+ - **RuboCop cops for games.** The gem ships rgame's `Game/` cops as a RuboCop
24
+ plugin, and `rgame new` enables them. They catch per-frame allocations, a node
25
+ drawn at its own position twice, literal text and Core named from headless
26
+ code. See [docs/api/cli.md](docs/api/cli.md#the-generated-rubocop-configuration).
27
+ - **Translation by default.** `RGame::Game` loads every `locales/**/*.yml` and
28
+ picks the player's language from `RGame::Core.preferred_locales`. A node draws
29
+ an `Engine::Text` built from a key, and a UI button's `label:` is a key. See
30
+ [docs/api/localization.md](docs/api/localization.md).
31
+ - **Fullscreen and scaling.** `Game.new` takes `fullscreen:` and `scale_mode:`,
32
+ and both can change while the game runs. `scale_mode:` maps the logical size
33
+ onto the window, and defaults to `:letterbox`. See
34
+ [docs/api/game.md](docs/api/game.md).
35
+ - **Saving.** `Util::SaveFile` reads and writes a game's state in the player's
36
+ save directory. `Components::Identity` gives a node a stable id to save it
37
+ under. See [docs/api/values.md](docs/api/values.md).
38
+ - **Box colliders.** `Components::BoxCollider` is a rectangle in the same
39
+ `CollisionWorld` as `CircleCollider`. `FeetCollider` derives its box from the
40
+ node's size, so a top-down character collides at its feet. See
41
+ [docs/api/components.md](docs/api/components.md).
42
+ - **Movers that something can stop.** `CharacterBody`, `Velocity` and
43
+ `PathFollow` share the base `Components::Mover`. Its `blocked_by:` lists what
44
+ stops a step: solid tiles, the world's edge or collider layers. `on_blocked` and
45
+ `on_unblocked` report the blocker and the axis. See
46
+ [docs/api/components.md](docs/api/components.md).
47
+ - **Pathfinding.** `Engine::NavGrid` finds routes over a tile grid, and
48
+ `TileWorld#nav_grid` hands one out. `Components::Navigator#go_to` walks a node
49
+ there. The search runs in C, in `Util::SolidGrid`, `Util::RouteSearch` and
50
+ `Util::TileSweep`. See [docs/api/toolbox.md](docs/api/toolbox.md).
51
+ - **`Components::World`** gives a scene its bounds without a tile map.
52
+ `ScreenWrap` and `DespawnOffscreen` read the bounds from it or from
53
+ `TileWorld`. See [docs/api/components.md](docs/api/components.md).
54
+ - **`Components::Hop`** lifts a character's picture off the ground through the
55
+ new `Node2D#elevation`. Colliders and cameras ignore the lift. See
56
+ [docs/api/components.md](docs/api/components.md).
57
+ - **New buttons and styles.** `UI::Button` is the base for a button with its
58
+ own look. `TextButton`, `PanelButton`, `OptionButton` and `IconButton` draw
59
+ their background from a `NineSliceStyle` or a `ShapeStyle`. A `hotkey:` presses
60
+ a button without focusing it. See [docs/api/ui.md](docs/api/ui.md).
61
+ - **Menu layouts and navigations.** `UI::Menu` takes a `layout:` (`Column`,
62
+ `Row` or `Ring`) and a `navigation:` (`Stepping`, `Pointing` or `nil`).
63
+ `UI::RadialMenu` focuses whatever the stick points at. `UI::PanelMenu` draws a
64
+ panel that grows with its buttons. See [docs/api/ui.md](docs/api/ui.md).
65
+ - **Menus open and close.** A menu answers `open?`, `open` and `close`, and
66
+ emits `on_opened` and `on_closed`. A closed menu draws nothing and takes no
67
+ input. `trigger:` names an action that holds a menu open while it is down. See
68
+ [docs/api/ui.md](docs/api/ui.md).
69
+ - **A UI atlas can name images.** A descriptor's `images` section cuts
70
+ rectangles from the sheet, and `Renderer#register_ui_atlas` registers each by
71
+ name. `UI::IconButton.new(image: :home)` then draws one.
72
+ - **Input prompts.** `InputMap#button_for(action, device)` returns the bound
73
+ button that device can press. `Controls.gamepad?` and `Controls.pad_button?`
74
+ tell the two kinds of id apart. See [docs/api/input.md](docs/api/input.md).
75
+ - **Sounds by path.** `audio.play_sound` and `play_music` accept a path and
76
+ load it through the asset manager on first use. `RGame::Game` subscribes an
77
+ `AudioDirector`, so `AudioBus` sounds play without setup. See
78
+ [docs/api/audio.md](docs/api/audio.md).
79
+ - **24 examples, one concept each.** Each is a single `main.rb` that runs on its
80
+ own, from `walk` and `sprite` to `pathfinding` and `localization`. See
81
+ [docs/api/examples.md](docs/api/examples.md).
82
+ - `AssetManager#glob` lists the files under the media root that match a pattern.
83
+ - `Util::Color` gains named colours, from `RED` to `DARK_GRAY`.
84
+
85
+ ### Changed
86
+
87
+ - **A subclass may not redefine `Node2D`'s or `Component`'s internal methods.**
88
+ Their private and protected machinery now starts with `_`. A subclass that
89
+ defines one of those names raises `NameError` where the class is defined.
90
+ Before, it silently switched that machinery off. See
91
+ [docs/api/scene_graph.md](docs/api/scene_graph.md).
92
+ - **Nodes draw in local space.** `Node2D#draw` pushes the node's transform
93
+ before it calls `on_draw`, so a node draws at its own origin:
94
+ `renderer.rect(0, 0, width, height)`. Drop `abs_x`/`abs_y` from draw calls, or
95
+ the position applies twice. See
96
+ [docs/api/scene_graph.md](docs/api/scene_graph.md).
97
+ - **`abs_x`, `abs_y` and `abs_angle` are now `world_x`, `world_y` and
98
+ `world_angle`.** A node computes them when read, so they are never a tick
99
+ stale. `world_x=` and `world_y=` place a node in world space. See
100
+ [docs/api/scene_graph.md](docs/api/scene_graph.md).
101
+ - **`UI::Menu` is handed its buttons.** `Menu#add(button)` replaces `add_item`,
102
+ `items` is now `buttons`, and `MenuItem` is now `UI::PanelButton`. The item
103
+ size moves to `layout: UI::Column.new(...)`. Confirm activates a button when
104
+ released. See [docs/api/ui.md](docs/api/ui.md).
105
+ - **`CharacterBody` takes its shape from a sibling collider.**
106
+ `CharacterBody.new(speed:, blocked_by:)` replaces `feet_width:` and
107
+ `feet_height:`. A body with no `blocked_by:` moves freely, and `TileWorld#move`
108
+ is gone. See [docs/api/components.md](docs/api/components.md).
109
+ - **Contacts are edges.** `on_hit` fires once when two colliders start to
110
+ overlap, and the new `on_separated` fires once when they part. Before,
111
+ `on_hit` fired on every step of an overlap.
112
+ - **`I18n` reads YAML tables keyed by locale.** A key falls back from `de-AT`
113
+ to `de` to the default. Plurals follow CLDR rules, and `I18n.missing` decides
114
+ what a missing key shows. `load_file` is gone. See
115
+ [docs/api/localization.md](docs/api/localization.md).
116
+ - **`ScreenWrap` and `DespawnOffscreen` work in world space**, so a node under an
117
+ offset parent wraps at the world's edge. Their `width:` and `height:` are now
118
+ optional. A node with two responses to the edge raises when the second
119
+ attaches.
120
+ - **A component raises when its sibling is missing.** `PlayerController`,
121
+ `WanderController` and `AnimatedSprite` name what they need when they attach.
122
+ A node with two movers raises too.
123
+ - **`AnimatedSprite` follows any mover**, and faces along the larger axis of
124
+ its heading.
125
+ - **Engine internals are private.** `Renderer`'s raw `draw_*`, `push_*`, `pop`
126
+ and `*_record` methods, `Recording#draw_at` and `Song#play_looping` are
127
+ private. Call `rect`, `translated`, `record`, `Recording#draw` and `Song#play`
128
+ instead. The Tiled parse helpers are private too.
129
+
130
+ ### Removed
131
+
132
+ - `Engine::CachedLabel`. Use `Engine::Text`.
133
+ - `Engine::Body`, `Engine::Actor`, `Engine::Matrix`, `Engine::Resettable` and
134
+ `Engine::PlayerController`. None had a caller in the engine.
135
+ `Components::PlayerController` stays.
136
+ - `Engine::TileCollision`, which is now `Engine::TileBlockers`.
137
+ - The examples `14_asteroids`, `15_tiled_world` and `16_hello_world`. They are
138
+ whole games rather than examples of one concept, and no longer ship.
139
+
140
+ ### Fixed
141
+
142
+ - The README's hello-world gave `on_draw` one parameter; it takes two
143
+ (`renderer, view`).
144
+ - A new `UI::Menu` drew its first item without a highlight until the first
145
+ press.
146
+ - `Path#distance_to` returned wrong distances for Integer coordinates.
147
+
15
148
  ## [0.2.0] - 2026-08-26
16
149
 
17
150
  ### Added
@@ -89,6 +222,7 @@ First release, and the first version that runs a game end to end.
89
222
  - **`RGame::Game`** — the entry point that wires the two halves together.
90
223
  - Examples: `14_asteroids`, `15_tiled_world`, `16_hello_world`.
91
224
 
92
- [Unreleased]: https://github.com/psuessenb/rgame/compare/v0.2.0...HEAD
225
+ [Unreleased]: https://github.com/psuessenb/rgame/compare/v0.3.0...HEAD
226
+ [0.3.0]: https://github.com/psuessenb/rgame/compare/v0.2.0...v0.3.0
93
227
  [0.2.0]: https://github.com/psuessenb/rgame/compare/v0.1.0...v0.2.0
94
228
  [0.1.0]: https://github.com/psuessenb/rgame/releases/tag/v0.1.0
data/README.md CHANGED
@@ -4,7 +4,7 @@ RGame is a small 2D game engine for Ruby, written in Ruby and C. It's build on t
4
4
 
5
5
  While still a work in progress, RGame aims to be more than a SDL/OpenGL
6
6
  binding - it ships with high level features like a scene graph, sprites,
7
- collision systems, debugging tools and an UI toolkit.
7
+ collision systems, debugging tools and an UI toolkit. You can check out the examples to get a feel for its capabilities.
8
8
 
9
9
  ## Why does this exist and should you use it?
10
10
 
@@ -14,19 +14,41 @@ RGame puts a lot of emphazis on testing and being testable: It separates the lay
14
14
 
15
15
  It also tries to marry the beauty of Ruby with the hard performance requirements of games: Hot paths have no per-frame allocation, because garbadge collection is what really slows down Ruby interpreation, and math-heavy use-cases are backed by C code instead of Ruby classes.
16
16
 
17
- Should you use it, though? If you're looking for something mature, free, and more battle-tested take a look at Godot instead. If you're looking for something mature and battle-tested in Ruby land, take a look at dragonruby instead (it's not free, but it's probably worth the price).
17
+ Should you use it, though? If you're looking for something mature, free, and more battle-tested take a look at Godot instead. If you're looking for something mature and battle-tested _in Ruby land_, take a look at dragonruby instead (it's not free, but it's probably worth the price).
18
18
 
19
19
  If you're just starting with game development and planning on making the next big indie hit, might as well pick this one as the engine for the game you never finish!
20
20
 
21
- In all seriousness, though: This is a hobby project of mine, and while it might develop into something actually useful, at the time of writing it's a playground. If you search for something I searched and found nothing, you can try this. I would be really happy if someone else actually uses it, but at this point I can't really recommend it for anything else than small projects and/or learning the ropes of game development.
21
+ In all seriousness, though: This is a hobby project of mine, and while it might develop into something actually useful, at the time of writing it's a playground. If you search for something I searched and found nothing - try RGame! If you want to learn how to write games or just need a small prototype, and you really like Ruby - try RGame! If all you know is Rails, but you want to make a game that doesn't run in a browser and don't care about shipping it - try RGame!
22
22
 
23
- ## Hello world
23
+ I would be really happy if someone else actually uses it, but at this point I can't really recommend it for anything else than small projects and/or learning the ropes of game development.
24
+
25
+ ## Getting started
26
+
27
+ Installing the gem puts an `rgame` command on your PATH, which you can use to setup a project:
24
28
 
25
29
  ```
30
+ gem install rgame
31
+ rgame new tictactoe
32
+
33
+ cd tictactoe
34
+ bundle install
35
+ bundle exec rspec # the game logic, headless — no window needed
36
+ ruby main.rb # the game itself
37
+ ```
38
+
39
+ What it writes is small — a game class, a root node, a spec and the usual configuration — but it is laid out the way the engine wants to be used: one file loads SDL, everything else stays graphics-free and therefore testable with no display. [The `rgame` command](docs/api/cli.md) explains the layout and why it matters.
40
+
41
+ `gem install` compiles two C extensions, so the [requirements](#requirements) below have to be in place first. That's the one big hurdle at the moment: This gem ships source-only, so you have to compile a lot of C _on your machine_ to get this running.
42
+
43
+ ## Hello world
44
+
45
+ The simplest "game" you can write, all in one file:
46
+
47
+ ```ruby
26
48
  require 'rgame/game'
27
49
 
28
50
  class Scene < RGame::Engine::Node2D
29
- def on_draw(renderer)
51
+ def on_draw(renderer, _view)
30
52
  renderer.text('Hello world!', 250, 200)
31
53
  end
32
54
  end
@@ -41,9 +63,40 @@ game.start
41
63
 
42
64
  You can learn more about how it works in the [documentation](docs/api/README.md).
43
65
 
66
+ ## Examples
67
+
68
+ `examples/` holds one small program per concept, each a single file you can run with `ruby examples/<name>/main.rb`. [The examples page](docs/api/examples.md) describes them in more detail. They're also all full of code comments so you have an easier time learning the concepts.
69
+
70
+ | Example | Shows |
71
+ |---|---|
72
+ | [walk](docs/api/examples.md#walk) | A player-controlled sprite — a node, three components, input as actions |
73
+ | [sprite](docs/api/examples.md#sprite) | One frame drawn at a node, with no animation behind it |
74
+ | [velocity](docs/api/examples.md#velocity) | Movement with nobody driving: a velocity, integrated |
75
+ | [scroll_map](docs/api/examples.md#scroll_map) | A Tiled map larger than the window, scrolled by a camera |
76
+ | [collision](docs/api/examples.md#collision) | Two shapes touching, and who gets told about it |
77
+ | [collision_tiles](docs/api/examples.md#collision_tiles) | Walking into a wall of solid tiles, and sliding along it |
78
+ | [jump_topdown](docs/api/examples.md#jump_topdown) | A hop in a top-down view, where the sprite rises and the feet stay on the ground |
79
+ | [signals](docs/api/examples.md#signals) | A node announcing something happened, to nobody in particular |
80
+ | [timer](docs/api/examples.md#timer) | Things that happen on a clock, with nothing pressed |
81
+ | [pooling](docs/api/examples.md#pooling) | Spawning a lot of things without allocating them |
82
+ | [game_menu](docs/api/examples.md#game_menu) | A menu over a world that keeps running |
83
+ | [menu_navigation](docs/api/examples.md#menu_navigation) | Several screens, and settings that persist |
84
+ | [radial_menu](docs/api/examples.md#radial_menu) | A wheel of icons, chosen by the direction of the stick |
85
+ | [quick_wheel](docs/api/examples.md#quick_wheel) | A wheel held open by a button and chosen by letting go |
86
+ | [skill_bar](docs/api/examples.md#skill_bar) | A row of tools, stepped through or fired by hotkeys |
87
+ | [sound](docs/api/examples.md#sound) | A sound effect fired by a button, and the seam it travels |
88
+ | [music](docs/api/examples.md#music) | A looping track, started and stopped |
89
+ | [split_screen](docs/api/examples.md#split_screen) | Two players in one world, drawn once per viewport |
90
+ | [input_glyphs](docs/api/examples.md#input_glyphs) | Prompts that match the device in your hands |
91
+ | [fullscreen](docs/api/examples.md#fullscreen) | Fullscreen, switched at any time, and the scale modes |
92
+ | [save_load](docs/api/examples.md#save_load) | Writing game state to disk and putting it back |
93
+ | [save_load_ids](docs/api/examples.md#save_load_ids) | A save that has to name things, and why a reference forces ids |
94
+ | [localization](docs/api/examples.md#localization) | The same screen in two languages, switched and remembered |
95
+ | [pathfinding](docs/api/examples.md#pathfinding) | Setting a target for an actor and let if find its way there |
96
+
44
97
  ## Requirements
45
98
 
46
- At the moment this gem ships only source-code and no precompiled binaries, which unfortunately means you need to compile a bunch of C code on your locale machine.
99
+ At the moment this gem ships only source-code and no precompiled binaries, which unfortunately means you need to compile a bunch of C code on your locale machine. This _also_ means you need to install some system libaries and have header files present.
47
100
 
48
101
  ### C engine
49
102
 
@@ -67,20 +120,15 @@ fixture and needs `libvorbisenc` to run.
67
120
 
68
121
  ### Ruby side
69
122
 
70
- - **Ruby 4.0.5**, pinned in `.ruby-version`. Installed here with
71
- [mise](https://mise.jdx.dev) (`mise install` in the project root picks up
72
- `.ruby-version`); any version manager that reads `.ruby-version` works just
73
- as well.
74
- - **Ruby development headers.** Version-manager builds (mise, rbenv, rvm,
75
- asdf) include them. On a distro-packaged Ruby, install `ruby-dev`
123
+ - **Ruby 4.0.5**, pinned in `.ruby-version`.
124
+ - **Ruby development headers.** Version-manager builds (mise, rbenv, rvm, asdf) include them. On a distro-packaged Ruby, install `ruby-dev`
76
125
  (Debian/Ubuntu). These are what `extconf.rb` compiles against.
77
126
  - **Bundler**, then `bundle install` for the dev/test gems (RSpec, RuboCop).
78
127
 
79
- Nothing else — the engine has no runtime Ruby dependencies, and the `Gemfile`
80
- holds only development gems.
128
+ Nothing else — the engine has no runtime Ruby dependencies, and the `Gemfile` holds only development gems.
81
129
 
82
130
  All three platforms below are built and tested on every push by
83
- [CI](.github/workflows/ci.yml).
131
+ [CI](.github/workflows/ci.yml) - with that people have usually at home, so Apple Silicon and not Apple Intel, etc.
84
132
 
85
133
  ### Debian / Ubuntu
86
134
 
@@ -180,7 +228,7 @@ rake spec # headless specs: RGame::Util, the engine layer, packaging
180
228
  rake spec:core # RGame::Core specs; opens real windows, boots its own Xvfb
181
229
  rake # everything: make test, rake spec, rake spec:core
182
230
  bundle exec rubocop # lint; configured in .rubocop.yml, which also loads the
183
- # project's own cops from rubocop/cop/game/.
231
+ # project's own cops from lib/rgame/rubocop/.
184
232
  ```
185
233
 
186
234
  The two Ruby suites are two directories and two processes on purpose. `spec/`
@@ -208,8 +256,7 @@ ruby ext/rgame_core/example.rb
208
256
 
209
257
  ## Packaging
210
258
 
211
- Both extensions and the Ruby layer ship as one gem, built from `rgame.gemspec`
212
- and published at [rubygems.org/gems/rgame](https://rubygems.org/gems/rgame):
259
+ Both extensions and the Ruby layer ship as one gem, built from `rgame.gemspec` and published at [rubygems.org/gems/rgame](https://rubygems.org/gems/rgame):
213
260
 
214
261
  ```
215
262
  gem install rgame # from RubyGems; compiles both extensions here
@@ -241,10 +288,7 @@ The version is `RGame::VERSION` in [lib/rgame/version.rb](lib/rgame/version.rb).
241
288
 
242
289
  ## Project structure
243
290
 
244
- The C lives under `ext/rgame_core/` rather than a top-level `src/`, because
245
- `gem install` runs each `extconf.rb` and an extension can only build sources
246
- inside its own directory — so one copy of the code serves both the standalone
247
- binary and the gem. The Ruby half is split the same way it is namespaced:
291
+ The C lives under `ext/rgame_core/` rather than a top-level `src/`, because`gem install` runs each `extconf.rb` and an extension can only build sources inside its own directory — so one copy of the code serves both the standalone binary and the gem. The Ruby half is split the same way it is namespaced:
248
292
  `lib/rgame/util/`, `lib/rgame/core/` and `lib/rgame/engine/`.
249
293
 
250
294
  A file-by-file map of the whole repository is in
@@ -252,49 +296,7 @@ A file-by-file map of the whole repository is in
252
296
 
253
297
  ## Roadmap
254
298
 
255
- All three layers exist and the engine is usable end to end: the games under
256
- [examples/](examples/) are written against exactly what is documented.
257
-
258
- **Done**
259
-
260
- 1. **C engine** — an SDL2 window and a fixed-timestep loop, keyboard and
261
- gamepad input with hot-plug, a z-sorted batching renderer with transforms,
262
- clipping and baked recordings, text from a shipped TrueType font, and audio.
263
- Linux, macOS and Windows are all supported and all gated by CI.
264
- 2. **Ruby C extensions** — both halves. `RGame::Core` binds
265
- `include/rgame/core.h` (the app, the renderer, images, fonts, recordings,
266
- sound); `RGame::Util` is the graphics-free one, so values can be required
267
- without pulling SDL and OpenGL into the process.
268
- 3. **Pure-Ruby half** — `RGame::Engine`, the layer a game is actually written
269
- in: the scene graph, components, signals, tile maps, collision,
270
- pathfinding, and split-screen players with a camera and a binding table
271
- each. `RGame::Game` wires it to `RGame::Core` and is the only class allowed
272
- to name both.
273
- 4. **Gem** — `rgame.gemspec` packages both halves, compiling each extension
274
- into `lib/rgame/` on install the way `make ext` does in a checkout.
275
- [Published to RubyGems](https://rubygems.org/gems/rgame), so
276
- `gem install rgame` works.
277
-
278
- **Next**
279
-
280
- - **A UI package worth the name.** What exists covers a region per player,
281
- focus and activation — enough for keyboard-and-controller menus. Layout,
282
- nesting, scrolling lists and text entry are all still open; see
283
- ["What this is not"](docs/api/ui.md#what-this-is-not).
284
- - **Precompiled binary gems**, so installing needs no compiler. The compiling
285
- is the easy part — CI already does it on three platforms — and the real
286
- blocker is that the binary still needs SDL2 at runtime. The options are
287
- written up in
288
- [docs/plans/precompiled-binary-gems.md](docs/plans/precompiled-binary-gems.md).
289
- - **Hot paths into C, where profiling says so** — the nine-slice tiling loops
290
- and the animated-tile draw loop are the candidates. Deliberately last: each
291
- is a straightforward move once the geometry is separable, and doing it early
292
- would trade readability for a speedup nobody has measured.
293
-
294
- Also known and deliberately deferred: the drawing path uses legacy
295
- compatibility-profile OpenGL (`glBegin`/`glEnd`), which needs no loader library.
296
- Moving to core-profile GL is a decision to take on purpose, not a drive-by
297
- change.
299
+ There is currently no roadmap for the next version.
298
300
 
299
301
  ## AI clause
300
302