rgame 0.2.0 → 0.3.1

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 +144 -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 +3 -5
  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 +167 -12
  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: 80fa9ee53085a7b37d554bb91491624d281e26cc366164b381fa677a1834a1e6
4
+ data.tar.gz: e14d15b1cd58be28af62f699eb61bd570ea23035ccd00d1e6db0360056179740
5
5
  SHA512:
6
- metadata.gz: f71a457f48b0f45206b03c68e8dfc8f222c9c2a87957ac2c44edd0f2330dc3412d82874544176379143a5ccf7e24596665253e1a75983d475fd8273a500bcdbe
7
- data.tar.gz: f3c7ac9d5663a9e0f0df3823be78c7cf7496176982e86933e5ad8c19293ade3c21320828127e4a31086f21b44a75096cde3b9b6a4695a51180517ba9f700313b
6
+ metadata.gz: bf661cb9d7828cce921ec971a352a078513d2b61edf1eb9f149b8c8d90843bdf9a959ca4c33ad54cf3babd16847b12bc6ad07a5ecdfa629dfe4d295331e994d9
7
+ data.tar.gz: aa7a3f5d7447bcf96f7c6c48666392a3ecc9baf49c3f6ffc81c360087e0d7bb4185d20d0c5f50cc297ee632178f132ae83be11daf57ee4c966d534b3c49f92ec
data/CHANGELOG.md CHANGED
@@ -12,6 +12,147 @@ index, not the argument.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.3.1] - 2026-09-15
16
+
17
+ ### Fixed
18
+
19
+ - `require "rgame"` failed under Bundler with `cannot load such file --
20
+ rexml/document`. The gem now declares `rexml` as a dependency, which Ruby 4.0
21
+ no longer loads without one.
22
+
23
+ ## [0.3.0] - 2026-09-15
24
+
25
+ ### Added
26
+
27
+ - **`rgame new NAME`.** The gem installs an `rgame` command, and `rgame new`
28
+ generates a project: a game class, a root node, an English translation table,
29
+ and a spec suite and RuboCop config that pass. Only `game.rb` loads SDL, so the
30
+ generated specs run without a display. See [docs/api/cli.md](docs/api/cli.md).
31
+ - **RuboCop cops for games.** The gem ships rgame's `Game/` cops as a RuboCop
32
+ plugin, and `rgame new` enables them. They catch per-frame allocations, a node
33
+ drawn at its own position twice, literal text and Core named from headless
34
+ code. See [docs/api/cli.md](docs/api/cli.md#the-generated-rubocop-configuration).
35
+ - **Translation by default.** `RGame::Game` loads every `locales/**/*.yml` and
36
+ picks the player's language from `RGame::Core.preferred_locales`. A node draws
37
+ an `Engine::Text` built from a key, and a UI button's `label:` is a key. See
38
+ [docs/api/localization.md](docs/api/localization.md).
39
+ - **Fullscreen and scaling.** `Game.new` takes `fullscreen:` and `scale_mode:`,
40
+ and both can change while the game runs. `scale_mode:` maps the logical size
41
+ onto the window, and defaults to `:letterbox`. See
42
+ [docs/api/game.md](docs/api/game.md).
43
+ - **Saving.** `Util::SaveFile` reads and writes a game's state in the player's
44
+ save directory. `Components::Identity` gives a node a stable id to save it
45
+ under. See [docs/api/values.md](docs/api/values.md).
46
+ - **Box colliders.** `Components::BoxCollider` is a rectangle in the same
47
+ `CollisionWorld` as `CircleCollider`. `FeetCollider` derives its box from the
48
+ node's size, so a top-down character collides at its feet. See
49
+ [docs/api/components.md](docs/api/components.md).
50
+ - **Movers that something can stop.** `CharacterBody`, `Velocity` and
51
+ `PathFollow` share the base `Components::Mover`. Its `blocked_by:` lists what
52
+ stops a step: solid tiles, the world's edge or collider layers. `on_blocked` and
53
+ `on_unblocked` report the blocker and the axis. See
54
+ [docs/api/components.md](docs/api/components.md).
55
+ - **Pathfinding.** `Engine::NavGrid` finds routes over a tile grid, and
56
+ `TileWorld#nav_grid` hands one out. `Components::Navigator#go_to` walks a node
57
+ there. The search runs in C, in `Util::SolidGrid`, `Util::RouteSearch` and
58
+ `Util::TileSweep`. See [docs/api/toolbox.md](docs/api/toolbox.md).
59
+ - **`Components::World`** gives a scene its bounds without a tile map.
60
+ `ScreenWrap` and `DespawnOffscreen` read the bounds from it or from
61
+ `TileWorld`. See [docs/api/components.md](docs/api/components.md).
62
+ - **`Components::Hop`** lifts a character's picture off the ground through the
63
+ new `Node2D#elevation`. Colliders and cameras ignore the lift. See
64
+ [docs/api/components.md](docs/api/components.md).
65
+ - **New buttons and styles.** `UI::Button` is the base for a button with its
66
+ own look. `TextButton`, `PanelButton`, `OptionButton` and `IconButton` draw
67
+ their background from a `NineSliceStyle` or a `ShapeStyle`. A `hotkey:` presses
68
+ a button without focusing it. See [docs/api/ui.md](docs/api/ui.md).
69
+ - **Menu layouts and navigations.** `UI::Menu` takes a `layout:` (`Column`,
70
+ `Row` or `Ring`) and a `navigation:` (`Stepping`, `Pointing` or `nil`).
71
+ `UI::RadialMenu` focuses whatever the stick points at. `UI::PanelMenu` draws a
72
+ panel that grows with its buttons. See [docs/api/ui.md](docs/api/ui.md).
73
+ - **Menus open and close.** A menu answers `open?`, `open` and `close`, and
74
+ emits `on_opened` and `on_closed`. A closed menu draws nothing and takes no
75
+ input. `trigger:` names an action that holds a menu open while it is down. See
76
+ [docs/api/ui.md](docs/api/ui.md).
77
+ - **A UI atlas can name images.** A descriptor's `images` section cuts
78
+ rectangles from the sheet, and `Renderer#register_ui_atlas` registers each by
79
+ name. `UI::IconButton.new(image: :home)` then draws one.
80
+ - **Input prompts.** `InputMap#button_for(action, device)` returns the bound
81
+ button that device can press. `Controls.gamepad?` and `Controls.pad_button?`
82
+ tell the two kinds of id apart. See [docs/api/input.md](docs/api/input.md).
83
+ - **Sounds by path.** `audio.play_sound` and `play_music` accept a path and
84
+ load it through the asset manager on first use. `RGame::Game` subscribes an
85
+ `AudioDirector`, so `AudioBus` sounds play without setup. See
86
+ [docs/api/audio.md](docs/api/audio.md).
87
+ - **24 examples, one concept each.** Each is a single `main.rb` that runs on its
88
+ own, from `walk` and `sprite` to `pathfinding` and `localization`. See
89
+ [docs/api/examples.md](docs/api/examples.md).
90
+ - `AssetManager#glob` lists the files under the media root that match a pattern.
91
+ - `Util::Color` gains named colours, from `RED` to `DARK_GRAY`.
92
+
93
+ ### Changed
94
+
95
+ - **A subclass may not redefine `Node2D`'s or `Component`'s internal methods.**
96
+ Their private and protected machinery now starts with `_`. A subclass that
97
+ defines one of those names raises `NameError` where the class is defined.
98
+ Before, it silently switched that machinery off. See
99
+ [docs/api/scene_graph.md](docs/api/scene_graph.md).
100
+ - **Nodes draw in local space.** `Node2D#draw` pushes the node's transform
101
+ before it calls `on_draw`, so a node draws at its own origin:
102
+ `renderer.rect(0, 0, width, height)`. Drop `abs_x`/`abs_y` from draw calls, or
103
+ the position applies twice. See
104
+ [docs/api/scene_graph.md](docs/api/scene_graph.md).
105
+ - **`abs_x`, `abs_y` and `abs_angle` are now `world_x`, `world_y` and
106
+ `world_angle`.** A node computes them when read, so they are never a tick
107
+ stale. `world_x=` and `world_y=` place a node in world space. See
108
+ [docs/api/scene_graph.md](docs/api/scene_graph.md).
109
+ - **`UI::Menu` is handed its buttons.** `Menu#add(button)` replaces `add_item`,
110
+ `items` is now `buttons`, and `MenuItem` is now `UI::PanelButton`. The item
111
+ size moves to `layout: UI::Column.new(...)`. Confirm activates a button when
112
+ released. See [docs/api/ui.md](docs/api/ui.md).
113
+ - **`CharacterBody` takes its shape from a sibling collider.**
114
+ `CharacterBody.new(speed:, blocked_by:)` replaces `feet_width:` and
115
+ `feet_height:`. A body with no `blocked_by:` moves freely, and `TileWorld#move`
116
+ is gone. See [docs/api/components.md](docs/api/components.md).
117
+ - **Contacts are edges.** `on_hit` fires once when two colliders start to
118
+ overlap, and the new `on_separated` fires once when they part. Before,
119
+ `on_hit` fired on every step of an overlap.
120
+ - **`I18n` reads YAML tables keyed by locale.** A key falls back from `de-AT`
121
+ to `de` to the default. Plurals follow CLDR rules, and `I18n.missing` decides
122
+ what a missing key shows. `load_file` is gone. See
123
+ [docs/api/localization.md](docs/api/localization.md).
124
+ - **`ScreenWrap` and `DespawnOffscreen` work in world space**, so a node under an
125
+ offset parent wraps at the world's edge. Their `width:` and `height:` are now
126
+ optional. A node with two responses to the edge raises when the second
127
+ attaches.
128
+ - **A component raises when its sibling is missing.** `PlayerController`,
129
+ `WanderController` and `AnimatedSprite` name what they need when they attach.
130
+ A node with two movers raises too.
131
+ - **`AnimatedSprite` follows any mover**, and faces along the larger axis of
132
+ its heading.
133
+ - **Engine internals are private.** `Renderer`'s raw `draw_*`, `push_*`, `pop`
134
+ and `*_record` methods, `Recording#draw_at` and `Song#play_looping` are
135
+ private. Call `rect`, `translated`, `record`, `Recording#draw` and `Song#play`
136
+ instead. The Tiled parse helpers are private too.
137
+
138
+ ### Removed
139
+
140
+ - `Engine::CachedLabel`. Use `Engine::Text`.
141
+ - `Engine::Body`, `Engine::Actor`, `Engine::Matrix`, `Engine::Resettable` and
142
+ `Engine::PlayerController`. None had a caller in the engine.
143
+ `Components::PlayerController` stays.
144
+ - `Engine::TileCollision`, which is now `Engine::TileBlockers`.
145
+ - The examples `14_asteroids`, `15_tiled_world` and `16_hello_world`. They are
146
+ whole games rather than examples of one concept, and no longer ship.
147
+
148
+ ### Fixed
149
+
150
+ - The README's hello-world gave `on_draw` one parameter; it takes two
151
+ (`renderer, view`).
152
+ - A new `UI::Menu` drew its first item without a highlight until the first
153
+ press.
154
+ - `Path#distance_to` returned wrong distances for Integer coordinates.
155
+
15
156
  ## [0.2.0] - 2026-08-26
16
157
 
17
158
  ### Added
@@ -89,6 +230,8 @@ First release, and the first version that runs a game end to end.
89
230
  - **`RGame::Game`** — the entry point that wires the two halves together.
90
231
  - Examples: `14_asteroids`, `15_tiled_world`, `16_hello_world`.
91
232
 
92
- [Unreleased]: https://github.com/psuessenb/rgame/compare/v0.2.0...HEAD
233
+ [Unreleased]: https://github.com/psuessenb/rgame/compare/v0.3.1...HEAD
234
+ [0.3.1]: https://github.com/psuessenb/rgame/compare/v0.3.0...v0.3.1
235
+ [0.3.0]: https://github.com/psuessenb/rgame/compare/v0.2.0...v0.3.0
93
236
  [0.2.0]: https://github.com/psuessenb/rgame/compare/v0.1.0...v0.2.0
94
237
  [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's one runtime gem is `rexml`, which Bundler installs with it, 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