rgame 0.1.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 (161) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/README.md +406 -0
  4. data/docs/api/README.md +167 -0
  5. data/docs/api/app.md +192 -0
  6. data/docs/api/assets.md +426 -0
  7. data/docs/api/audio.md +208 -0
  8. data/docs/api/components.md +321 -0
  9. data/docs/api/drawing.md +330 -0
  10. data/docs/api/game.md +99 -0
  11. data/docs/api/images.md +118 -0
  12. data/docs/api/input.md +179 -0
  13. data/docs/api/internals.md +110 -0
  14. data/docs/api/scene_graph.md +159 -0
  15. data/docs/api/signals.md +142 -0
  16. data/docs/api/systems.md +98 -0
  17. data/docs/api/text.md +116 -0
  18. data/docs/api/toolbox.md +240 -0
  19. data/docs/api/values.md +101 -0
  20. data/ext/README.md +225 -0
  21. data/ext/rgame_core/app/app.c +721 -0
  22. data/ext/rgame_core/app/app_gl.h +64 -0
  23. data/ext/rgame_core/app/frame_loop.c +42 -0
  24. data/ext/rgame_core/app/frame_loop.h +54 -0
  25. data/ext/rgame_core/audio/audio.c +466 -0
  26. data/ext/rgame_core/audio/audio_internal.h +45 -0
  27. data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
  28. data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
  29. data/ext/rgame_core/example.rb +188 -0
  30. data/ext/rgame_core/extconf.rb +167 -0
  31. data/ext/rgame_core/graphics/backend.c +52 -0
  32. data/ext/rgame_core/graphics/backend.h +64 -0
  33. data/ext/rgame_core/graphics/canvas.c +247 -0
  34. data/ext/rgame_core/graphics/canvas.h +143 -0
  35. data/ext/rgame_core/graphics/clip.c +87 -0
  36. data/ext/rgame_core/graphics/clip.h +89 -0
  37. data/ext/rgame_core/graphics/draw_queue.c +216 -0
  38. data/ext/rgame_core/graphics/draw_queue.h +174 -0
  39. data/ext/rgame_core/graphics/gl_backend.c +122 -0
  40. data/ext/rgame_core/graphics/gl_backend.h +43 -0
  41. data/ext/rgame_core/graphics/image.c +304 -0
  42. data/ext/rgame_core/graphics/image_internal.h +30 -0
  43. data/ext/rgame_core/graphics/primitives.c +189 -0
  44. data/ext/rgame_core/graphics/primitives.h +111 -0
  45. data/ext/rgame_core/graphics/recording.c +119 -0
  46. data/ext/rgame_core/graphics/recording.h +88 -0
  47. data/ext/rgame_core/graphics/texture.c +181 -0
  48. data/ext/rgame_core/graphics/texture.h +165 -0
  49. data/ext/rgame_core/graphics/transform.c +128 -0
  50. data/ext/rgame_core/graphics/transform.h +106 -0
  51. data/ext/rgame_core/include/rgame/core.h +577 -0
  52. data/ext/rgame_core/input/device_slots.c +103 -0
  53. data/ext/rgame_core/input/device_slots.h +93 -0
  54. data/ext/rgame_core/input/gamepad.c +145 -0
  55. data/ext/rgame_core/input/gamepad.h +63 -0
  56. data/ext/rgame_core/input/input.c +109 -0
  57. data/ext/rgame_core/input/input.h +99 -0
  58. data/ext/rgame_core/ruby/audio_ext.c +321 -0
  59. data/ext/rgame_core/ruby/core_ext.c +513 -0
  60. data/ext/rgame_core/ruby/core_ext.h +51 -0
  61. data/ext/rgame_core/ruby/font_ext.c +168 -0
  62. data/ext/rgame_core/ruby/image_ext.c +230 -0
  63. data/ext/rgame_core/ruby/recording_ext.c +186 -0
  64. data/ext/rgame_core/ruby/renderer_ext.c +376 -0
  65. data/ext/rgame_core/text/atlas.c +59 -0
  66. data/ext/rgame_core/text/atlas.h +85 -0
  67. data/ext/rgame_core/text/font.c +281 -0
  68. data/ext/rgame_core/text/font.h +139 -0
  69. data/ext/rgame_core/text/font_atlas.c +385 -0
  70. data/ext/rgame_core/text/font_internal.h +47 -0
  71. data/ext/rgame_core/text/glyph_cache.c +142 -0
  72. data/ext/rgame_core/text/glyph_cache.h +89 -0
  73. data/ext/rgame_core/vendor/README.md +159 -0
  74. data/ext/rgame_core/vendor/miniaudio.h +95864 -0
  75. data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
  76. data/ext/rgame_core/vendor/stb_image.h +7988 -0
  77. data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
  78. data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
  79. data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
  80. data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
  81. data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
  82. data/ext/rgame_util/color.c +19 -0
  83. data/ext/rgame_util/color.h +60 -0
  84. data/ext/rgame_util/color_ext.c +156 -0
  85. data/ext/rgame_util/extconf.rb +27 -0
  86. data/ext/rgame_util/tensor.c +186 -0
  87. data/ext/rgame_util/util_ext.c +27 -0
  88. data/ext/rgame_util/util_ext.h +16 -0
  89. data/lib/rgame/boot.rb +13 -0
  90. data/lib/rgame/core/app.rb +82 -0
  91. data/lib/rgame/core/asset_manager.rb +224 -0
  92. data/lib/rgame/core/audio.rb +124 -0
  93. data/lib/rgame/core/font.rb +49 -0
  94. data/lib/rgame/core/gamepad.rb +55 -0
  95. data/lib/rgame/core/image.rb +55 -0
  96. data/lib/rgame/core/input.rb +77 -0
  97. data/lib/rgame/core/nine_slice.rb +163 -0
  98. data/lib/rgame/core/recording.rb +52 -0
  99. data/lib/rgame/core/renderer.rb +363 -0
  100. data/lib/rgame/core/sprite_sheet.rb +108 -0
  101. data/lib/rgame/core/tile_map_renderer.rb +160 -0
  102. data/lib/rgame/core/ui_atlas.rb +86 -0
  103. data/lib/rgame/core.rb +24 -0
  104. data/lib/rgame/engine/actor.rb +53 -0
  105. data/lib/rgame/engine/animation_set.rb +49 -0
  106. data/lib/rgame/engine/animator.rb +44 -0
  107. data/lib/rgame/engine/audio_bus.rb +24 -0
  108. data/lib/rgame/engine/audio_director.rb +29 -0
  109. data/lib/rgame/engine/body.rb +49 -0
  110. data/lib/rgame/engine/cached_label.rb +33 -0
  111. data/lib/rgame/engine/camera.rb +33 -0
  112. data/lib/rgame/engine/camera_view.rb +28 -0
  113. data/lib/rgame/engine/circle_collider.rb +32 -0
  114. data/lib/rgame/engine/collision_box.rb +34 -0
  115. data/lib/rgame/engine/collision_system.rb +44 -0
  116. data/lib/rgame/engine/component.rb +30 -0
  117. data/lib/rgame/engine/components/action_trigger.rb +41 -0
  118. data/lib/rgame/engine/components/animated_sprite.rb +63 -0
  119. data/lib/rgame/engine/components/character_body.rb +70 -0
  120. data/lib/rgame/engine/components/circle_collider.rb +44 -0
  121. data/lib/rgame/engine/components/collision_world.rb +103 -0
  122. data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
  123. data/lib/rgame/engine/components/path_follow.rb +84 -0
  124. data/lib/rgame/engine/components/player_controller.rb +24 -0
  125. data/lib/rgame/engine/components/pool.rb +53 -0
  126. data/lib/rgame/engine/components/screen_wrap.rb +27 -0
  127. data/lib/rgame/engine/components/sprite.rb +31 -0
  128. data/lib/rgame/engine/components/targeting.rb +54 -0
  129. data/lib/rgame/engine/components/thrust_controller.rb +65 -0
  130. data/lib/rgame/engine/components/tile_world.rb +68 -0
  131. data/lib/rgame/engine/components/timer.rb +75 -0
  132. data/lib/rgame/engine/components/velocity.rb +27 -0
  133. data/lib/rgame/engine/components/wander_controller.rb +60 -0
  134. data/lib/rgame/engine/debug_overlay.rb +106 -0
  135. data/lib/rgame/engine/i18n.rb +97 -0
  136. data/lib/rgame/engine/input/action_mapper.rb +46 -0
  137. data/lib/rgame/engine/input/actions.rb +41 -0
  138. data/lib/rgame/engine/input/player_controller.rb +14 -0
  139. data/lib/rgame/engine/matrix.rb +32 -0
  140. data/lib/rgame/engine/node2d.rb +271 -0
  141. data/lib/rgame/engine/path.rb +78 -0
  142. data/lib/rgame/engine/pool.rb +51 -0
  143. data/lib/rgame/engine/resettable.rb +67 -0
  144. data/lib/rgame/engine/scene/scene_stack.rb +65 -0
  145. data/lib/rgame/engine/signal.rb +75 -0
  146. data/lib/rgame/engine/spatial_hash.rb +71 -0
  147. data/lib/rgame/engine/tile_collision.rb +78 -0
  148. data/lib/rgame/engine/tile_map.rb +149 -0
  149. data/lib/rgame/engine/tileset.rb +101 -0
  150. data/lib/rgame/engine/timer.rb +51 -0
  151. data/lib/rgame/engine.rb +68 -0
  152. data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
  153. data/lib/rgame/fonts/OFL.txt +102 -0
  154. data/lib/rgame/game.rb +129 -0
  155. data/lib/rgame/util/color.rb +27 -0
  156. data/lib/rgame/util/controls.rb +107 -0
  157. data/lib/rgame/util/tensor.rb +12 -0
  158. data/lib/rgame/util.rb +8 -0
  159. data/lib/rgame/version.rb +12 -0
  160. data/lib/rgame.rb +20 -0
  161. metadata +215 -0
data/ext/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # ext/
2
+
3
+ The project's two Ruby C extensions. They are split by **dependency**, and that
4
+ split is the rule for deciding where new code goes:
5
+
6
+ | | `rgame_core` | `rgame_util` |
7
+ |---|---|---|
8
+ | Ruby namespace | `RGame::Core` | `RGame::Util` |
9
+ | Required as | `rgame/core_ext` | `rgame/util_ext` |
10
+ | Entry point | `Init_core_ext` | `Init_util_ext` |
11
+ | Links | SDL2 + OpenGL + libm + pthread + libdl | nothing but Ruby |
12
+ | Holds | `App` (window, GL context, main loop), `Image`, `Renderer`, `Recording`, `Font`, `Audio`, `Sample`, `Song` | `Tensor`, `Color` |
13
+
14
+ Anything that depends on SDL/OpenGL — or on something that does — belongs in
15
+ `rgame_core`. Everything else belongs in `rgame_util`. The point of the
16
+ split is that `RGame::Util` can be required, and its specs run, in a process
17
+ with no graphics libraries loaded and no display available:
18
+
19
+ ```
20
+ ext/rgame_core/
21
+ extconf.rb # mkmf script -> Makefile; pkg_config("sdl2"), -lGL
22
+ include/rgame/core.h # the public C API
23
+ example.rb # manual/visual smoke test (opens a real window)
24
+ app/
25
+ app.c # engine: SDL window/GL context + main loop
26
+ app_gl.h # private: the GL context behind the opaque app handle
27
+ frame_loop.c/.h # pure fixed-timestep + FPS logic (unit-tested)
28
+ graphics/
29
+ transform.c/.h # pure 2D affine transform stack (unit-tested)
30
+ clip.c/.h # pure rects + intersecting clip stack (unit-tested)
31
+ draw_queue.c/.h # pure z-sort + batching of draw commands (unit-tested)
32
+ canvas.c/.h # pure transform+clip+queue composition (unit-tested)
33
+ backend.h/.c # the GL seam: function-pointer table + submit loop
34
+ texture.c/.h # pure texture sheets, sub-rects and UVs (unit-tested)
35
+ primitives.c/.h # pure rects/lines/circles/sprites -> canvas (unit-tested)
36
+ recording.c/.h # pure baked draws, replayed cheaply (unit-tested)
37
+ gl_backend.c/.h # the real GL calls: the only gl* on the draw path
38
+ image.c # decode a PNG + upload it: the thin GL shim
39
+ image_internal.h # what the draw path needs from inside an image
40
+ text/
41
+ atlas.c/.h # pure glyph-atlas shelf packing (unit-tested)
42
+ glyph_cache.c/.h # pure codepoint -> glyph table (unit-tested)
43
+ font.c/.h # pure typeface: metrics, kerning, UTF-8 (unit-tested)
44
+ font_atlas.c # glyph atlas pages on the GPU: the impure quarter
45
+ font_internal.h # what the draw path needs from inside a font
46
+ input/
47
+ input.c/.h # pure button-id space + input snapshot (unit-tested)
48
+ device_slots.c/.h # pure controller-slot table, no SDL (unit-tested)
49
+ gamepad.c/.h # thin SDL_GameController shim (open/close/poll)
50
+ audio/
51
+ audio.c # sound device, samples and songs (unit-tested)
52
+ audio_internal.h # the live-sound counter, for tests
53
+ vorbis_decoder.c/.h # ogg for miniaudio, over stb_vorbis (unit-tested)
54
+ ruby/ # the only C here that includes ruby.h
55
+ core_ext.c # Ruby-facing glue: VALUE wrappers + trampolines
56
+ core_ext.h # one init function per Ruby-visible class here
57
+ image_ext.c # RGame::Core::Image — the Ruby binding
58
+ font_ext.c # RGame::Core::Font — the Ruby binding
59
+ audio_ext.c # RGame::Core::Audio, Sample and Song — the bindings
60
+ renderer_ext.c # RGame::Core::Renderer — the drawing primitives
61
+ recording_ext.c # RGame::Core::Recording — baked, replayable draws
62
+ vendor/ # third-party sources + licences (stb_image,
63
+ # stb_truetype, stb_vorbis, miniaudio)
64
+ *_impl.c # one per vendored library, built with warnings off
65
+
66
+ ext/rgame_util/
67
+ extconf.rb # mkmf script -> Makefile; no pkg_config, no -lGL
68
+ util_ext.c # entry point; calls each class's init
69
+ tensor.c # RGame::Util::Tensor — flat-array 3D grid
70
+ color.c/.h # pure RGBA packing, no ruby.h (unit-tested)
71
+ color_ext.c # RGame::Util::Color — the Ruby binding
72
+ ```
73
+
74
+ ## Why the engine lives here and not in `src/`
75
+
76
+ A gem's C extension is built by `gem install` running `extconf.rb` from
77
+ *inside its own directory* — it can't reach up to a sibling `src/`. Putting
78
+ the engine sources here means one copy serves both the gem and the standalone
79
+ binary the root `Makefile` builds. `src/` keeps only `main.c`, which stays out
80
+ of this directory precisely so mkmf doesn't compile its `main()` into the
81
+ extension.
82
+
83
+ ## How it's wired
84
+
85
+ `extconf.rb` runs `mkmf` to generate a Makefile. Every `.c` becomes one loadable
86
+ `.so`, linked against SDL2 + OpenGL the same way the root `Makefile` links the
87
+ standalone binary. No prebuilt `librgame_core.a` in the middle, so there's one
88
+ build step.
89
+
90
+ mkmf's default is to compile every `.c` in the extension's own directory and
91
+ nothing deeper, so `rgame_core`'s subsystem folders are named in its
92
+ `extconf.rb` — one `SOURCE_DIRS` list feeding `$srcs` (what to compile) and
93
+ `$VPATH` (where make looks for a source named by basename). Within a listed
94
+ folder nothing has to be remembered: dropping a `.c` into `graphics/` is all it
95
+ takes to get it compiled and linked. Adding a *folder* is one entry in that
96
+ list, and forgetting it fails loudly — the file is simply never compiled, and
97
+ the first call into it is an undefined symbol at `require` time.
98
+
99
+ Two consequences of how mkmf does this are worth knowing:
100
+
101
+ - **Objects land flat**, named after the source's basename, whatever folder it
102
+ came from. So basenames have to be unique across the tree — and that rule
103
+ enforces itself, because mkmf aborts with `source files duplication` rather
104
+ than silently overwriting one object with another.
105
+ - **Header dependencies need help.** mkmf emits `$(OBJS): $(HDRS)` but builds
106
+ `HDRS` by globbing the extension's own directory only, which is now empty of
107
+ headers. `extconf.rb` appends the real list, or editing a header would
108
+ rebuild nothing and link objects compiled against the old struct.
109
+
110
+ The vendored translation units are the exception, and are built with warnings
111
+ off. mkmf has no per-file flag setting, so `extconf.rb` appends an explicit rule
112
+ per entry in its `VENDORED` table to the Makefile it just generated — an
113
+ explicit rule beats mkmf's generic `.c.o` one, so it applies to those files and
114
+ nothing else. The `_impl.c` suffix is what marks a file as one of them, and it
115
+ is reserved for that: the project stays `-Wall -Wextra`-clean everywhere we
116
+ wrote the code. See `rgame_core/vendor/README.md`.
117
+
118
+ Not everything in a namespace comes from its extension: `RGame::Core::Input`,
119
+ `RGame::Core::Gamepad` and `RGame::Util::Controls` are pure Ruby in `lib/`,
120
+ layered on top, and `RGame::Core::Image` is C with a few sheet-slicing methods
121
+ added in `lib/rgame/core/image.rb`. The tables above list what each *extension*
122
+ provides.
123
+
124
+ Both extensions name themselves under `rgame/` in `create_makefile`, which
125
+ namespaces them on the load path and leaves the bare name `rgame` to
126
+ `lib/rgame.rb`, the pure-Ruby entry point. Each `Init_` function calls
127
+ `rb_define_module("RGame")` — idempotent, so it returns the same module
128
+ whichever extension loads first.
129
+
130
+ The glue only ever calls the public API — the `rgame_app` struct stays opaque
131
+ here exactly as it does for `src/main.c`. The one interesting part is the
132
+ callback bridge: `rgame_app_run` owns the loop and calls C function pointers,
133
+ so `core_ext.c` installs small **trampolines** that call back into Ruby
134
+ procs (stashed as instance variables, which also keeps them alive for the GC).
135
+ See the comments in `core_ext.c` for the details and the current
136
+ exception-safety caveat.
137
+
138
+ ## Build & run
139
+
140
+ From the project root:
141
+
142
+ ```
143
+ make ext # both extensions, each copied to lib/rgame/
144
+ make ext-core # just this one
145
+ make ext-util # just this one
146
+
147
+ ruby ext/rgame_core/example.rb # opens a window; Esc or close to quit
148
+ ```
149
+
150
+ `make ext-*` copies each built `.so` into `lib/rgame/`, which is where
151
+ `require "rgame/core_ext"` / `require "rgame/util_ext"` find it — mirroring
152
+ how rake-compiler installs a compiled extension into `lib/<gem>/`.
153
+
154
+ ## Ruby API
155
+
156
+ ```ruby
157
+ require "rgame" # RGame::Util only — no SDL/GL loaded
158
+ require "rgame/core" # adds RGame::Core, pulls in SDL2 + OpenGL
159
+
160
+ class MyGame < RGame::Core::App
161
+ def initialize = super(width: 800, height: 600, caption: "title")
162
+
163
+ def update(dt); end # one fixed simulation tick
164
+ def draw; end # render one frame
165
+ def needs_redraw?; end # false skips the draw
166
+ def button_down(id); end # discrete key press
167
+ end
168
+
169
+ app = MyGame.new
170
+ app.run # loops until #close or the window is closed
171
+ app.ticks_ms # => Integer, monotonic ms since startup
172
+ app.fps # => Float, most recent FPS reading
173
+
174
+ # Symbolic actions, resolved through Input's binding table. The id vocabulary
175
+ # lives in RGame::Util::Controls — ids are values, so a game (or the engine
176
+ # layer, which may not name Core) can name one:
177
+ controls = RGame::Util::Controls
178
+ input = RGame::Core::Input.new(app)
179
+ input.down?(:fire) # keyboard (the default)
180
+ input.down?(:fire, device: controls.gamepad(0))
181
+ input.axis(:move_x, device: controls.gamepad(0)) # => Float
182
+
183
+ # Rebinding is just a different table:
184
+ RGame::Core::Input.new(app, bindings: controls::DEFAULT_KEYBOARD.merge(fire: controls::KEY_RETURN))
185
+
186
+ # Which controllers are plugged in — a readout for menus, not the frame path:
187
+ pads = RGame::Core::Gamepad.new(app)
188
+ pads.count # => 1
189
+ pads.each_connected { |slot, name| ... } # "Player 2: <name>"
190
+
191
+ # Images: one decode and one upload, sliced into as many views as you like.
192
+ sheet = RGame::Core::Image.new(app, "tiles.png")
193
+ sheet.width; sheet.height
194
+ sheet.subimage(16, 0, 16, 16) # a view, not a copy
195
+ RGame::Core::Image.load_tiles(app, "tiles.png", 16, 16) # => [Image, ...]
196
+
197
+ # Drawing, from inside App#draw only. Nothing is immediate: the frame is
198
+ # z-sorted and batched once, after #draw returns.
199
+ renderer = RGame::Core::Renderer.new(app)
200
+ renderer.rect(10, 10, 100, 40, color: RGame::Util::Color::WHITE)
201
+ renderer.circle(200, 200, 30, color: [255, 0, 0])
202
+ renderer.line(0, 0, 100, 100, thickness: 4)
203
+ renderer.image(sheet, 400, 300, angle: 45, scale: 2) # centred, clockwise
204
+ renderer.rotated(30, 400, 300) { renderer.rect(380, 280, 40, 40) }
205
+ renderer.clipped(0, 0, 400, 600) { renderer.background(sheet) }
206
+
207
+ # Text. The renderer has a font already; Font.new(app, size) makes another.
208
+ renderer.text("Score: 1200", 10, 10)
209
+ renderer.text_width("Score: 1200") # => Float, and what #text actually draws
210
+
211
+ # Bake a block of drawing once, replay it for one call per texture.
212
+ ground = renderer.record { 100.times { |i| renderer.rect(i * 8, 0, 6, 6) } }
213
+ ground.draw(-camera_x, -camera_y)
214
+
215
+ # Sound. The device owns no window, so it takes no app.
216
+ audio = RGame::Core::Audio.new
217
+ audio.sample("hit.ogg").play # another voice each time
218
+ audio.song("theme.ogg").play(looping: true) # streamed, one voice, stoppable
219
+
220
+ grid = RGame::Util::Tensor.new(width, height, depth, initial: nil)
221
+ grid[x, y, z] = value
222
+
223
+ colour = RGame::Util::Color.new(255, 128, 0) # frozen, compares by value
224
+ RGame::Util::Color.coerce([255, 128, 0]) # nil / [r,g,b] / [r,g,b,a] / Color
225
+ ```