rgame 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -1
- data/README.md +34 -14
- data/docs/api/README.md +10 -4
- data/docs/api/input.md +38 -0
- data/ext/README.md +31 -0
- data/ext/rgame_core/app/app.c +11 -1
- data/ext/rgame_core/app/sdl_session.h +18 -0
- data/ext/rgame_core/extconf.rb +27 -1
- data/ext/rgame_core/include/rgame/core.h +62 -0
- data/ext/rgame_core/input/virtual_gamepad.c +151 -0
- data/ext/rgame_core/ruby/core_ext.c +1 -0
- data/ext/rgame_core/ruby/core_ext.h +3 -0
- data/ext/rgame_core/ruby/virtual_gamepad_ext.c +162 -0
- data/ext/rgame_util/extconf.rb +5 -0
- data/lib/rgame/cli/templates/Gemfile.tt +4 -4
- data/lib/rgame/core/virtual_gamepad.rb +26 -0
- data/lib/rgame/core.rb +1 -0
- data/lib/rgame/engine/tile_map.rb +1 -2
- data/lib/rgame/version.rb +1 -1
- metadata +20 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e39a6f27c3dea363873399361aa98c3b24c9de748f54ae635f93d9fef82dcfc
|
|
4
|
+
data.tar.gz: 89933482a0d7d99167ad7b725cf5eb0d8c503000bf4596893cf597e44e0ec6b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: edfe6650fc2f6e36988ed687ca7685e049b2e341f685d534e4c2a535adc4549441676ebf3126857c340bb846191623e3fd94edfa6d0d132639c3f0b349eff374
|
|
7
|
+
data.tar.gz: 64ba08460681639d8c53923874844527348b4a1490ff85085060051e3c2f66d9a33b959e846f29a1ca5386f542c55497982bdc84936fee91e200b51eaef9e71e
|
data/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,27 @@ index, not the argument.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.4.0] - 2026-09-16
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`gem install rgame` needs no compiler and no SDL2 on a common desktop.**
|
|
20
|
+
Apple Silicon Macs, x86-64 Linux and 64-bit Windows get a gem whose two
|
|
21
|
+
extensions are already built, with SDL2 linked into them. Every other machine,
|
|
22
|
+
and every Ruby but 4.0, installs the source gem and compiles it as before.
|
|
23
|
+
- **A virtual gamepad for tests.** `RGame::Core::VirtualGamepad.new` plugs a
|
|
24
|
+
synthetic controller into a running `App`, which seats it and reads it like a
|
|
25
|
+
real pad; `set_button`, `set_axis` and `detach` drive it. See
|
|
26
|
+
[docs/api/input.md](docs/api/input.md#rgamecorevirtualgamepad).
|
|
27
|
+
|
|
28
|
+
## [0.3.1] - 2026-09-15
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `require "rgame"` failed under Bundler with `cannot load such file --
|
|
33
|
+
rexml/document`. The gem now declares `rexml` as a dependency, which Ruby 4.0
|
|
34
|
+
no longer loads without one.
|
|
35
|
+
|
|
15
36
|
## [0.3.0] - 2026-09-15
|
|
16
37
|
|
|
17
38
|
### Added
|
|
@@ -222,7 +243,9 @@ First release, and the first version that runs a game end to end.
|
|
|
222
243
|
- **`RGame::Game`** — the entry point that wires the two halves together.
|
|
223
244
|
- Examples: `14_asteroids`, `15_tiled_world`, `16_hello_world`.
|
|
224
245
|
|
|
225
|
-
[Unreleased]: https://github.com/psuessenb/rgame/compare/v0.
|
|
246
|
+
[Unreleased]: https://github.com/psuessenb/rgame/compare/v0.4.0...HEAD
|
|
247
|
+
[0.4.0]: https://github.com/psuessenb/rgame/compare/v0.3.1...v0.4.0
|
|
248
|
+
[0.3.1]: https://github.com/psuessenb/rgame/compare/v0.3.0...v0.3.1
|
|
226
249
|
[0.3.0]: https://github.com/psuessenb/rgame/compare/v0.2.0...v0.3.0
|
|
227
250
|
[0.2.0]: https://github.com/psuessenb/rgame/compare/v0.1.0...v0.2.0
|
|
228
251
|
[0.1.0]: https://github.com/psuessenb/rgame/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -36,9 +36,15 @@ bundle exec rspec # the game logic, headless — no window needed
|
|
|
36
36
|
ruby main.rb # the game itself
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
It's just a small skeleton — a game class, a root node, a spec and the usual configuration — but it is laid out the way the engine wants to be used: one file loads SDL, everything else stays graphics-free and therefore testable with no display. [The `rgame` command](docs/api/cli.md) explains the layout and the reasoning. This is also sets you up with a Rubocop configuration already geared towards RGame and game development in general.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
**On a common desktop that first line needs nothing else** — no compiler, no SDL2, no header files. `gem install rgame` fetches a gem whose two C extensions are already built, with SDL2 linked into them:
|
|
42
|
+
|
|
43
|
+
- macOS 11 or later on Apple Silicon
|
|
44
|
+
- x86-64 Linux with glibc 2.29 or later
|
|
45
|
+
- 64-bit Windows, on a RubyInstaller Ruby
|
|
46
|
+
|
|
47
|
+
All three need Ruby 4.0. Anywhere else — an Intel Mac, a Raspberry Pi, Ruby 4.1 — `gem install` falls back to the gem that ships the C and compiles it on your machine. That one needs [a compiler and SDL2](#building-from-source).
|
|
42
48
|
|
|
43
49
|
## Hello world
|
|
44
50
|
|
|
@@ -94,9 +100,11 @@ You can learn more about how it works in the [documentation](docs/api/README.md)
|
|
|
94
100
|
| [localization](docs/api/examples.md#localization) | The same screen in two languages, switched and remembered |
|
|
95
101
|
| [pathfinding](docs/api/examples.md#pathfinding) | Setting a target for an actor and let if find its way there |
|
|
96
102
|
|
|
97
|
-
##
|
|
103
|
+
## Building from source
|
|
104
|
+
|
|
105
|
+
**This section is for two readers: anyone on a platform the binary gems miss, and anyone working on RGame itself.** Installing on one of the three platforms above needs none of it.
|
|
98
106
|
|
|
99
|
-
|
|
107
|
+
Compiling the C engine needs a compiler and the system libraries it links against. A source install of the gem needs the compiler, `pkg-config`, SDL2 and OpenGL from the list below. The rest is for running rgame's own suites.
|
|
100
108
|
|
|
101
109
|
### C engine
|
|
102
110
|
|
|
@@ -125,7 +133,7 @@ fixture and needs `libvorbisenc` to run.
|
|
|
125
133
|
(Debian/Ubuntu). These are what `extconf.rb` compiles against.
|
|
126
134
|
- **Bundler**, then `bundle install` for the dev/test gems (RSpec, RuboCop).
|
|
127
135
|
|
|
128
|
-
Nothing else
|
|
136
|
+
Nothing else. The engine's one runtime gem is `rexml`, which Bundler installs with it, and the `Gemfile` holds only development gems.
|
|
129
137
|
|
|
130
138
|
All three platforms below are built and tested on every push by
|
|
131
139
|
[CI](.github/workflows/ci.yml) - with that people have usually at home, so Apple Silicon and not Apple Intel, etc.
|
|
@@ -259,23 +267,35 @@ ruby ext/rgame_core/example.rb
|
|
|
259
267
|
Both extensions and the Ruby layer ship as one gem, built from `rgame.gemspec` and published at [rubygems.org/gems/rgame](https://rubygems.org/gems/rgame):
|
|
260
268
|
|
|
261
269
|
```
|
|
262
|
-
gem install rgame # from RubyGems; compiles
|
|
270
|
+
gem install rgame # from RubyGems; compiles nothing on a covered platform
|
|
263
271
|
```
|
|
264
272
|
|
|
265
|
-
Or from a checkout, which
|
|
273
|
+
Or from a checkout, which builds the source gem locally:
|
|
266
274
|
|
|
267
275
|
```
|
|
268
276
|
rake build # package into pkg/rgame-<version>.gem
|
|
269
277
|
gem install pkg/rgame-*.gem # compiles both extensions on this machine
|
|
270
278
|
```
|
|
271
279
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
**RubyGems holds two kinds of gem for each version.** The source gem carries the
|
|
281
|
+
C and compiles on install. Three platform gems carry `core_ext` and `util_ext`
|
|
282
|
+
already built, with SDL2 linked statically into `core_ext`; they ship no `.c`,
|
|
283
|
+
no `extconf.rb` and declare no extensions, so they compile nothing. `gem
|
|
284
|
+
install` picks by platform and Ruby version on its own.
|
|
285
|
+
|
|
286
|
+
CI builds and publishes all four. `tools/platform_gem.rake` builds a platform
|
|
287
|
+
gem, `tools/check_platform_gem.rb` checks it against nine rules before it may be
|
|
288
|
+
published, and the `smoke` job installs it on a runner with no SDL2 and plays
|
|
289
|
+
the examples out of it.
|
|
290
|
+
|
|
291
|
+
Installing the source gem runs each `extconf.rb` and installs the resulting
|
|
292
|
+
`.so` into the gem's own `lib/rgame/`, which is the same layout `make ext`
|
|
293
|
+
produces in a checkout — so `require "rgame"` and `require "rgame/core"` behave
|
|
294
|
+
identically whichever gem you got, including the guarantee that the first of
|
|
295
|
+
those loads no graphics libraries. Its system dependencies are the ones the C
|
|
296
|
+
engine needs (SDL2, OpenGL, pkg-config, a compiler); `extconf.rb` aborts with
|
|
297
|
+
the package to install if one is missing, rather than failing later at the link
|
|
298
|
+
step.
|
|
279
299
|
|
|
280
300
|
What ships is a glob over `lib/`, `ext/` and `docs/api/`, not a hand-written
|
|
281
301
|
list: a new C source or a runtime asset dropped into either tree is packaged
|
data/docs/api/README.md
CHANGED
|
@@ -11,7 +11,7 @@ or write C.
|
|
|
11
11
|
| [The `rgame` command](cli.md) | `rgame new NAME` — starting a project, and the layout it gives you |
|
|
12
12
|
| [App](app.md) | `RGame::Core::App` — the window and the frame loop |
|
|
13
13
|
| [Game](game.md) | `RGame::Game` — the entry point that wires both halves together |
|
|
14
|
-
| [Input](input.md) | `RGame::Core::Input`, `RGame::Util::Controls`, `RGame::Core::Gamepad` |
|
|
14
|
+
| [Input](input.md) | `RGame::Core::Input`, `RGame::Util::Controls`, `RGame::Core::Gamepad`, `RGame::Core::VirtualGamepad` |
|
|
15
15
|
| [Drawing](drawing.md) | `RGame::Core::Renderer` — shapes, images, transforms, clipping, recordings |
|
|
16
16
|
| [Images](images.md) | `RGame::Core::Image` — loading PNGs, subimages, tiles |
|
|
17
17
|
| [Text](text.md) | `RGame::Core::Font` and `Renderer#text` |
|
|
@@ -67,8 +67,14 @@ The Core spec suite does this to load exactly one layer.
|
|
|
67
67
|
`rgame/core` also loads `RGame::Util::Controls`, the input id vocabulary,
|
|
68
68
|
because the input classes need it.
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
**Your install either compiled both extensions or arrived with them built.** On
|
|
71
|
+
Apple Silicon macOS, x86-64 Linux and 64-bit Windows, `gem install rgame` fetches
|
|
72
|
+
a gem that already holds `core_ext` and `util_ext`, with SDL2 inside `core_ext`,
|
|
73
|
+
and compiles nothing. Every other machine gets the gem that ships the C and
|
|
74
|
+
builds both on install, against a system SDL2. Either way the two land in the
|
|
75
|
+
same place and the three requires above behave the same.
|
|
76
|
+
|
|
77
|
+
In a checkout of the repository, compile them before you require anything:
|
|
72
78
|
|
|
73
79
|
```
|
|
74
80
|
make ext # builds both, copies them into lib/rgame/
|
|
@@ -82,7 +88,7 @@ and `Core`. `Engine` holds what a game is written in.
|
|
|
82
88
|
| | `RGame::Util` | `RGame::Core` | `RGame::Engine` |
|
|
83
89
|
|---|---|---|---|
|
|
84
90
|
| Contains | shareable *values* — no window, no GPU, nothing to release | things owning a window, GPU or OS handle | game concepts: the scene graph a game is written in |
|
|
85
|
-
| Classes | `Color`, `Tensor`, `Controls`, `Z`, `SolidGrid`, `RouteSearch`, `TileSweep`, `SaveFile` | `App`, `Input`, `Gamepad`, `Image`, `Renderer`, `Recording`, `Font`, `Audio`, `SpriteSheet`, `NineSlice`, `UiAtlas`, `TileMapRenderer`, `AssetManager` | `Node2D`, components, systems, signals, `TileMap`, `Player`, `InputMap`, `UI::Menu` |
|
|
91
|
+
| Classes | `Color`, `Tensor`, `Controls`, `Z`, `SolidGrid`, `RouteSearch`, `TileSweep`, `SaveFile` | `App`, `Input`, `Gamepad`, `VirtualGamepad`, `Image`, `Renderer`, `Recording`, `Font`, `Audio`, `SpriteSheet`, `NineSlice`, `UiAtlas`, `TileMapRenderer`, `AssetManager` | `Node2D`, components, systems, signals, `TileMap`, `Player`, `InputMap`, `UI::Menu` |
|
|
86
92
|
| Loading it costs | nothing | SDL2 + OpenGL in your process | nothing |
|
|
87
93
|
|
|
88
94
|
**A value goes in `Util`; only a handle-owner goes in `Core`.** A colour is a
|
data/docs/api/input.md
CHANGED
|
@@ -436,3 +436,41 @@ end
|
|
|
436
436
|
|
|
437
437
|
When a controller is unplugged mid-press, the engine clears its buttons and axes.
|
|
438
438
|
A button held at that moment does not stay stuck down.
|
|
439
|
+
|
|
440
|
+
## `RGame::Core::VirtualGamepad`
|
|
441
|
+
|
|
442
|
+
**`VirtualGamepad` plugs a synthetic controller into a running `App`, for tests
|
|
443
|
+
that need the gamepad path with no hardware.** SDL fabricates the device inside
|
|
444
|
+
the process. The `App` seats it and calls `gamepad_connected` on its next frame,
|
|
445
|
+
as it would for a real pad, and `Input` reads its buttons and axes. It serves
|
|
446
|
+
tests, not gameplay; rgame's own Core suite drives its gamepad and hot-plug specs
|
|
447
|
+
through it.
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
pad = RGame::Core::VirtualGamepad.new # raises RuntimeError unless an App is open
|
|
451
|
+
|
|
452
|
+
pad.set_button(0, true) # button 0, which Input reads as Controls::PAD_A
|
|
453
|
+
pad.set_axis(0, -32_768) # axis 0 is Controls::AXIS_LEFT_X, fully left
|
|
454
|
+
pad.button_down?(0) # the raw button, before controller mapping
|
|
455
|
+
pad.game_controller? # does SDL have a mapping for it?
|
|
456
|
+
pad.attached? # is it still a live device?
|
|
457
|
+
pad.detach # the App calls gamepad_disconnected
|
|
458
|
+
|
|
459
|
+
RGame::Core::VirtualGamepad.pump # apply pad state SDL has not applied yet
|
|
460
|
+
RGame::Core::VirtualGamepad.sdl_error # what SDL last said
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Buttons and axes take SDL's own numbers.** A button number is a `Controls` pad
|
|
464
|
+
id minus `Controls::BUTTON_GAMEPAD_FIRST`. An axis number is the `Controls` axis
|
|
465
|
+
id itself. `set_axis` takes -32768 to 32767 and raises `RangeError` outside it.
|
|
466
|
+
|
|
467
|
+
`set_button` and `set_axis` return true when SDL accepts the change. SDL may
|
|
468
|
+
still apply a change later: outside an `App`'s frame loop a press reads back
|
|
469
|
+
only after one `VirtualGamepad.pump`. A test that must see a press land checks
|
|
470
|
+
`button_down?` and pumps until it does.
|
|
471
|
+
|
|
472
|
+
`detach` does nothing when called a second time. A pad that is never detached
|
|
473
|
+
stays plugged in until the last `App` is destroyed, because collecting the
|
|
474
|
+
object does not unplug it. SDL shuts down with the last `App` and takes every
|
|
475
|
+
virtual pad with it. After that every method except `detach` raises
|
|
476
|
+
`RuntimeError`.
|
data/ext/README.md
CHANGED
|
@@ -151,6 +151,37 @@ ruby ext/rgame_core/example.rb # opens a window; Esc or close to quit
|
|
|
151
151
|
`require "rgame/core_ext"` / `require "rgame/util_ext"` find it — mirroring
|
|
152
152
|
how rake-compiler installs a compiled extension into `lib/<gem>/`.
|
|
153
153
|
|
|
154
|
+
`rgame_core` links the system's SDL2 by default. To link SDL2 statically
|
|
155
|
+
instead, the way a platform gem does, build the pinned release first:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
rake sdl2 # SDL2 as a static library, in build/sdl2
|
|
159
|
+
make ext SDL2_STATIC=build/sdl2 # extconf.rb --with-sdl2-static=<prefix>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This needs CMake, and Ninja on Windows. Switching between the two rebuilds the
|
|
163
|
+
whole extension.
|
|
164
|
+
|
|
165
|
+
A platform gem ships both extensions precompiled, with that SDL2 linked in. It
|
|
166
|
+
builds from a checkout with no `make ext` objects in `ext/`:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
make ext-clean
|
|
170
|
+
rake -f tools/platform_gem.rake platform_gem # pkg/rgame-<version>-<platform>.gem
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
On Linux it runs inside the rake-compiler-dock image, as CI does, so that the
|
|
174
|
+
binaries load on older glibc versions.
|
|
175
|
+
|
|
176
|
+
What the gem does once installed is a different question, and the answer only
|
|
177
|
+
counts on a machine that did not build it — no SDL2, no compiler, no checkout on
|
|
178
|
+
the load path:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
gem install --local pkg/rgame-<version>-<platform>.gem
|
|
182
|
+
ruby tools/check_installed_gem.rb # nothing compiled, and require finds the gem
|
|
183
|
+
```
|
|
184
|
+
|
|
154
185
|
## Ruby API
|
|
155
186
|
|
|
156
187
|
```ruby
|
data/ext/rgame_core/app/app.c
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
#include "rgame/core.h"
|
|
23
23
|
#include "app/app_gl.h"
|
|
24
|
+
#include "app/sdl_session.h"
|
|
24
25
|
#include "graphics/canvas.h"
|
|
25
26
|
#include "text/font_internal.h"
|
|
26
27
|
#include "app/frame_loop.h"
|
|
@@ -52,6 +53,13 @@
|
|
|
52
53
|
*/
|
|
53
54
|
static int rgame_live_apps = 0;
|
|
54
55
|
|
|
56
|
+
/* Counts the times SDL has started from no live apps; see sdl_session.h. */
|
|
57
|
+
static unsigned rgame_sdl_sessions = 0;
|
|
58
|
+
|
|
59
|
+
unsigned rgame_sdl_session(void) {
|
|
60
|
+
return rgame_live_apps > 0 ? rgame_sdl_sessions : 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
struct rgame_app {
|
|
56
64
|
SDL_Window *window;
|
|
57
65
|
SDL_GLContext gl_context;
|
|
@@ -138,7 +146,9 @@ rgame_app *rgame_app_create(int width, int height, const char *title, int fullsc
|
|
|
138
146
|
|
|
139
147
|
SDL_GL_SetSwapInterval(1); /* vsync */
|
|
140
148
|
|
|
141
|
-
rgame_live_apps
|
|
149
|
+
if (rgame_live_apps++ == 0) {
|
|
150
|
+
rgame_sdl_sessions++;
|
|
151
|
+
}
|
|
142
152
|
|
|
143
153
|
app->running = 1;
|
|
144
154
|
app->refs = 1;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#ifndef RGAME_SDL_SESSION_H
|
|
2
|
+
#define RGAME_SDL_SESSION_H
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Which run of SDL is current. app.c starts SDL when the first app opens and
|
|
6
|
+
* shuts it down when the last one goes, and SDL_Quit frees every handle SDL
|
|
7
|
+
* gave out. A handle that can outlive every app — a virtual gamepad — records
|
|
8
|
+
* the session it was made in and compares, rather than handing SDL a pointer
|
|
9
|
+
* SDL has already freed.
|
|
10
|
+
*
|
|
11
|
+
* Private to the engine, like app_gl.h.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/* Non-zero while SDL is running, and different for every run; 0 when no app is
|
|
15
|
+
* alive. */
|
|
16
|
+
unsigned rgame_sdl_session(void);
|
|
17
|
+
|
|
18
|
+
#endif /* RGAME_SDL_SESSION_H */
|
data/ext/rgame_core/extconf.rb
CHANGED
|
@@ -15,7 +15,21 @@ $INCFLAGS << ' -I$(srcdir)/include'
|
|
|
15
15
|
|
|
16
16
|
$INCFLAGS << ' -I$(srcdir)/../rgame_util'
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
static_sdl2 = with_config('sdl2-static')
|
|
19
|
+
|
|
20
|
+
if static_sdl2
|
|
21
|
+
static_sdl2 = File.expand_path(static_sdl2)
|
|
22
|
+
pc_dir = File.join(static_sdl2, 'lib', 'pkgconfig')
|
|
23
|
+
abort "No SDL2 under #{static_sdl2}. Run: rake sdl2" unless File.exist?(File.join(pc_dir, 'sdl2.pc'))
|
|
24
|
+
|
|
25
|
+
ENV['PKG_CONFIG_LIBDIR'] = pc_dir
|
|
26
|
+
abort "pkg-config could not read #{pc_dir}/sdl2.pc" unless pkg_config('sdl2')
|
|
27
|
+
|
|
28
|
+
static_only = Shellwords.shellwords(pkg_config('sdl2', 'libs', 'static').to_s) - Shellwords.shellwords($libs)
|
|
29
|
+
$libs += " #{static_only.shelljoin}" unless static_only.empty?
|
|
30
|
+
else
|
|
31
|
+
abort 'SDL2 not found (pkg-config --exists sdl2 failed). Install libsdl2-dev.' unless pkg_config('sdl2')
|
|
32
|
+
end
|
|
19
33
|
|
|
20
34
|
abort 'SDL2 OpenGL header not found (SDL2/SDL_opengl.h). Install libsdl2-dev.' unless have_header('SDL2/SDL_opengl.h')
|
|
21
35
|
|
|
@@ -44,6 +58,18 @@ $libs = append_library($libs, 'dl') if have_library('dl')
|
|
|
44
58
|
|
|
45
59
|
$CFLAGS << ' -std=gnu17 -Wall -Wextra'
|
|
46
60
|
|
|
61
|
+
if static_sdl2
|
|
62
|
+
case RbConfig::CONFIG['host_os']
|
|
63
|
+
when /linux/ then $LDFLAGS << ' -Wl,--exclude-libs,ALL'
|
|
64
|
+
when /darwin/ then $LDFLAGS << ' -Wl,-exported_symbol,_Init_core_ext'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
unless enable_config('libruby-link', true) || RbConfig::CONFIG['host_os'].match?(/mingw|mswin|cygwin/)
|
|
69
|
+
$LIBRUBYARG = ''
|
|
70
|
+
$DEFLIBPATH.delete('$(libdir)')
|
|
71
|
+
end
|
|
72
|
+
|
|
47
73
|
create_makefile('rgame/core_ext')
|
|
48
74
|
|
|
49
75
|
VENDORED = {
|
|
@@ -335,6 +335,68 @@ const char *rgame_app_gamepad_name(const rgame_app *app, int slot);
|
|
|
335
335
|
/* How many controllers are currently connected. */
|
|
336
336
|
int rgame_app_gamepad_count(const rgame_app *app);
|
|
337
337
|
|
|
338
|
+
/*
|
|
339
|
+
* A synthetic game controller, for tests that need the real gamepad path with
|
|
340
|
+
* no hardware. SDL fabricates it in-process: it reports itself as a game
|
|
341
|
+
* controller and raises genuine device-added and device-removed events, so an
|
|
342
|
+
* app seats it and fires its hot-plug callbacks exactly as it would a real pad.
|
|
343
|
+
*
|
|
344
|
+
* It lives here rather than in a test helper because only engine code is
|
|
345
|
+
* guaranteed to call the SDL the engine runs on, whether SDL is linked
|
|
346
|
+
* statically or dynamically.
|
|
347
|
+
*
|
|
348
|
+
* SDL must be running, which means some app must be alive. When the last app is
|
|
349
|
+
* destroyed SDL shuts down and takes every virtual pad with it; a pad attached
|
|
350
|
+
* before that is then stale, and every call below except detach and free
|
|
351
|
+
* reports it rather than touching SDL.
|
|
352
|
+
*/
|
|
353
|
+
typedef struct rgame_virtual_gamepad rgame_virtual_gamepad;
|
|
354
|
+
|
|
355
|
+
/* Plugs in a new virtual pad. Returns NULL when no app is alive or SDL refuses;
|
|
356
|
+
* rgame_virtual_gamepad_error says why. */
|
|
357
|
+
rgame_virtual_gamepad *rgame_virtual_gamepad_attach(void);
|
|
358
|
+
|
|
359
|
+
/* Whether SDL has shut down since this pad was attached. */
|
|
360
|
+
int rgame_virtual_gamepad_stale(const rgame_virtual_gamepad *pad);
|
|
361
|
+
|
|
362
|
+
/*
|
|
363
|
+
* Sets an SDL controller button (SDL_CONTROLLER_BUTTON_*) up or down, or an
|
|
364
|
+
* axis (SDL_CONTROLLER_AXIS_*) to a value in -32768..32767. Returns 0 on
|
|
365
|
+
* success and -1 when SDL refuses. Each runs SDL's joystick update, but SDL
|
|
366
|
+
* may still defer the change: outside an app's frame loop a press read back
|
|
367
|
+
* only after one rgame_virtual_gamepad_pump.
|
|
368
|
+
*/
|
|
369
|
+
int rgame_virtual_gamepad_set_button(rgame_virtual_gamepad *pad, int button, int down);
|
|
370
|
+
int rgame_virtual_gamepad_set_axis(rgame_virtual_gamepad *pad, int axis, int value);
|
|
371
|
+
|
|
372
|
+
/*
|
|
373
|
+
* The raw joystick button as SDL last applied it — before any controller
|
|
374
|
+
* mapping. Differs from what an app reads only if the mapping is wrong.
|
|
375
|
+
*/
|
|
376
|
+
int rgame_virtual_gamepad_button_down(const rgame_virtual_gamepad *pad, int button);
|
|
377
|
+
|
|
378
|
+
/* Whether SDL treats the pad as a game controller, i.e. has a mapping for it.
|
|
379
|
+
* An app seats only pads for which this is 1. */
|
|
380
|
+
int rgame_virtual_gamepad_is_game_controller(const rgame_virtual_gamepad *pad);
|
|
381
|
+
|
|
382
|
+
/* Whether the pad is still a live, attached device. */
|
|
383
|
+
int rgame_virtual_gamepad_attached(const rgame_virtual_gamepad *pad);
|
|
384
|
+
|
|
385
|
+
/* Runs SDL's event pump and applies pending joystick state — for waiting on a
|
|
386
|
+
* change SDL has accepted and not yet applied. */
|
|
387
|
+
void rgame_virtual_gamepad_pump(void);
|
|
388
|
+
|
|
389
|
+
/* Unplugs the pad, as far as SDL and every app are concerned. Safe to call
|
|
390
|
+
* twice, and on a stale pad, where it does nothing. */
|
|
391
|
+
void rgame_virtual_gamepad_detach(rgame_virtual_gamepad *pad);
|
|
392
|
+
|
|
393
|
+
/* Frees the handle without touching SDL. A pad freed while still attached stays
|
|
394
|
+
* plugged in until SDL shuts down. NULL-safe. */
|
|
395
|
+
void rgame_virtual_gamepad_free(rgame_virtual_gamepad *pad);
|
|
396
|
+
|
|
397
|
+
/* Why the last attach failed, or SDL's own last error. Never NULL. */
|
|
398
|
+
const char *rgame_virtual_gamepad_error(void);
|
|
399
|
+
|
|
338
400
|
/* Monotonic milliseconds since startup. For time-based animation phase, etc. */
|
|
339
401
|
unsigned int rgame_app_ticks_ms(const rgame_app *app);
|
|
340
402
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* virtual_gamepad.c — a synthetic SDL game controller, for tests.
|
|
3
|
+
*
|
|
4
|
+
* Layer 3, and thin: every function is one or two SDL calls. What earns it a
|
|
5
|
+
* file in the engine rather than in a spec helper is *which* SDL it calls. A
|
|
6
|
+
* helper reaching SDL from outside the extension has to find it by name or by
|
|
7
|
+
* exported symbol, and neither works on every platform once SDL is linked into
|
|
8
|
+
* core_ext statically: on Windows the extension exports no SDL symbol, and a
|
|
9
|
+
* library name resolves to some other copy of SDL. Code compiled into the
|
|
10
|
+
* engine can only call the SDL the engine runs on.
|
|
11
|
+
*
|
|
12
|
+
* Covered end to end by the gamepad and input specs in spec_core/, which drive
|
|
13
|
+
* an app's real hot-plug and button path through it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include "rgame/core.h"
|
|
17
|
+
#include "app/sdl_session.h"
|
|
18
|
+
|
|
19
|
+
#include <SDL2/SDL.h>
|
|
20
|
+
#include <stdlib.h>
|
|
21
|
+
|
|
22
|
+
struct rgame_virtual_gamepad {
|
|
23
|
+
SDL_Joystick *joystick;
|
|
24
|
+
unsigned session;
|
|
25
|
+
int detached;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
static const char *attach_error = NULL;
|
|
29
|
+
|
|
30
|
+
static int usable(const rgame_virtual_gamepad *pad) {
|
|
31
|
+
return !pad->detached && pad->session == rgame_sdl_session();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* The pad's device index now. SDL renumbers device indices whenever any other
|
|
36
|
+
* device arrives or leaves, so the index attach returned is not kept: the
|
|
37
|
+
* instance id is the stable identity, and the index is looked up from it.
|
|
38
|
+
*/
|
|
39
|
+
static int device_index(const rgame_virtual_gamepad *pad) {
|
|
40
|
+
SDL_JoystickID id = SDL_JoystickInstanceID(pad->joystick);
|
|
41
|
+
int count = SDL_NumJoysticks();
|
|
42
|
+
for (int index = 0; index < count; index++) {
|
|
43
|
+
if (SDL_JoystickGetDeviceInstanceID(index) == id) {
|
|
44
|
+
return index;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return -1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
rgame_virtual_gamepad *rgame_virtual_gamepad_attach(void) {
|
|
51
|
+
attach_error = NULL;
|
|
52
|
+
|
|
53
|
+
unsigned session = rgame_sdl_session();
|
|
54
|
+
if (session == 0) {
|
|
55
|
+
attach_error = "no app is open, so SDL is not running";
|
|
56
|
+
return NULL;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
int index = SDL_JoystickAttachVirtual(SDL_JOYSTICK_TYPE_GAMECONTROLLER, SDL_CONTROLLER_AXIS_MAX,
|
|
60
|
+
SDL_CONTROLLER_BUTTON_MAX, 0);
|
|
61
|
+
if (index < 0) {
|
|
62
|
+
return NULL;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
SDL_Joystick *joystick = SDL_JoystickOpen(index);
|
|
66
|
+
if (!joystick) {
|
|
67
|
+
SDL_JoystickDetachVirtual(index);
|
|
68
|
+
return NULL;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
rgame_virtual_gamepad *pad = calloc(1, sizeof *pad);
|
|
72
|
+
if (!pad) {
|
|
73
|
+
SDL_JoystickClose(joystick);
|
|
74
|
+
SDL_JoystickDetachVirtual(index);
|
|
75
|
+
attach_error = "out of memory";
|
|
76
|
+
return NULL;
|
|
77
|
+
}
|
|
78
|
+
pad->joystick = joystick;
|
|
79
|
+
pad->session = session;
|
|
80
|
+
return pad;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
int rgame_virtual_gamepad_stale(const rgame_virtual_gamepad *pad) {
|
|
84
|
+
return pad->session != rgame_sdl_session();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
int rgame_virtual_gamepad_set_button(rgame_virtual_gamepad *pad, int button, int down) {
|
|
88
|
+
if (!usable(pad)) {
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
int result = SDL_JoystickSetVirtualButton(pad->joystick, button, down ? SDL_PRESSED : SDL_RELEASED);
|
|
92
|
+
/* Setting virtual state only queues it. An update applies it where SDL can;
|
|
93
|
+
* where it cannot yet, rgame_virtual_gamepad_pump is the retry. */
|
|
94
|
+
SDL_JoystickUpdate();
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
int rgame_virtual_gamepad_set_axis(rgame_virtual_gamepad *pad, int axis, int value) {
|
|
99
|
+
if (!usable(pad)) {
|
|
100
|
+
return -1;
|
|
101
|
+
}
|
|
102
|
+
int result = SDL_JoystickSetVirtualAxis(pad->joystick, axis, (Sint16)value);
|
|
103
|
+
SDL_JoystickUpdate();
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
int rgame_virtual_gamepad_button_down(const rgame_virtual_gamepad *pad, int button) {
|
|
108
|
+
return usable(pad) && SDL_JoystickGetButton(pad->joystick, button) == SDL_PRESSED;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
int rgame_virtual_gamepad_is_game_controller(const rgame_virtual_gamepad *pad) {
|
|
112
|
+
if (!usable(pad)) {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
int index = device_index(pad);
|
|
116
|
+
return index >= 0 && SDL_IsGameController(index) == SDL_TRUE;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
int rgame_virtual_gamepad_attached(const rgame_virtual_gamepad *pad) {
|
|
120
|
+
return usable(pad) && SDL_JoystickGetAttached(pad->joystick) == SDL_TRUE;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
void rgame_virtual_gamepad_pump(void) {
|
|
124
|
+
if (rgame_sdl_session() == 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
SDL_PumpEvents();
|
|
128
|
+
SDL_JoystickUpdate();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void rgame_virtual_gamepad_detach(rgame_virtual_gamepad *pad) {
|
|
132
|
+
if (!usable(pad)) {
|
|
133
|
+
pad->detached = 1;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
int index = device_index(pad);
|
|
137
|
+
SDL_JoystickClose(pad->joystick);
|
|
138
|
+
if (index >= 0) {
|
|
139
|
+
SDL_JoystickDetachVirtual(index);
|
|
140
|
+
}
|
|
141
|
+
pad->joystick = NULL;
|
|
142
|
+
pad->detached = 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
void rgame_virtual_gamepad_free(rgame_virtual_gamepad *pad) {
|
|
146
|
+
free(pad);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const char *rgame_virtual_gamepad_error(void) {
|
|
150
|
+
return attach_error ? attach_error : SDL_GetError();
|
|
151
|
+
}
|
|
@@ -28,6 +28,9 @@ void rgame_init_locale(VALUE mCore);
|
|
|
28
28
|
* share a file when everything else here does not. */
|
|
29
29
|
void rgame_init_audio(VALUE mCore);
|
|
30
30
|
|
|
31
|
+
/* A synthetic game controller for the Core specs; see virtual_gamepad_ext.c. */
|
|
32
|
+
void rgame_init_virtual_gamepad(VALUE mCore);
|
|
33
|
+
|
|
31
34
|
/*
|
|
32
35
|
* Wraps a baked recording in a Ruby object. Only Renderer#record calls this:
|
|
33
36
|
* `images` is the Array of Images drawn while recording, which the new object
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* virtual_gamepad_ext.c — the Ruby binding for RGame::Core::VirtualGamepad.
|
|
3
|
+
*
|
|
4
|
+
* pad = RGame::Core::VirtualGamepad.new # needs an open App
|
|
5
|
+
* pad.set_button(0, true) # SDL_CONTROLLER_BUTTON_A down
|
|
6
|
+
* pad.button_down?(0) # => true
|
|
7
|
+
* pad.detach
|
|
8
|
+
*
|
|
9
|
+
* Test-only, and named so, like Audio.debug_live_sounds. The engine side is
|
|
10
|
+
* input/virtual_gamepad.c, which says why this belongs in the extension. This
|
|
11
|
+
* file checks arguments and turns the engine's "stale" and "refused" answers
|
|
12
|
+
* into exceptions; how long to wait for a press to land, and whether presses
|
|
13
|
+
* land at all on a given machine, is the spec harness's business
|
|
14
|
+
* (spec_core/support/virtual_gamepad.rb).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#include "ruby/core_ext.h"
|
|
18
|
+
|
|
19
|
+
#include "rgame/core.h"
|
|
20
|
+
|
|
21
|
+
typedef struct {
|
|
22
|
+
rgame_virtual_gamepad *pad;
|
|
23
|
+
} rgame_virtual_gamepad_ref;
|
|
24
|
+
|
|
25
|
+
/* Frees the handle only. Detaching from inside the collector would raise a
|
|
26
|
+
* device-removed event at whatever moment GC happened to run, and after the
|
|
27
|
+
* last App has gone SDL would be touched after SDL_Quit. */
|
|
28
|
+
static void virtual_gamepad_ref_free(void *ptr) {
|
|
29
|
+
rgame_virtual_gamepad_ref *ref = ptr;
|
|
30
|
+
rgame_virtual_gamepad_free(ref->pad);
|
|
31
|
+
xfree(ref);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static size_t virtual_gamepad_ref_size(const void *ptr) {
|
|
35
|
+
(void)ptr;
|
|
36
|
+
return sizeof(rgame_virtual_gamepad_ref);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static const rb_data_type_t virtual_gamepad_data_type = {
|
|
40
|
+
.wrap_struct_name = "rgame_virtual_gamepad",
|
|
41
|
+
.function = {
|
|
42
|
+
.dfree = virtual_gamepad_ref_free,
|
|
43
|
+
.dsize = virtual_gamepad_ref_size,
|
|
44
|
+
},
|
|
45
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/* The pad behind `self`, raising when it can no longer be used: never
|
|
49
|
+
* initialized, or attached during a run of SDL that has since ended. */
|
|
50
|
+
static rgame_virtual_gamepad *virtual_gamepad_live(VALUE self) {
|
|
51
|
+
rgame_virtual_gamepad_ref *ref;
|
|
52
|
+
TypedData_Get_Struct(self, rgame_virtual_gamepad_ref, &virtual_gamepad_data_type, ref);
|
|
53
|
+
if (!ref->pad) {
|
|
54
|
+
rb_raise(rb_eRuntimeError, "virtual gamepad is not initialized");
|
|
55
|
+
}
|
|
56
|
+
if (rgame_virtual_gamepad_stale(ref->pad)) {
|
|
57
|
+
rb_raise(rb_eRuntimeError,
|
|
58
|
+
"virtual gamepad outlived SDL: every App was destroyed after it was attached");
|
|
59
|
+
}
|
|
60
|
+
return ref->pad;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static VALUE virtual_gamepad_alloc(VALUE klass) {
|
|
64
|
+
rgame_virtual_gamepad_ref *ref;
|
|
65
|
+
VALUE object = TypedData_Make_Struct(klass, rgame_virtual_gamepad_ref,
|
|
66
|
+
&virtual_gamepad_data_type, ref);
|
|
67
|
+
ref->pad = NULL;
|
|
68
|
+
return object;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* VirtualGamepad.new — plugs in a pad. Raises when no App is open. */
|
|
72
|
+
static VALUE virtual_gamepad_initialize(VALUE self) {
|
|
73
|
+
rgame_virtual_gamepad_ref *ref;
|
|
74
|
+
TypedData_Get_Struct(self, rgame_virtual_gamepad_ref, &virtual_gamepad_data_type, ref);
|
|
75
|
+
if (ref->pad) {
|
|
76
|
+
rb_raise(rb_eRuntimeError, "virtual gamepad is already attached");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
rgame_virtual_gamepad *pad = rgame_virtual_gamepad_attach();
|
|
80
|
+
if (!pad) {
|
|
81
|
+
rb_raise(rb_eRuntimeError, "cannot attach a virtual gamepad: %s",
|
|
82
|
+
rgame_virtual_gamepad_error());
|
|
83
|
+
}
|
|
84
|
+
ref->pad = pad;
|
|
85
|
+
return self;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* #set_button(button, down) — `button` is SDL's controller button number, the
|
|
90
|
+
* same number RGame::Util::Controls' PAD_ ids add to the gamepad range. True
|
|
91
|
+
* when SDL accepted the change, which SDL may apply only on a later pump.
|
|
92
|
+
*/
|
|
93
|
+
static VALUE virtual_gamepad_set_button(VALUE self, VALUE button, VALUE down) {
|
|
94
|
+
rgame_virtual_gamepad *pad = virtual_gamepad_live(self);
|
|
95
|
+
int result = rgame_virtual_gamepad_set_button(pad, NUM2INT(button), RTEST(down));
|
|
96
|
+
return result == 0 ? Qtrue : Qfalse;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* #set_axis(axis, value) — `value` in -32768..32767, SDL's own axis range. */
|
|
100
|
+
static VALUE virtual_gamepad_set_axis(VALUE self, VALUE axis, VALUE value) {
|
|
101
|
+
rgame_virtual_gamepad *pad = virtual_gamepad_live(self);
|
|
102
|
+
int raw = NUM2INT(value);
|
|
103
|
+
if (raw < -32768 || raw > 32767) {
|
|
104
|
+
rb_raise(rb_eRangeError, "axis value %d is outside -32768..32767", raw);
|
|
105
|
+
}
|
|
106
|
+
return rgame_virtual_gamepad_set_axis(pad, NUM2INT(axis), raw) == 0 ? Qtrue : Qfalse;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* #button_down?(button) — the raw joystick button, before controller mapping. */
|
|
110
|
+
static VALUE virtual_gamepad_button_down_p(VALUE self, VALUE button) {
|
|
111
|
+
rgame_virtual_gamepad *pad = virtual_gamepad_live(self);
|
|
112
|
+
return rgame_virtual_gamepad_button_down(pad, NUM2INT(button)) ? Qtrue : Qfalse;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* #game_controller? — whether SDL has a mapping for the pad, which an App needs
|
|
116
|
+
* before it seats one. */
|
|
117
|
+
static VALUE virtual_gamepad_game_controller_p(VALUE self) {
|
|
118
|
+
return rgame_virtual_gamepad_is_game_controller(virtual_gamepad_live(self)) ? Qtrue : Qfalse;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static VALUE virtual_gamepad_attached_p(VALUE self) {
|
|
122
|
+
return rgame_virtual_gamepad_attached(virtual_gamepad_live(self)) ? Qtrue : Qfalse;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* #detach — unplugs the pad. Does nothing the second time, or once SDL has shut
|
|
126
|
+
* down, since the pad went with it. */
|
|
127
|
+
static VALUE virtual_gamepad_detach(VALUE self) {
|
|
128
|
+
rgame_virtual_gamepad_ref *ref;
|
|
129
|
+
TypedData_Get_Struct(self, rgame_virtual_gamepad_ref, &virtual_gamepad_data_type, ref);
|
|
130
|
+
if (ref->pad) {
|
|
131
|
+
rgame_virtual_gamepad_detach(ref->pad);
|
|
132
|
+
}
|
|
133
|
+
return Qnil;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* VirtualGamepad.pump — runs SDL's event pump and applies pending pad state. */
|
|
137
|
+
static VALUE virtual_gamepad_s_pump(VALUE klass) {
|
|
138
|
+
(void)klass;
|
|
139
|
+
rgame_virtual_gamepad_pump();
|
|
140
|
+
return Qnil;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* VirtualGamepad.sdl_error — what SDL last said when it refused something. */
|
|
144
|
+
static VALUE virtual_gamepad_s_sdl_error(VALUE klass) {
|
|
145
|
+
(void)klass;
|
|
146
|
+
return rb_utf8_str_new_cstr(rgame_virtual_gamepad_error());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void rgame_init_virtual_gamepad(VALUE mCore) {
|
|
150
|
+
VALUE cVirtualGamepad = rb_define_class_under(mCore, "VirtualGamepad", rb_cObject);
|
|
151
|
+
|
|
152
|
+
rb_define_alloc_func(cVirtualGamepad, virtual_gamepad_alloc);
|
|
153
|
+
rb_define_method(cVirtualGamepad, "initialize", virtual_gamepad_initialize, 0);
|
|
154
|
+
rb_define_method(cVirtualGamepad, "set_button", virtual_gamepad_set_button, 2);
|
|
155
|
+
rb_define_method(cVirtualGamepad, "set_axis", virtual_gamepad_set_axis, 2);
|
|
156
|
+
rb_define_method(cVirtualGamepad, "button_down?", virtual_gamepad_button_down_p, 1);
|
|
157
|
+
rb_define_method(cVirtualGamepad, "game_controller?", virtual_gamepad_game_controller_p, 0);
|
|
158
|
+
rb_define_method(cVirtualGamepad, "attached?", virtual_gamepad_attached_p, 0);
|
|
159
|
+
rb_define_method(cVirtualGamepad, "detach", virtual_gamepad_detach, 0);
|
|
160
|
+
rb_define_singleton_method(cVirtualGamepad, "pump", virtual_gamepad_s_pump, 0);
|
|
161
|
+
rb_define_singleton_method(cVirtualGamepad, "sdl_error", virtual_gamepad_s_sdl_error, 0);
|
|
162
|
+
}
|
data/ext/rgame_util/extconf.rb
CHANGED
|
@@ -6,4 +6,9 @@ $CFLAGS << ' -std=gnu17 -Wall -Wextra'
|
|
|
6
6
|
|
|
7
7
|
$CFLAGS << ' -ffp-contract=off'
|
|
8
8
|
|
|
9
|
+
unless enable_config('libruby-link', true) || RbConfig::CONFIG['host_os'].match?(/mingw|mswin|cygwin/)
|
|
10
|
+
$LIBRUBYARG = ''
|
|
11
|
+
$DEFLIBPATH.delete('$(libdir)')
|
|
12
|
+
end
|
|
13
|
+
|
|
9
14
|
create_makefile('rgame/util_ext')
|
|
@@ -8,10 +8,10 @@ source 'https://rubygems.org'
|
|
|
8
8
|
# replace this line with something like `ruby '~> <%= ruby_version.split('.').first(2).join('.') %>'`.
|
|
9
9
|
ruby file: '.ruby-version'
|
|
10
10
|
|
|
11
|
-
# The engine.
|
|
12
|
-
# system libraries rather than gems
|
|
13
|
-
# extensions, so a C compiler, `make`, `pkg-config` and the
|
|
14
|
-
# development headers have to be present. See the rgame README.
|
|
11
|
+
# The engine. Its one gem dependency is rexml, which Bundler installs with it.
|
|
12
|
+
# SDL2 and OpenGL are system libraries rather than gems, and installing rgame
|
|
13
|
+
# compiles two C extensions, so a C compiler, `make`, `pkg-config` and the
|
|
14
|
+
# SDL2/OpenGL development headers have to be present. See the rgame README.
|
|
15
15
|
gem 'rgame', '<%= rgame_requirement %>'
|
|
16
16
|
|
|
17
17
|
group :development, :test do
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rgame/core_ext'
|
|
4
|
+
|
|
5
|
+
module RGame
|
|
6
|
+
module Core
|
|
7
|
+
# A synthetic game controller, for tests that need the real gamepad path
|
|
8
|
+
# with no hardware. It is C (`ext/rgame_core/input/virtual_gamepad.c`, bound
|
|
9
|
+
# in `ruby/virtual_gamepad_ext.c`), and it serves tests, not gameplay.
|
|
10
|
+
#
|
|
11
|
+
# pad = RGame::Core::VirtualGamepad.new # an App must be open
|
|
12
|
+
# pad.set_button(0, true) # SDL_CONTROLLER_BUTTON_A
|
|
13
|
+
# pad.set_axis(0, -32_768) # left stick fully left
|
|
14
|
+
# pad.detach # the App sees it unplugged
|
|
15
|
+
#
|
|
16
|
+
# SDL fabricates the device in-process, so the App seats it and raises its
|
|
17
|
+
# `gamepad_connected` and `gamepad_disconnected` callbacks as it would for a
|
|
18
|
+
# real pad. Buttons and axes take SDL's own numbers.
|
|
19
|
+
#
|
|
20
|
+
# It is part of the extension rather than a spec helper because only the
|
|
21
|
+
# extension is guaranteed to reach the SDL the App runs on, whether SDL is
|
|
22
|
+
# linked statically or dynamically.
|
|
23
|
+
class VirtualGamepad
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/rgame/core.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative 'core/app'
|
|
|
5
5
|
require_relative 'core/locale'
|
|
6
6
|
require_relative 'core/input'
|
|
7
7
|
require_relative 'core/gamepad'
|
|
8
|
+
require_relative 'core/virtual_gamepad'
|
|
8
9
|
require_relative 'core/image'
|
|
9
10
|
require_relative 'core/renderer'
|
|
10
11
|
require_relative 'core/recording'
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'rexml/document'
|
|
4
|
-
require 'base64'
|
|
5
4
|
require 'zlib'
|
|
6
5
|
|
|
7
6
|
require_relative '../util'
|
|
@@ -52,7 +51,7 @@ module RGame
|
|
|
52
51
|
layers = []
|
|
53
52
|
above = []
|
|
54
53
|
root.each_element('layer') do |layer_el|
|
|
55
|
-
raw =
|
|
54
|
+
raw = layer_el.elements['data'].text.strip.unpack1('m')
|
|
56
55
|
gids = Zlib::Inflate.inflate(raw).unpack('V*')
|
|
57
56
|
gids.map! { |g| g & FLIP_MASK }
|
|
58
57
|
layers << gids
|
data/lib/rgame/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paul Süßenbach
|
|
8
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
|
|
@@ -125,6 +139,7 @@ files:
|
|
|
125
139
|
- ext/rgame_core/app/frame_loop.h
|
|
126
140
|
- ext/rgame_core/app/locale.c
|
|
127
141
|
- ext/rgame_core/app/locale.h
|
|
142
|
+
- ext/rgame_core/app/sdl_session.h
|
|
128
143
|
- ext/rgame_core/audio/audio.c
|
|
129
144
|
- ext/rgame_core/audio/audio_internal.h
|
|
130
145
|
- ext/rgame_core/audio/vorbis_decoder.c
|
|
@@ -158,6 +173,7 @@ files:
|
|
|
158
173
|
- ext/rgame_core/input/gamepad.h
|
|
159
174
|
- ext/rgame_core/input/input.c
|
|
160
175
|
- ext/rgame_core/input/input.h
|
|
176
|
+
- ext/rgame_core/input/virtual_gamepad.c
|
|
161
177
|
- ext/rgame_core/ruby/audio_ext.c
|
|
162
178
|
- ext/rgame_core/ruby/core_ext.c
|
|
163
179
|
- ext/rgame_core/ruby/core_ext.h
|
|
@@ -166,6 +182,7 @@ files:
|
|
|
166
182
|
- ext/rgame_core/ruby/locale_ext.c
|
|
167
183
|
- ext/rgame_core/ruby/recording_ext.c
|
|
168
184
|
- ext/rgame_core/ruby/renderer_ext.c
|
|
185
|
+
- ext/rgame_core/ruby/virtual_gamepad_ext.c
|
|
169
186
|
- ext/rgame_core/text/atlas.c
|
|
170
187
|
- ext/rgame_core/text/atlas.h
|
|
171
188
|
- ext/rgame_core/text/font.c
|
|
@@ -232,6 +249,7 @@ files:
|
|
|
232
249
|
- lib/rgame/core/sprite_sheet.rb
|
|
233
250
|
- lib/rgame/core/tile_map_renderer.rb
|
|
234
251
|
- lib/rgame/core/ui_atlas.rb
|
|
252
|
+
- lib/rgame/core/virtual_gamepad.rb
|
|
235
253
|
- lib/rgame/engine.rb
|
|
236
254
|
- lib/rgame/engine/actor_blockers.rb
|
|
237
255
|
- lib/rgame/engine/animation_set.rb
|