rgame 0.1.0 → 0.2.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +94 -0
  3. data/README.md +130 -233
  4. data/docs/api/README.md +116 -69
  5. data/docs/api/assets.md +11 -12
  6. data/docs/api/components.md +58 -34
  7. data/docs/api/drawing.md +77 -9
  8. data/docs/api/game.md +34 -13
  9. data/docs/api/input.md +232 -51
  10. data/docs/api/scene_graph.md +242 -15
  11. data/docs/api/systems.md +20 -0
  12. data/docs/api/toolbox.md +19 -15
  13. data/docs/api/ui.md +98 -0
  14. data/docs/api/values.md +32 -0
  15. data/ext/README.md +6 -5
  16. data/ext/rgame_core/app/app.c +182 -8
  17. data/ext/rgame_core/audio/audio.c +74 -0
  18. data/ext/rgame_core/example.rb +17 -6
  19. data/ext/rgame_core/extconf.rb +52 -24
  20. data/ext/rgame_core/graphics/canvas.c +45 -4
  21. data/ext/rgame_core/graphics/canvas.h +65 -10
  22. data/ext/rgame_core/graphics/clip.c +22 -13
  23. data/ext/rgame_core/include/rgame/core.h +113 -3
  24. data/ext/rgame_core/input/gamepad.c +57 -3
  25. data/ext/rgame_core/ruby/core_ext.c +16 -0
  26. data/ext/rgame_core/ruby/renderer_ext.c +23 -0
  27. data/ext/rgame_util/color_ext.c +12 -3
  28. data/lib/rgame/core/app.rb +2 -0
  29. data/lib/rgame/core/input.rb +35 -41
  30. data/lib/rgame/core/recording.rb +3 -1
  31. data/lib/rgame/core/renderer.rb +76 -28
  32. data/lib/rgame/core/tile_map_renderer.rb +84 -55
  33. data/lib/rgame/engine/camera.rb +55 -10
  34. data/lib/rgame/engine/component.rb +11 -1
  35. data/lib/rgame/engine/components/animated_sprite.rb +9 -3
  36. data/lib/rgame/engine/components/camera_follow.rb +44 -0
  37. data/lib/rgame/engine/components/character_body.rb +25 -4
  38. data/lib/rgame/engine/components/sprite.rb +11 -1
  39. data/lib/rgame/engine/components/tile_world.rb +31 -18
  40. data/lib/rgame/engine/culling.rb +47 -0
  41. data/lib/rgame/engine/debug_overlay.rb +20 -9
  42. data/lib/rgame/engine/input/action_mapper.rb +101 -21
  43. data/lib/rgame/engine/input/actions.rb +69 -12
  44. data/lib/rgame/engine/input/input_map.rb +178 -0
  45. data/lib/rgame/engine/layout.rb +82 -0
  46. data/lib/rgame/engine/node2d.rb +205 -36
  47. data/lib/rgame/engine/player.rb +69 -0
  48. data/lib/rgame/engine/player_layer.rb +70 -0
  49. data/lib/rgame/engine/players.rb +212 -0
  50. data/lib/rgame/engine/scene/scene_stack.rb +25 -3
  51. data/lib/rgame/engine/spatial_hash.rb +17 -4
  52. data/lib/rgame/engine/tile_map_layer.rb +84 -0
  53. data/lib/rgame/engine/ui/menu.rb +115 -0
  54. data/lib/rgame/engine/ui/menu_item.rb +84 -0
  55. data/lib/rgame/engine/view.rb +76 -0
  56. data/lib/rgame/engine/viewports.rb +174 -0
  57. data/lib/rgame/engine/world_view.rb +70 -0
  58. data/lib/rgame/engine.rb +13 -1
  59. data/lib/rgame/game.rb +81 -11
  60. data/lib/rgame/util/controls.rb +117 -41
  61. data/lib/rgame/util/z.rb +133 -0
  62. data/lib/rgame/util.rb +1 -0
  63. data/lib/rgame/version.rb +1 -1
  64. metadata +26 -11
  65. data/lib/rgame/engine/camera_view.rb +0 -28
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGame
4
+ module Util
5
+ # The vocabulary of draw order: which band a thing is drawn in, and how the
6
+ # single number the renderer sorts on is built out of one.
7
+ #
8
+ # RGame::Util::Z::BANDS # => [:world, :hud, :overlay, :debug]
9
+ # RGame::Util::Z.base(:hud, 3) # the fourth slot handed out in the HUD band
10
+ #
11
+ # These are values — plain Symbols and Integers with no window, GPU or OS
12
+ # handle behind them — so they live here rather than in Core, and both sides
13
+ # of the line may name them. `RGame::Engine` decides which band a node is
14
+ # in; `RGame::Core::Renderer` turns that into a z. Neither could reach the
15
+ # other's spelling of it, and the vocabulary is exactly what they have to
16
+ # agree on. Same reasoning as RGame::Util::Controls.
17
+ #
18
+ # ## Draw order is tree order, not a number a caller picks
19
+ #
20
+ # A frame's order is decided in three steps, coarsest first:
21
+ #
22
+ # 1. **The band.** Structural and inherited: everything in `:world` is under
23
+ # everything in `:hud`, whatever either drew. Bands are 2**40 apart, so
24
+ # no arithmetic below can carry one into the next.
25
+ # 2. **The slot.** The scene graph is walked depth-first with siblings in
26
+ # `z` order, and each node takes the next slot in its band as it is
27
+ # reached. Because the walk descends fully before moving on, a node's
28
+ # subtree occupies one contiguous run of slots — which is what makes a
29
+ # subtree atomic, and what stops some of a node's children landing in
30
+ # front of a sibling the node itself is behind.
31
+ # 3. **The offset.** `Z_MIN..Z_MAX` inside the node's own slot, and the only
32
+ # part a `z:` argument on a drawing call touches. It orders a node's
33
+ # components against each other and its own draw calls against each
34
+ # other, and it cannot reach the next node, let alone the next band.
35
+ #
36
+ # So a node's `z` is **only ever compared to its siblings'**. Its magnitude
37
+ # means nothing: `z: 1` and `z: 1_000_000` behave identically if they are
38
+ # the only two children, and negatives are ordinary.
39
+ #
40
+ # ## The arithmetic is exact
41
+ #
42
+ # Every value here is an integer below 2**42, and the `double` the draw
43
+ # queue sorts on is exact below 2**53. Two different slots can therefore
44
+ # never compare equal by rounding — which would show up as two sprites
45
+ # swapping places between frames, and would be very hard to see as a
46
+ # precision problem.
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
+ SLOT = 1024
52
+ HALF = SLOT / 2
53
+
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
+ Z_MIN = -HALF
58
+ Z_MAX = HALF - 1
59
+
60
+ # The gap between bands: 2**30 slots each, which no frame will approach.
61
+ STRIDE = 1 << 40
62
+ SLOTS_PER_BAND = STRIDE / SLOT
63
+
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
+ BANDS = %i[world hud overlay debug].freeze
72
+ DEFAULT = :world
73
+
74
+ INDICES = BANDS.each_with_index.to_h.freeze
75
+
76
+ def self.band?(band) = INDICES.key?(band)
77
+
78
+ # Raises unless `band` names one. Called where a band is *set* rather than
79
+ # where it is used, so a typo surfaces at assignment with the list in the
80
+ # message instead of as a KeyError from inside a draw.
81
+ def self.band!(band)
82
+ return band if band?(band)
83
+
84
+ raise ArgumentError, "unknown z band #{band.inspect}; expected one of #{BANDS.inspect}"
85
+ end
86
+
87
+ # Which band this is, 0-based and back to front. This is what a renderer's
88
+ # slot counters are keyed on, so it is also where a bad band is caught.
89
+ def self.index(band)
90
+ INDICES[band] || band!(band)
91
+ end
92
+
93
+ # The z base of the `slot_index`-th slot in the band with that index.
94
+ # Offsets are measured from the middle of the slot, so Z_MIN..Z_MAX is
95
+ # symmetric — a node can draw behind its own sprite as easily as in front.
96
+ #
97
+ # Takes the band's *index* rather than its name because a renderer already
98
+ # has the index in hand (it counts slots per index), and looking the same
99
+ # Symbol up twice per node per frame is work with nothing to show for it.
100
+ # hot-path
101
+ def self.slot_base(band_index, slot_index)
102
+ if slot_index >= SLOTS_PER_BAND
103
+ raise RangeError,
104
+ "z band #{BANDS[band_index].inspect} is full at " \
105
+ "#{SLOTS_PER_BAND} slots per frame"
106
+ end
107
+
108
+ (band_index * STRIDE) + (slot_index * SLOT) + HALF
109
+ end
110
+
111
+ # The same thing by name, for a caller that has one rather than an index.
112
+ def self.base(band, slot_index) = slot_base(index(band), slot_index)
113
+
114
+ # Checks a `z:` argument and returns it. Drawing methods run this instead
115
+ # of trusting the caller, because the whole guarantee — that a node cannot
116
+ # draw outside its band — rests on the offset staying inside one slot.
117
+ # A stale global z (the bands used to be Integers a caller passed) lands
118
+ # here and says so rather than sorting somewhere surprising.
119
+ # hot-path
120
+ def self.offset(z)
121
+ raise TypeError, "no implicit conversion of #{z.class} into Float" unless z.is_a?(Numeric)
122
+
123
+ unless z.between?(Z_MIN, Z_MAX)
124
+ raise ArgumentError,
125
+ "z: #{z} is outside a node's slot (#{Z_MIN}..#{Z_MAX}); a `z:` orders one " \
126
+ 'node\'s own drawing, and the band comes from the tree'
127
+ end
128
+
129
+ z
130
+ end
131
+ end
132
+ end
133
+ end
data/lib/rgame/util.rb CHANGED
@@ -6,3 +6,4 @@
6
6
  require_relative 'util/color'
7
7
  require_relative 'util/controls'
8
8
  require_relative 'util/tensor'
9
+ 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.1.0'
11
+ VERSION = '0.2.0'
12
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rgame
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Süßenbach
@@ -10,16 +10,15 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: |
13
- rgame is a 2D game engine written in C on top of SDL2 and OpenGL and exposed
14
- to Ruby as C extensions. It opens a window, runs a fixed-timestep main loop,
15
- reads keyboard and controllers, loads PNGs onto the GPU and draws shapes and
16
- sprites through a z-sorted batching renderer.
13
+ RGame is a small 2D game engine for Ruby, written in Ruby and C. It's build
14
+ on top of SDL2, OpenGL and miniaudio. It's built with testability and
15
+ performance in mind, and aims to be an engine where you can write your whole
16
+ game code in Ruby, test it as usual with RSpec (or Minitest, or another test
17
+ framework) and still have acceptable performance.
17
18
 
18
- Everything Ruby-visible lives under the RGame module, split in two by what it
19
- depends on. RGame::Core owns the window, the GPU and the OS handles;
20
- RGame::Util holds the shareable value types and links no graphics libraries
21
- at all, so `require "rgame"` gives you the pure half with no SDL or OpenGL in
22
- the process and pure-logic code stays testable with no display.
19
+ While still a work in progress, RGame aims to be more than a SDL/OpenGL
20
+ binding - it ships with high level features like a scene graph, sprites,
21
+ collision systems, debugging tools and an UI toolkit.
23
22
  email:
24
23
  - 2452696+psuessenb@users.noreply.github.com
25
24
  executables: []
@@ -28,6 +27,7 @@ extensions:
28
27
  - ext/rgame_util/extconf.rb
29
28
  extra_rdoc_files: []
30
29
  files:
30
+ - CHANGELOG.md
31
31
  - LICENSE
32
32
  - README.md
33
33
  - docs/api/README.md
@@ -45,6 +45,7 @@ files:
45
45
  - docs/api/systems.md
46
46
  - docs/api/text.md
47
47
  - docs/api/toolbox.md
48
+ - docs/api/ui.md
48
49
  - docs/api/values.md
49
50
  - ext/README.md
50
51
  - ext/rgame_core/app/app.c
@@ -140,13 +141,13 @@ files:
140
141
  - lib/rgame/engine/body.rb
141
142
  - lib/rgame/engine/cached_label.rb
142
143
  - lib/rgame/engine/camera.rb
143
- - lib/rgame/engine/camera_view.rb
144
144
  - lib/rgame/engine/circle_collider.rb
145
145
  - lib/rgame/engine/collision_box.rb
146
146
  - lib/rgame/engine/collision_system.rb
147
147
  - lib/rgame/engine/component.rb
148
148
  - lib/rgame/engine/components/action_trigger.rb
149
149
  - lib/rgame/engine/components/animated_sprite.rb
150
+ - lib/rgame/engine/components/camera_follow.rb
150
151
  - lib/rgame/engine/components/character_body.rb
151
152
  - lib/rgame/engine/components/circle_collider.rb
152
153
  - lib/rgame/engine/components/collision_world.rb
@@ -162,14 +163,20 @@ files:
162
163
  - lib/rgame/engine/components/timer.rb
163
164
  - lib/rgame/engine/components/velocity.rb
164
165
  - lib/rgame/engine/components/wander_controller.rb
166
+ - lib/rgame/engine/culling.rb
165
167
  - lib/rgame/engine/debug_overlay.rb
166
168
  - lib/rgame/engine/i18n.rb
167
169
  - lib/rgame/engine/input/action_mapper.rb
168
170
  - lib/rgame/engine/input/actions.rb
171
+ - lib/rgame/engine/input/input_map.rb
169
172
  - lib/rgame/engine/input/player_controller.rb
173
+ - lib/rgame/engine/layout.rb
170
174
  - lib/rgame/engine/matrix.rb
171
175
  - lib/rgame/engine/node2d.rb
172
176
  - lib/rgame/engine/path.rb
177
+ - lib/rgame/engine/player.rb
178
+ - lib/rgame/engine/player_layer.rb
179
+ - lib/rgame/engine/players.rb
173
180
  - lib/rgame/engine/pool.rb
174
181
  - lib/rgame/engine/resettable.rb
175
182
  - lib/rgame/engine/scene/scene_stack.rb
@@ -177,8 +184,14 @@ files:
177
184
  - lib/rgame/engine/spatial_hash.rb
178
185
  - lib/rgame/engine/tile_collision.rb
179
186
  - lib/rgame/engine/tile_map.rb
187
+ - lib/rgame/engine/tile_map_layer.rb
180
188
  - lib/rgame/engine/tileset.rb
181
189
  - lib/rgame/engine/timer.rb
190
+ - lib/rgame/engine/ui/menu.rb
191
+ - lib/rgame/engine/ui/menu_item.rb
192
+ - lib/rgame/engine/view.rb
193
+ - lib/rgame/engine/viewports.rb
194
+ - lib/rgame/engine/world_view.rb
182
195
  - lib/rgame/fonts/LiberationSans-Regular.ttf
183
196
  - lib/rgame/fonts/OFL.txt
184
197
  - lib/rgame/game.rb
@@ -186,6 +199,7 @@ files:
186
199
  - lib/rgame/util/color.rb
187
200
  - lib/rgame/util/controls.rb
188
201
  - lib/rgame/util/tensor.rb
202
+ - lib/rgame/util/z.rb
189
203
  - lib/rgame/version.rb
190
204
  homepage: https://github.com/psuessenb/rgame
191
205
  licenses:
@@ -194,6 +208,7 @@ metadata:
194
208
  source_code_uri: https://github.com/psuessenb/rgame
195
209
  bug_tracker_uri: https://github.com/psuessenb/rgame/issues
196
210
  documentation_uri: https://github.com/psuessenb/rgame/blob/main/docs/api/README.md
211
+ changelog_uri: https://github.com/psuessenb/rgame/blob/main/CHANGELOG.md
197
212
  rubygems_mfa_required: 'true'
198
213
  rdoc_options: []
199
214
  require_paths:
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RGame
4
- module Engine
5
- # A node whose children live in *world* space and are drawn through a Camera: it
6
- # wraps their draw in renderer.translated(-camera.x, -camera.y), so the camera's
7
- # offset maps the world onto the screen. The children never know about the camera —
8
- # they draw at their own absolute (world) origin, and the view transform does the
9
- # rest. The owning scene drives the camera (e.g. centring it on the player); this
10
- # node only applies it.
11
- #
12
- # The offset is a draw-time transform, not a position baked into a node, so the same
13
- # world can later be drawn through several cameras (split-screen) by repeating the
14
- # pass under different offsets/clips.
15
- class CameraView < Node2D
16
- def initialize(camera:, **)
17
- super(**)
18
- @camera = camera
19
- end
20
-
21
- private
22
-
23
- def draw_children(renderer)
24
- renderer.translated(-@camera.x, -@camera.y) { super }
25
- end
26
- end
27
- end
28
- end