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
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- # Holds a display string and rebuilds it only when its source value changes, so a
6
- # per-frame draw can show the cached copy without interpolating (allocating) a String
7
- # every frame. The format block runs only on an actual change.
8
- #
9
- # @score_label = Engine::CachedLabel.new { |score| "Score: #{score}" } # build the block once
10
- # renderer.text(@score_label[@score], 12, 10) # in on_draw: cached, no alloc
11
- #
12
- # `@score_label[value]` returns the cached string when `value` is unchanged, and
13
- # otherwise rebuilds it through the block. Construct it (and its block) outside the
14
- # per-frame path — e.g. in `on_add` — so the interpolation isn't itself a per-frame cost.
15
- class CachedLabel
16
- def initialize(&format)
17
- raise ArgumentError, 'CachedLabel needs a format block' unless format
18
-
19
- @format = format
20
- @value = nil
21
- @text = nil
22
- end
23
-
24
- # The cached string for `value`, rebuilt only when `value` differs from last time.
25
- def [](value)
26
- return @text if !@text.nil? && value == @value
27
-
28
- @value = value
29
- @text = @format.call(value)
30
- end
31
- end
32
- end
33
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- # Drives an Actor from player input: turns the move_x/move_y actions into a
6
- # movement intent. NPC controllers implement the same `intent(dt, input)` seam
7
- # and ignore `input`.
8
- class PlayerController
9
- def intent(_dt, input)
10
- [input.axis(:move_x), input.axis(:move_y)]
11
- end
12
- end
13
- end
14
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- # A fixed-size 2-D grid backed by a single flat (row-major) array, addressed as
6
- # [x, y]. The flat backing keeps the whole grid in one contiguous allocation —
7
- # cheaper than an array-of-arrays and a straightforward shape to later move into a
8
- # C-level buffer. Pure; no bounds checking on the hot path (callers stay in range).
9
- #
10
- # This stays Ruby, unlike the engine's old `Tensor`, which was deleted in
11
- # favour of the C `RGame::Util::Tensor`. The reason is only that there is no
12
- # C `Util::Matrix` to swap to — `Util` holds `Tensor`, `Controls` and
13
- # `Color`. If one is ever written, this is the caller to point at it.
14
- class Matrix
15
- attr_reader :width, :height
16
-
17
- def initialize(width, height, initial: nil)
18
- @width = width
19
- @height = height
20
- @data = Array.new(width * height, initial)
21
- end
22
-
23
- def [](x, y)
24
- @data[(y * @width) + x]
25
- end
26
-
27
- def []=(x, y, value)
28
- @data[(y * @width) + x] = value
29
- end
30
- end
31
- end
32
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- # Builds value-object classes for pooling. Like `Data.define`, instances expose
6
- # read-only accessors and carry their fields as a unit — but where a `Data` value
7
- # is fully immutable (every change means a fresh allocation), these add exactly
8
- # one mutation: `reset`, which overwrites all fields at once and returns self.
9
- #
10
- # That is the only mutability a pool needs: acquire a recycled instance and
11
- # `reset` it, without exposing the per-field setters a `Struct` would. The methods
12
- # are generated fixed-arity with direct ivar assignment (as Struct/Data do), so
13
- # `reset` is allocation-free and recycling stays zero-allocation in steady state.
14
- #
15
- # Positional by default; pass `keyword_init: true` for keyword fields. Keywords
16
- # are *also* allocation-free here because they are generated as named parameters
17
- # (`reset(x:, y:)`), not a `**kwargs` splat — the splat is the one form that builds
18
- # a Hash per call.
19
- #
20
- # Point = Engine::Resettable.define(:x, :y)
21
- # p = Point.new(3, 4); p.x # => 3
22
- # p.reset(5, 6) # same object, new values; returns p
23
- #
24
- # Event = Engine::Resettable.define(:type, :payload, keyword_init: true)
25
- # e = Event.new(type: :hit, payload: 10)
26
- # e.reset(type: :lose, payload: nil) # same object; returns e
27
- # e.respond_to?(:type=) # => false (no setters)
28
- #
29
- # Field names are generated into real methods, so they must be plain identifiers.
30
- module Resettable
31
- IDENTIFIER = /\A[a-z_][a-zA-Z0-9_]*\z/
32
-
33
- def self.define(*fields, keyword_init: false)
34
- fields.each do |field|
35
- raise ArgumentError, "invalid field name: #{field.inspect}" unless field.to_s.match?(IDENTIFIER)
36
- end
37
-
38
- params = fields.map { |f| keyword_init ? "#{f}:" : f.to_s }.join(', ')
39
- assign = fields.map { |f| "@#{f} = #{f}" }.join("\n")
40
-
41
- Class.new do
42
- class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
43
- # For fields [:x, :y] this generates (keyword_init makes `x, y` -> `x:, y:`):
44
- # attr_reader :x, :y
45
- # def initialize(x, y)
46
- # @x = x; @y = y
47
- # end
48
- # def reset(x, y)
49
- # @x = x; @y = y
50
- # self
51
- # end
52
- attr_reader #{fields.map { |f| ":#{f}" }.join(', ')}
53
-
54
- def initialize(#{params})
55
- #{assign}
56
- end
57
-
58
- def reset(#{params})
59
- #{assign}
60
- self
61
- end
62
- RUBY
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- # Axis-separated AABB-vs-tile collision resolution (pure). `solid` is a callable
6
- # `solid.call(col, row) -> bool`. Resolve X then Y (with the X result) to get
7
- # wall-sliding. Assumes per-step movement smaller than a tile (no tunneling),
8
- # which holds for our speeds.
9
- class TileCollision
10
- EPS = 1e-9
11
-
12
- def initialize(tile_width:, tile_height:, solid:)
13
- @tile_width = tile_width
14
- @tile_height = tile_height
15
- @solid = solid
16
- end
17
-
18
- # Move an AABB (top-left x, y; size w, h) by dx, snapping flush against solids.
19
- def resolve_x(x, y, w, h, dx)
20
- nx = x + dx
21
- return nx if dx.zero?
22
-
23
- first_row = (y / @tile_height).floor
24
- last_row = ((y + h - EPS) / @tile_height).floor
25
-
26
- if dx.positive?
27
- col = ((nx + w - EPS) / @tile_width).floor
28
- return col * @tile_width - w if solid_in_rows?(col, first_row, last_row)
29
- else
30
- col = (nx / @tile_width).floor
31
- return (col + 1) * @tile_width if solid_in_rows?(col, first_row, last_row)
32
- end
33
- nx
34
- end
35
-
36
- def resolve_y(x, y, w, h, dy)
37
- ny = y + dy
38
- return ny if dy.zero?
39
-
40
- first_col = (x / @tile_width).floor
41
- last_col = ((x + w - EPS) / @tile_width).floor
42
-
43
- if dy.positive?
44
- row = ((ny + h - EPS) / @tile_height).floor
45
- return row * @tile_height - h if solid_in_cols?(row, first_col, last_col)
46
- else
47
- row = (ny / @tile_height).floor
48
- return (row + 1) * @tile_height if solid_in_cols?(row, first_col, last_col)
49
- end
50
- ny
51
- end
52
-
53
- private
54
-
55
- # Plain integer loops rather than (a..b).any? { ... }: the Range literal would
56
- # allocate on this per-frame collision path.
57
- def solid_in_rows?(col, first_row, last_row)
58
- row = first_row
59
- while row <= last_row
60
- return true if @solid.call(col, row)
61
-
62
- row += 1
63
- end
64
- false
65
- end
66
-
67
- def solid_in_cols?(row, first_col, last_col)
68
- col = first_col
69
- while col <= last_col
70
- return true if @solid.call(col, row)
71
-
72
- col += 1
73
- end
74
- false
75
- end
76
- end
77
- end
78
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- module UI
6
- # One entry in a Menu: a label on a nine-slice, and a signal for when it is
7
- # chosen.
8
- #
9
- # resume = menu.add_item('Resume')
10
- # resume.on_activated { cutscene.close }
11
- #
12
- # It draws itself from its **state** — focused, pressed, disabled or
13
- # idle — which is why the shipped atlas has an element for each. There is
14
- # no hover, because there is no pointer: what a mouse-driven control would
15
- # get from the cursor being over it, this gets from the Menu telling it it
16
- # is the focused one.
17
- #
18
- # Its position is its own, resolved through the tree like any node's, so a
19
- # Menu inside a PlayerLayer puts its items inside that player's region
20
- # without either of them arranging it.
21
- class MenuItem < Node2D
22
- signal :on_activated # emits no payload; the item is the handle
23
-
24
- # Atlas element per state. Replaceable per menu, so a game with its own
25
- # art is not obliged to name it the way the shipped atlas does.
26
- STYLE = {
27
- idle: :button_idle,
28
- focus: :button_focus,
29
- pressed: :button_pressed,
30
- disabled: :button_disabled
31
- }.freeze
32
-
33
- LABEL_COLOR = [46, 34, 24].freeze
34
- DISABLED_LABEL_COLOR = [120, 110, 100].freeze
35
-
36
- attr_accessor :label, :enabled
37
- attr_writer :focused, :pressed
38
-
39
- def initialize(label:, style: STYLE, enabled: true, **)
40
- super(**)
41
- @label = label
42
- @style = style
43
- @enabled = enabled
44
- @focused = false
45
- @pressed = false
46
- end
47
-
48
- def enabled? = @enabled
49
- def focused? = @focused
50
-
51
- # Fires the signal and returns the item, or nil if it is disabled — so a
52
- # caller never has to check first, and a disabled item cannot be
53
- # activated by any route.
54
- def activate
55
- return nil unless @enabled
56
-
57
- on_activated_signal.emit
58
- self
59
- end
60
-
61
- # The panel and its label share this node's slot, so the only ordering
62
- # question is which of the two goes on top — and `z: 1` says exactly
63
- # that, about this item and nothing else in the frame.
64
- def on_draw(renderer, _view)
65
- renderer.nine_slice(@style.fetch(state), abs_x, abs_y, width, height)
66
- renderer.text(@label, label_x(renderer), label_y(renderer),
67
- z: 1, color: @enabled ? LABEL_COLOR : DISABLED_LABEL_COLOR)
68
- end
69
-
70
- private
71
-
72
- def state
73
- return :disabled unless @enabled
74
- return :idle unless @focused
75
-
76
- @pressed ? :pressed : :focus
77
- end
78
-
79
- def label_x(renderer) = abs_x + ((width - renderer.text_width(@label)) / 2)
80
- def label_y(renderer) = abs_y + ((height - renderer.text_height) / 2)
81
- end
82
- end
83
- end
84
- end