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
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+
6
+ module RGame
7
+ module Util
8
+ # A game's saved state, as one JSON file.
9
+ #
10
+ # save = SaveFile.new('slot1.json', game: 'sheepdog')
11
+ # save.write(dog: [120, 80], sheep: [[40, 40], [90, 30]])
12
+ # save.read # => { dog: [120, 80], sheep: [...] }
13
+ # save.read.fetch(:dog, nil) # => nil when there is no save yet
14
+ #
15
+ # It holds no open handle — it opens, reads or writes, and closes — which is
16
+ # why this is `Util` rather than `Core` (see CLAUDE.md, "Value objects go in
17
+ # Util"). JSON comes from the standard library, so nothing here adds a
18
+ # runtime dependency.
19
+ #
20
+ # ## Reading never raises
21
+ #
22
+ # `read` answers with the default for a file that is missing, empty,
23
+ # truncated, not JSON, or JSON that is not an object. That is deliberate and
24
+ # it is the whole reason this class exists rather than three lines of
25
+ # `JSON.parse(File.read(...))` at a call site.
26
+ #
27
+ # A save file is the one input a game has that it did not produce this run.
28
+ # It survives crashes, disk-full, a killed process mid-write, an editor, and
29
+ # a copy from someone else's machine. A game that raises on any of those is
30
+ # a game that cannot be started again, and the player's only fix is to find
31
+ # and delete a file they were never told about. Losing a save is bad;
32
+ # refusing to launch is worse.
33
+ #
34
+ # **Writing still raises.** A read failure has a sensible answer — "there is
35
+ # no save" — and a write failure does not: silently discarding the player's
36
+ # progress is the failure that gets noticed hours later. A caller that wants
37
+ # to survive a full disk can rescue; it cannot un-swallow an exception this
38
+ # class never raised.
39
+ #
40
+ # ## Writing is atomic
41
+ #
42
+ # `write` writes a temporary file beside the target and renames it over the
43
+ # top. `File.rename` within one directory is atomic on every platform this
44
+ # engine supports, so a save is either the old one or the new one and never
45
+ # half of each.
46
+ #
47
+ # This matters more than it sounds: the moment a game most wants to save is
48
+ # on the way out, which is also the moment it is most likely to be killed.
49
+ # A plain `File.write` truncates first and fills in after, so a crash in
50
+ # between leaves a zero-byte file where the player's afternoon used to be.
51
+ class SaveFile
52
+ # Where saves go, per platform, following each one's own convention rather
53
+ # than dropping a dotfile in the home directory:
54
+ #
55
+ # Linux $XDG_DATA_HOME/<game>, or ~/.local/share/<game>
56
+ # macOS ~/Library/Application Support/<game>
57
+ # Windows %APPDATA%\<game>
58
+ #
59
+ # `game` names the directory, so two games do not share a save.
60
+ def self.directory(game)
61
+ case RbConfig::CONFIG['host_os']
62
+ when /darwin/ then File.join(Dir.home, 'Library', 'Application Support', game)
63
+ when /mswin|mingw|cygwin/ then File.join(ENV.fetch('APPDATA', Dir.home), game)
64
+ else File.join(ENV.fetch('XDG_DATA_HOME', File.join(Dir.home, '.local', 'share')), game)
65
+ end
66
+ end
67
+
68
+ attr_reader :path
69
+
70
+ # `name` is a file name inside the game's save directory; pass `dir:` to
71
+ # put it somewhere else, which is what a spec does.
72
+ def initialize(name, game: 'rgame', dir: nil)
73
+ @path = File.join(dir || SaveFile.directory(game), name)
74
+ end
75
+
76
+ def exist? = File.exist?(@path)
77
+
78
+ # The saved state, or `default` if there is nothing usable to read. Keys
79
+ # come back as Symbols, so a game writes and reads the same shape.
80
+ def read(default = {})
81
+ parsed = JSON.parse(File.read(@path), symbolize_names: true)
82
+ parsed.is_a?(Hash) ? parsed : default
83
+ rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, JSON::ParserError
84
+ default
85
+ end
86
+
87
+ # Writes `state` as JSON, atomically. Creates the directory if it is not
88
+ # there — a first save on a fresh machine is the normal case, not an error.
89
+ def write(state)
90
+ FileUtils.mkdir_p(File.dirname(@path))
91
+ temporary = "#{@path}.#{Process.pid}.tmp"
92
+ File.write(temporary, JSON.pretty_generate(state))
93
+ File.rename(temporary, @path)
94
+ self
95
+ end
96
+
97
+ # Removes the save, and says nothing if there was not one. "Delete my
98
+ # save" and "there was no save" leave the player in the same place.
99
+ def delete
100
+ File.unlink(@path)
101
+ self
102
+ rescue Errno::ENOENT
103
+ self
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rgame/util_ext'
4
+
5
+ module RGame
6
+ module Util
7
+ # Which cells of a tile grid are solid: one byte a cell, in C. A value with no handle
8
+ # behind it, which is why it lives in Util and why the engine layer may own one.
9
+ #
10
+ # It is the one store a tile world's solidity lives in. The blockers that stop a walker
11
+ # and the RouteSearch that plans one both read it, so they cannot disagree about a wall,
12
+ # and a cell changed with #set_solid is changed for both at once.
13
+ #
14
+ # #revision counts the changes: it moves on a #set_solid that changes a cell and on no
15
+ # other, so anything derived from the cells — a search's region labels — can tell
16
+ # whether it is stale without being told.
17
+ #
18
+ # Coordinates are Integers, and anything else is a TypeError. Outside the grid is open,
19
+ # including an Integer too large to be inside any grid.
20
+ #
21
+ # grid = RGame::Util::SolidGrid.build(8, 3) { |col, _row| col == 3 }
22
+ # grid.solid?(3, 1) # => true
23
+ # grid.solid?(-1, 0) # => false
24
+ # grid.set_solid(3, 1, false)
25
+ # grid.revision # => 1
26
+ class SolidGrid
27
+ # A grid with each cell's solidity read from the block, once per cell, row by row.
28
+ def self.build(width, height)
29
+ grid = new(width, height)
30
+ height.times do |row|
31
+ width.times { |col| grid.set_solid(col, row, true) if yield(col, row) }
32
+ end
33
+ grid
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,12 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # RGame::Util::Tensor is implemented in C — see ext/rgame_util/tensor.c. It used
4
- # to be a pure-Ruby class here; the public API (RGame::Util::Tensor.new(width,
5
- # height, depth, initial: nil), #[], #[]=, #width/#height/#depth) is unchanged,
6
- # only the backing implementation moved to C for speed and a compact flat-array
7
- # layout.
8
- #
9
- # The compiled extension is a separate .so from the SDL/GL engine, so requiring
10
- # Tensor pulls in no graphics libraries. It loads from lib/rgame/util_ext.so,
11
- # which the build (`make ext-util`) copies out of ext/rgame_util/.
12
3
  require 'rgame/util_ext'
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rgame/util_ext'
4
+
5
+ module RGame
6
+ module Util
7
+ # An axis-aligned box against the solid tiles of a SolidGrid, in C. It reads its grid and
8
+ # never writes it, and keeps it alive; any number of sweeps may read one grid, so a change
9
+ # made with SolidGrid#set_solid is seen by all of them at once.
10
+ #
11
+ # It answers two questions with one piece of arithmetic, so they cannot disagree:
12
+ #
13
+ # - **#resolve_x / #resolve_y** — where the box's left (top) edge lands moving dx (dy). A
14
+ # step that would enter a solid tile snaps the box flush against that tile's edge. Each
15
+ # axis is resolved on its own. The step is assumed smaller than a tile; a longer one can
16
+ # pass through a tile without seeing it.
17
+ # - **#travel?** — whether the box can move by (dx, dy) without any resolve along the way
18
+ # falling short of where it was heading. The box is swept in overlapping windows half a
19
+ # tile long, a quarter tile apart, each resolved X-then-Y and Y-then-X, which makes the
20
+ # answer sound for a walker that resolves its own steps and steps less than a quarter tile
21
+ # at a time.
22
+ #
23
+ # Boxes are given by their top-left corner and size, in pixels. Outside the grid is open.
24
+ # Results are Floats. A coordinate that is not a number is a TypeError; a non-finite one is
25
+ # a FloatDomainError, except in a resolve with no movement, which returns the box where it
26
+ # is.
27
+ #
28
+ # grid = RGame::Util::SolidGrid.build(20, 15) { |col, _row| col == 5 } # a wall at x 80..96
29
+ # sweep = RGame::Util::TileSweep.new(grid, 16, 16)
30
+ # sweep.resolve_x(58.0, 32.0, 12, 6, 14.0) # => 68.0, flush against the wall
31
+ # sweep.travel?(10.0, 32.0, 12, 6, 50.0, 0) # => true
32
+ # sweep.travel?(10.0, 32.0, 12, 6, 90.0, 0) # => false
33
+ class TileSweep
34
+ end
35
+ end
36
+ end
data/lib/rgame/util/z.rb CHANGED
@@ -45,29 +45,15 @@ module RGame
45
45
  # swapping places between frames, and would be very hard to see as a
46
46
  # precision problem.
47
47
  module Z
48
- # How much room a node has for ordering its own drawing. Nothing in this
49
- # repo uses more than three offsets; a thousand is room to stop thinking
50
- # about it.
51
48
  SLOT = 1024
52
49
  HALF = SLOT / 2
53
50
 
54
- # What a `z:` argument on a drawing call may be. Signed, because a node
55
- # drawing something *behind* its sprite is as ordinary as drawing
56
- # something in front of it, and the sprite's own default is 0.
57
51
  Z_MIN = -HALF
58
52
  Z_MAX = HALF - 1
59
53
 
60
- # The gap between bands: 2**30 slots each, which no frame will approach.
61
54
  STRIDE = 1 << 40
62
55
  SLOTS_PER_BAND = STRIDE / SLOT
63
56
 
64
- # In order, back to front.
65
- #
66
- # `:world` is what a node with no band ancestor is in, so a game that
67
- # never mentions a band is entirely in it. `:hud` is one player's own
68
- # screen space (RGame::Engine::PlayerLayer), `:overlay` is screen space
69
- # across the whole window — a cutscene, a results panel — and `:debug` is
70
- # the development overlay, over everything by construction.
71
57
  BANDS = %i[world hud overlay debug].freeze
72
58
  DEFAULT = :world
73
59
 
@@ -78,6 +64,8 @@ module RGame
78
64
  # Raises unless `band` names one. Called where a band is *set* rather than
79
65
  # where it is used, so a typo surfaces at assignment with the list in the
80
66
  # message instead of as a KeyError from inside a draw.
67
+ #
68
+ # @api private
81
69
  def self.band!(band)
82
70
  return band if band?(band)
83
71
 
@@ -97,6 +85,8 @@ module RGame
97
85
  # Takes the band's *index* rather than its name because a renderer already
98
86
  # has the index in hand (it counts slots per index), and looking the same
99
87
  # Symbol up twice per node per frame is work with nothing to show for it.
88
+ #
89
+ # @api private
100
90
  # hot-path
101
91
  def self.slot_base(band_index, slot_index)
102
92
  if slot_index >= SLOTS_PER_BAND
data/lib/rgame/util.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # RGame::Util — everything that does NOT depend on SDL, OpenGL, or anything
4
- # built on them. Backed by the graphics-free extension in ext/rgame_util/.
5
- # Its counterpart is RGame::Core (see lib/rgame/core.rb).
6
3
  require_relative 'util/color'
7
4
  require_relative 'util/controls'
5
+ require_relative 'util/route_search'
6
+ require_relative 'util/save_file'
7
+ require_relative 'util/solid_grid'
8
8
  require_relative 'util/tensor'
9
+ require_relative 'util/tile_sweep'
9
10
  require_relative 'util/z'
data/lib/rgame/version.rb CHANGED
@@ -8,5 +8,5 @@
8
8
  # module here is enough; the extensions define `RGame` too, and doing so twice
9
9
  # is harmless.
10
10
  module RGame
11
- VERSION = '0.2.0'
11
+ VERSION = '0.3.1'
12
12
  end
data/lib/rgame.rb CHANGED
@@ -1,20 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Entry point for `require "rgame"`: everything that runs without a window.
4
- #
5
- # require 'rgame' # RGame::Util + RGame::Engine — no graphics libraries
6
- # require 'rgame/core' # adds the window, the GPU and the sound device
7
- # require 'rgame/game' # all of it, wired together
8
- #
9
- # Each line is a strict superset of the one above, and the split is the point.
10
- # **This one loads no SDL and no OpenGL** — `RGame::Util` is a graphics-free
11
- # extension and `RGame::Engine` is pure Ruby — so game logic and the specs that
12
- # cover it run with no display present. `spec/rgame/no_graphics_spec.rb` holds
13
- # that up rather than leaving it to good intentions.
14
- #
15
- # Nothing is forced through this file. `rgame/util`, `rgame/engine` and
16
- # `rgame/core` are separately requirable, which is how the Core spec suite loads
17
- # exactly one layer and gets a `NameError` if it reaches for another.
18
3
  require_relative 'rgame/version'
19
4
  require_relative 'rgame/util'
20
5
  require_relative 'rgame/engine'
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rgame
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Süßenbach
8
- bindir: bin
8
+ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rexml
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.4'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.4'
12
26
  description: |
13
27
  RGame is a small 2D game engine for Ruby, written in Ruby and C. It's build
14
28
  on top of SDL2, OpenGL and miniaudio. It's built with testability and
@@ -21,7 +35,8 @@ description: |
21
35
  collision systems, debugging tools and an UI toolkit.
22
36
  email:
23
37
  - 2452696+psuessenb@users.noreply.github.com
24
- executables: []
38
+ executables:
39
+ - rgame
25
40
  extensions:
26
41
  - ext/rgame_core/extconf.rb
27
42
  - ext/rgame_util/extconf.rb
@@ -34,24 +49,96 @@ files:
34
49
  - docs/api/app.md
35
50
  - docs/api/assets.md
36
51
  - docs/api/audio.md
52
+ - docs/api/cli.md
37
53
  - docs/api/components.md
38
54
  - docs/api/drawing.md
55
+ - docs/api/examples.md
39
56
  - docs/api/game.md
40
57
  - docs/api/images.md
41
58
  - docs/api/input.md
42
59
  - docs/api/internals.md
60
+ - docs/api/localization.md
43
61
  - docs/api/scene_graph.md
44
62
  - docs/api/signals.md
45
63
  - docs/api/systems.md
46
64
  - docs/api/text.md
65
+ - docs/api/tile_maps.md
47
66
  - docs/api/toolbox.md
48
67
  - docs/api/ui.md
49
68
  - docs/api/values.md
69
+ - examples/assets/README.md
70
+ - examples/assets/blip.ogg
71
+ - examples/assets/glyphs.json
72
+ - examples/assets/glyphs.png
73
+ - examples/assets/hero.json
74
+ - examples/assets/hero.png
75
+ - examples/assets/icons.json
76
+ - examples/assets/icons.png
77
+ - examples/assets/music.ogg
78
+ - examples/assets/skills.json
79
+ - examples/assets/skills.png
80
+ - examples/assets/tileset.png
81
+ - examples/assets/tileset.tsx
82
+ - examples/assets/town.tmx
83
+ - examples/assets/ui.json
84
+ - examples/assets/ui.png
85
+ - examples/collision/locales/en.yml
86
+ - examples/collision/main.rb
87
+ - examples/collision_tiles/locales/en.yml
88
+ - examples/collision_tiles/main.rb
89
+ - examples/fullscreen/locales/en.yml
90
+ - examples/fullscreen/main.rb
91
+ - examples/game_menu/locales/en.yml
92
+ - examples/game_menu/main.rb
93
+ - examples/input_glyphs/locales/en.yml
94
+ - examples/input_glyphs/main.rb
95
+ - examples/jump_topdown/locales/en.yml
96
+ - examples/jump_topdown/main.rb
97
+ - examples/localization/locales/de.yml
98
+ - examples/localization/locales/en.yml
99
+ - examples/localization/main.rb
100
+ - examples/menu_navigation/locales/en.yml
101
+ - examples/menu_navigation/main.rb
102
+ - examples/music/locales/en.yml
103
+ - examples/music/main.rb
104
+ - examples/pathfinding/locales/en.yml
105
+ - examples/pathfinding/main.rb
106
+ - examples/pooling/locales/en.yml
107
+ - examples/pooling/main.rb
108
+ - examples/quick_wheel/locales/en.yml
109
+ - examples/quick_wheel/main.rb
110
+ - examples/radial_menu/locales/en.yml
111
+ - examples/radial_menu/main.rb
112
+ - examples/save_load/locales/en.yml
113
+ - examples/save_load/main.rb
114
+ - examples/save_load_ids/locales/en.yml
115
+ - examples/save_load_ids/main.rb
116
+ - examples/scroll_map/locales/en.yml
117
+ - examples/scroll_map/main.rb
118
+ - examples/signals/locales/en.yml
119
+ - examples/signals/main.rb
120
+ - examples/skill_bar/locales/en.yml
121
+ - examples/skill_bar/main.rb
122
+ - examples/sound/locales/en.yml
123
+ - examples/sound/main.rb
124
+ - examples/split_screen/locales/en.yml
125
+ - examples/split_screen/main.rb
126
+ - examples/sprite/locales/en.yml
127
+ - examples/sprite/main.rb
128
+ - examples/timer/locales/en.yml
129
+ - examples/timer/main.rb
130
+ - examples/velocity/locales/en.yml
131
+ - examples/velocity/main.rb
132
+ - examples/walk/locales/en.yml
133
+ - examples/walk/main.rb
134
+ - exe/rgame
50
135
  - ext/README.md
51
136
  - ext/rgame_core/app/app.c
52
137
  - ext/rgame_core/app/app_gl.h
53
138
  - ext/rgame_core/app/frame_loop.c
54
139
  - ext/rgame_core/app/frame_loop.h
140
+ - ext/rgame_core/app/locale.c
141
+ - ext/rgame_core/app/locale.h
55
142
  - ext/rgame_core/audio/audio.c
56
143
  - ext/rgame_core/audio/audio_internal.h
57
144
  - ext/rgame_core/audio/vorbis_decoder.c
@@ -90,6 +177,7 @@ files:
90
177
  - ext/rgame_core/ruby/core_ext.h
91
178
  - ext/rgame_core/ruby/font_ext.c
92
179
  - ext/rgame_core/ruby/image_ext.c
180
+ - ext/rgame_core/ruby/locale_ext.c
93
181
  - ext/rgame_core/ruby/recording_ext.c
94
182
  - ext/rgame_core/ruby/renderer_ext.c
95
183
  - ext/rgame_core/text/atlas.c
@@ -113,11 +201,36 @@ files:
113
201
  - ext/rgame_util/color.h
114
202
  - ext/rgame_util/color_ext.c
115
203
  - ext/rgame_util/extconf.rb
204
+ - ext/rgame_util/route_search.c
205
+ - ext/rgame_util/route_search.h
206
+ - ext/rgame_util/route_search_ext.c
207
+ - ext/rgame_util/solid_grid.c
208
+ - ext/rgame_util/solid_grid.h
209
+ - ext/rgame_util/solid_grid_ext.c
116
210
  - ext/rgame_util/tensor.c
211
+ - ext/rgame_util/tile_sweep.c
212
+ - ext/rgame_util/tile_sweep.h
213
+ - ext/rgame_util/tile_sweep_ext.c
117
214
  - ext/rgame_util/util_ext.c
118
215
  - ext/rgame_util/util_ext.h
119
216
  - lib/rgame.rb
120
217
  - lib/rgame/boot.rb
218
+ - lib/rgame/cli.rb
219
+ - lib/rgame/cli/new_project.rb
220
+ - lib/rgame/cli/templates/Gemfile.tt
221
+ - lib/rgame/cli/templates/README.md.tt
222
+ - lib/rgame/cli/templates/Rakefile.tt
223
+ - lib/rgame/cli/templates/assets/locales/en.yml.tt
224
+ - lib/rgame/cli/templates/game.rb.tt
225
+ - lib/rgame/cli/templates/gitignore.tt
226
+ - lib/rgame/cli/templates/main.rb.tt
227
+ - lib/rgame/cli/templates/nodes/root.rb.tt
228
+ - lib/rgame/cli/templates/rspec.tt
229
+ - lib/rgame/cli/templates/rubocop.yml.tt
230
+ - lib/rgame/cli/templates/ruby-version.tt
231
+ - lib/rgame/cli/templates/spec/locales_spec.rb.tt
232
+ - lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt
233
+ - lib/rgame/cli/templates/spec/spec_helper.rb.tt
121
234
  - lib/rgame/core.rb
122
235
  - lib/rgame/core/app.rb
123
236
  - lib/rgame/core/asset_manager.rb
@@ -126,6 +239,7 @@ files:
126
239
  - lib/rgame/core/gamepad.rb
127
240
  - lib/rgame/core/image.rb
128
241
  - lib/rgame/core/input.rb
242
+ - lib/rgame/core/locale.rb
129
243
  - lib/rgame/core/nine_slice.rb
130
244
  - lib/rgame/core/recording.rb
131
245
  - lib/rgame/core/renderer.rb
@@ -133,13 +247,12 @@ files:
133
247
  - lib/rgame/core/tile_map_renderer.rb
134
248
  - lib/rgame/core/ui_atlas.rb
135
249
  - lib/rgame/engine.rb
136
- - lib/rgame/engine/actor.rb
250
+ - lib/rgame/engine/actor_blockers.rb
137
251
  - lib/rgame/engine/animation_set.rb
138
252
  - lib/rgame/engine/animator.rb
139
253
  - lib/rgame/engine/audio_bus.rb
140
254
  - lib/rgame/engine/audio_director.rb
141
- - lib/rgame/engine/body.rb
142
- - lib/rgame/engine/cached_label.rb
255
+ - lib/rgame/engine/bounds_blockers.rb
143
256
  - lib/rgame/engine/camera.rb
144
257
  - lib/rgame/engine/circle_collider.rb
145
258
  - lib/rgame/engine/collision_box.rb
@@ -147,11 +260,17 @@ files:
147
260
  - lib/rgame/engine/component.rb
148
261
  - lib/rgame/engine/components/action_trigger.rb
149
262
  - lib/rgame/engine/components/animated_sprite.rb
263
+ - lib/rgame/engine/components/box_collider.rb
150
264
  - lib/rgame/engine/components/camera_follow.rb
151
265
  - lib/rgame/engine/components/character_body.rb
152
266
  - lib/rgame/engine/components/circle_collider.rb
153
267
  - lib/rgame/engine/components/collision_world.rb
154
268
  - lib/rgame/engine/components/despawn_offscreen.rb
269
+ - lib/rgame/engine/components/feet_collider.rb
270
+ - lib/rgame/engine/components/hop.rb
271
+ - lib/rgame/engine/components/identity.rb
272
+ - lib/rgame/engine/components/mover.rb
273
+ - lib/rgame/engine/components/navigator.rb
155
274
  - lib/rgame/engine/components/path_follow.rb
156
275
  - lib/rgame/engine/components/player_controller.rb
157
276
  - lib/rgame/engine/components/pool.rb
@@ -163,42 +282,78 @@ files:
163
282
  - lib/rgame/engine/components/timer.rb
164
283
  - lib/rgame/engine/components/velocity.rb
165
284
  - lib/rgame/engine/components/wander_controller.rb
285
+ - lib/rgame/engine/components/world.rb
286
+ - lib/rgame/engine/contact_set.rb
166
287
  - lib/rgame/engine/culling.rb
167
288
  - lib/rgame/engine/debug_overlay.rb
168
289
  - lib/rgame/engine/i18n.rb
290
+ - lib/rgame/engine/i18n/plural.rb
291
+ - lib/rgame/engine/i18n/plural_rules.rb
292
+ - lib/rgame/engine/i18n/template.rb
169
293
  - lib/rgame/engine/input/action_mapper.rb
170
294
  - lib/rgame/engine/input/actions.rb
171
295
  - lib/rgame/engine/input/input_map.rb
172
- - lib/rgame/engine/input/player_controller.rb
173
296
  - lib/rgame/engine/layout.rb
174
- - lib/rgame/engine/matrix.rb
297
+ - lib/rgame/engine/nav_grid.rb
175
298
  - lib/rgame/engine/node2d.rb
176
299
  - lib/rgame/engine/path.rb
177
300
  - lib/rgame/engine/player.rb
178
301
  - lib/rgame/engine/player_layer.rb
179
302
  - lib/rgame/engine/players.rb
180
303
  - lib/rgame/engine/pool.rb
181
- - lib/rgame/engine/resettable.rb
304
+ - lib/rgame/engine/presentation.rb
182
305
  - lib/rgame/engine/scene/scene_stack.rb
306
+ - lib/rgame/engine/sealed_privates.rb
183
307
  - lib/rgame/engine/signal.rb
184
308
  - lib/rgame/engine/spatial_hash.rb
185
- - lib/rgame/engine/tile_collision.rb
309
+ - lib/rgame/engine/text.rb
310
+ - lib/rgame/engine/tile_blockers.rb
186
311
  - lib/rgame/engine/tile_map.rb
187
312
  - lib/rgame/engine/tile_map_layer.rb
188
313
  - lib/rgame/engine/tileset.rb
189
314
  - lib/rgame/engine/timer.rb
315
+ - lib/rgame/engine/ui/button.rb
316
+ - lib/rgame/engine/ui/column.rb
317
+ - lib/rgame/engine/ui/icon_button.rb
190
318
  - lib/rgame/engine/ui/menu.rb
191
- - lib/rgame/engine/ui/menu_item.rb
319
+ - lib/rgame/engine/ui/navigation.rb
320
+ - lib/rgame/engine/ui/nine_slice_style.rb
321
+ - lib/rgame/engine/ui/option_button.rb
322
+ - lib/rgame/engine/ui/panel_button.rb
323
+ - lib/rgame/engine/ui/panel_menu.rb
324
+ - lib/rgame/engine/ui/pointing.rb
325
+ - lib/rgame/engine/ui/radial_menu.rb
326
+ - lib/rgame/engine/ui/ring.rb
327
+ - lib/rgame/engine/ui/row.rb
328
+ - lib/rgame/engine/ui/shape_style.rb
329
+ - lib/rgame/engine/ui/stack.rb
330
+ - lib/rgame/engine/ui/stepping.rb
331
+ - lib/rgame/engine/ui/text_button.rb
192
332
  - lib/rgame/engine/view.rb
193
333
  - lib/rgame/engine/viewports.rb
194
334
  - lib/rgame/engine/world_view.rb
195
335
  - lib/rgame/fonts/LiberationSans-Regular.ttf
196
336
  - lib/rgame/fonts/OFL.txt
197
337
  - lib/rgame/game.rb
338
+ - lib/rgame/rubocop.rb
339
+ - lib/rgame/rubocop/cop/game/draw_in_local_space.rb
340
+ - lib/rgame/rubocop/cop/game/hot_path.rb
341
+ - lib/rgame/rubocop/cop/game/layer_boundary.rb
342
+ - lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb
343
+ - lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb
344
+ - lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb
345
+ - lib/rgame/rubocop/cop/game/no_literal_text.rb
346
+ - lib/rgame/rubocop/cop/game/no_needless_allocation.rb
347
+ - lib/rgame/rubocop/default.yml
348
+ - lib/rgame/rubocop/plugin.rb
198
349
  - lib/rgame/util.rb
199
350
  - lib/rgame/util/color.rb
200
351
  - lib/rgame/util/controls.rb
352
+ - lib/rgame/util/route_search.rb
353
+ - lib/rgame/util/save_file.rb
354
+ - lib/rgame/util/solid_grid.rb
201
355
  - lib/rgame/util/tensor.rb
356
+ - lib/rgame/util/tile_sweep.rb
202
357
  - lib/rgame/util/z.rb
203
358
  - lib/rgame/version.rb
204
359
  homepage: https://github.com/psuessenb/rgame
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- # A character in the world. Composes the pieces it owns — a collision box, an
6
- # animator, a sprite id (the renderer maps it to a sheet), and a controller that
7
- # decides movement — rather than baking them in. Pure logic; no graphics.
8
- #
9
- # The controller responds to `intent(dt, input) -> [ix, iy]` with each axis in
10
- # [-1, 1]; PlayerController reads input, AI/scripted controllers ignore it.
11
- class Actor
12
- attr_accessor :x, :y
13
- attr_reader :sprite_id, :collision_box, :animator
14
-
15
- def initialize(x:, y:, sprite_id:, collision_box:, animator:, controller:, speed: 120.0)
16
- @x = x
17
- @y = y
18
- @sprite_id = sprite_id
19
- @collision_box = collision_box
20
- @animator = animator
21
- @controller = controller
22
- @speed = speed
23
- end
24
-
25
- def update(dt, collision_system, input)
26
- intent_x, intent_y = @controller.intent(dt, input)
27
-
28
- collision_system.move(self, intent_x * @speed * dt, intent_y * @speed * dt)
29
-
30
- @animator.play(walk_animation(intent_x, intent_y))
31
- @animator.update(dt)
32
- end
33
-
34
- # [row, col, flip_x] for the current frame.
35
- def frame
36
- @animator.frame
37
- end
38
-
39
- private
40
-
41
- # Facing follows movement intent (so we still "walk" while pushing a wall);
42
- # horizontal wins over vertical.
43
- def walk_animation(intent_x, intent_y)
44
- if intent_x.negative? then :walk_left
45
- elsif intent_x.positive? then :walk_right
46
- elsif intent_y.negative? then :walk_up
47
- elsif intent_y.positive? then :walk_down
48
- else :stand
49
- end
50
- end
51
- end
52
- end
53
- end