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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +94 -0
- data/README.md +130 -233
- data/docs/api/README.md +116 -69
- data/docs/api/assets.md +11 -12
- data/docs/api/components.md +58 -34
- data/docs/api/drawing.md +77 -9
- data/docs/api/game.md +34 -13
- data/docs/api/input.md +232 -51
- data/docs/api/scene_graph.md +242 -15
- data/docs/api/systems.md +20 -0
- data/docs/api/toolbox.md +19 -15
- data/docs/api/ui.md +98 -0
- data/docs/api/values.md +32 -0
- data/ext/README.md +6 -5
- data/ext/rgame_core/app/app.c +182 -8
- data/ext/rgame_core/audio/audio.c +74 -0
- data/ext/rgame_core/example.rb +17 -6
- data/ext/rgame_core/extconf.rb +52 -24
- data/ext/rgame_core/graphics/canvas.c +45 -4
- data/ext/rgame_core/graphics/canvas.h +65 -10
- data/ext/rgame_core/graphics/clip.c +22 -13
- data/ext/rgame_core/include/rgame/core.h +113 -3
- data/ext/rgame_core/input/gamepad.c +57 -3
- data/ext/rgame_core/ruby/core_ext.c +16 -0
- data/ext/rgame_core/ruby/renderer_ext.c +23 -0
- data/ext/rgame_util/color_ext.c +12 -3
- data/lib/rgame/core/app.rb +2 -0
- data/lib/rgame/core/input.rb +35 -41
- data/lib/rgame/core/recording.rb +3 -1
- data/lib/rgame/core/renderer.rb +76 -28
- data/lib/rgame/core/tile_map_renderer.rb +84 -55
- data/lib/rgame/engine/camera.rb +55 -10
- data/lib/rgame/engine/component.rb +11 -1
- data/lib/rgame/engine/components/animated_sprite.rb +9 -3
- data/lib/rgame/engine/components/camera_follow.rb +44 -0
- data/lib/rgame/engine/components/character_body.rb +25 -4
- data/lib/rgame/engine/components/sprite.rb +11 -1
- data/lib/rgame/engine/components/tile_world.rb +31 -18
- data/lib/rgame/engine/culling.rb +47 -0
- data/lib/rgame/engine/debug_overlay.rb +20 -9
- data/lib/rgame/engine/input/action_mapper.rb +101 -21
- data/lib/rgame/engine/input/actions.rb +69 -12
- data/lib/rgame/engine/input/input_map.rb +178 -0
- data/lib/rgame/engine/layout.rb +82 -0
- data/lib/rgame/engine/node2d.rb +205 -36
- data/lib/rgame/engine/player.rb +69 -0
- data/lib/rgame/engine/player_layer.rb +70 -0
- data/lib/rgame/engine/players.rb +212 -0
- data/lib/rgame/engine/scene/scene_stack.rb +25 -3
- data/lib/rgame/engine/spatial_hash.rb +17 -4
- data/lib/rgame/engine/tile_map_layer.rb +84 -0
- data/lib/rgame/engine/ui/menu.rb +115 -0
- data/lib/rgame/engine/ui/menu_item.rb +84 -0
- data/lib/rgame/engine/view.rb +76 -0
- data/lib/rgame/engine/viewports.rb +174 -0
- data/lib/rgame/engine/world_view.rb +70 -0
- data/lib/rgame/engine.rb +13 -1
- data/lib/rgame/game.rb +81 -11
- data/lib/rgame/util/controls.rb +117 -41
- data/lib/rgame/util/z.rb +133 -0
- data/lib/rgame/util.rb +1 -0
- data/lib/rgame/version.rb +1 -1
- metadata +26 -11
- data/lib/rgame/engine/camera_view.rb +0 -28
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include "graphics/clip.h"
|
|
2
2
|
|
|
3
|
+
#include <stdint.h>
|
|
4
|
+
|
|
3
5
|
/* The canonical empty rect. Every path that produces "nothing" produces this
|
|
4
6
|
* exact value, so two empties compare equal and cannot split a batch. */
|
|
5
7
|
static const rgame_rect RGAME_EMPTY_RECT = { 0, 0, 0, 0 };
|
|
@@ -30,20 +32,27 @@ rgame_rect rgame_rect_intersect(rgame_rect a, rgame_rect b) {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/*
|
|
33
|
-
* Edges are computed in `
|
|
34
|
-
* pass a very large width to mean "as wide as possible", and
|
|
35
|
-
* then overflow — which is undefined behaviour, not merely a
|
|
36
|
-
* The result always fits back in an int because it is
|
|
37
|
-
* smaller of the two inputs.
|
|
35
|
+
* Edges are computed in `int64_t` rather than `int`. A caller can
|
|
36
|
+
* legitimately pass a very large width to mean "as wide as possible", and
|
|
37
|
+
* x + w would then overflow — which is undefined behaviour, not merely a
|
|
38
|
+
* wrong answer. The result always fits back in an int because it is
|
|
39
|
+
* bounded by the smaller of the two inputs.
|
|
40
|
+
*
|
|
41
|
+
* `int64_t` rather than `long`: `long` is only 32 bits on Windows (LLP64),
|
|
42
|
+
* the same width as `int` there, so it does not actually buy the extra
|
|
43
|
+
* headroom this needs — found by UBSan under clang64, which is the only
|
|
44
|
+
* configuration that caught it (the intermediate overflow this guards
|
|
45
|
+
* against never occurs in Check's own test values on a 64-bit `long`
|
|
46
|
+
* platform, so plain Linux/macOS runs never exercise the bug either).
|
|
38
47
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
int64_t left = a.x > b.x ? a.x : b.x;
|
|
49
|
+
int64_t top = a.y > b.y ? a.y : b.y;
|
|
50
|
+
int64_t a_right = (int64_t)a.x + a.w;
|
|
51
|
+
int64_t b_right = (int64_t)b.x + b.w;
|
|
52
|
+
int64_t a_bottom = (int64_t)a.y + a.h;
|
|
53
|
+
int64_t b_bottom = (int64_t)b.y + b.h;
|
|
54
|
+
int64_t right = a_right < b_right ? a_right : b_right;
|
|
55
|
+
int64_t bottom = a_bottom < b_bottom ? a_bottom : b_bottom;
|
|
47
56
|
|
|
48
57
|
if (right <= left || bottom <= top) {
|
|
49
58
|
return RGAME_EMPTY_RECT;
|
|
@@ -52,16 +52,93 @@ void rgame_app_destroy(rgame_app *app);
|
|
|
52
52
|
#define RGAME_BUTTON_GAMEPAD_FIRST 0x1000
|
|
53
53
|
#define RGAME_BUTTON_GAMEPAD_LAST 0x10FF
|
|
54
54
|
|
|
55
|
-
/* Keyboard ids == SDL scancodes
|
|
56
|
-
*
|
|
55
|
+
/* Keyboard ids == SDL scancodes, which are physical *positions* rather than
|
|
56
|
+
* letters: RGAME_KEY_A is the key marked A on a QWERTY board and Q on AZERTY.
|
|
57
|
+
* The set is what a Western keyboard can be relied on to have. No numpad (most
|
|
58
|
+
* laptops have none), no GUI/Windows/Command key, no print-screen cluster, and
|
|
59
|
+
* nothing whose position depends on the layout. Those can be added when
|
|
60
|
+
* something needs them; each is a #define plus a _Static_assert in app.c. */
|
|
61
|
+
#define RGAME_KEY_A 4
|
|
62
|
+
#define RGAME_KEY_B 5
|
|
63
|
+
#define RGAME_KEY_C 6
|
|
64
|
+
#define RGAME_KEY_D 7
|
|
65
|
+
#define RGAME_KEY_E 8
|
|
66
|
+
#define RGAME_KEY_F 9
|
|
67
|
+
#define RGAME_KEY_G 10
|
|
68
|
+
#define RGAME_KEY_H 11
|
|
69
|
+
#define RGAME_KEY_I 12
|
|
70
|
+
#define RGAME_KEY_J 13
|
|
71
|
+
#define RGAME_KEY_K 14
|
|
72
|
+
#define RGAME_KEY_L 15
|
|
73
|
+
#define RGAME_KEY_M 16
|
|
74
|
+
#define RGAME_KEY_N 17
|
|
75
|
+
#define RGAME_KEY_O 18
|
|
76
|
+
#define RGAME_KEY_P 19
|
|
77
|
+
#define RGAME_KEY_Q 20
|
|
78
|
+
#define RGAME_KEY_R 21
|
|
79
|
+
#define RGAME_KEY_S 22
|
|
80
|
+
#define RGAME_KEY_T 23
|
|
81
|
+
#define RGAME_KEY_U 24
|
|
82
|
+
#define RGAME_KEY_V 25
|
|
83
|
+
#define RGAME_KEY_W 26
|
|
84
|
+
#define RGAME_KEY_X 27
|
|
85
|
+
#define RGAME_KEY_Y 28
|
|
86
|
+
#define RGAME_KEY_Z 29
|
|
87
|
+
#define RGAME_KEY_1 30
|
|
88
|
+
#define RGAME_KEY_2 31
|
|
89
|
+
#define RGAME_KEY_3 32
|
|
90
|
+
#define RGAME_KEY_4 33
|
|
91
|
+
#define RGAME_KEY_5 34
|
|
92
|
+
#define RGAME_KEY_6 35
|
|
93
|
+
#define RGAME_KEY_7 36
|
|
94
|
+
#define RGAME_KEY_8 37
|
|
95
|
+
#define RGAME_KEY_9 38
|
|
96
|
+
#define RGAME_KEY_0 39
|
|
57
97
|
#define RGAME_KEY_RETURN 40
|
|
58
98
|
#define RGAME_KEY_ESCAPE 41
|
|
99
|
+
#define RGAME_KEY_BACKSPACE 42
|
|
100
|
+
#define RGAME_KEY_TAB 43
|
|
59
101
|
#define RGAME_KEY_SPACE 44
|
|
102
|
+
#define RGAME_KEY_MINUS 45
|
|
103
|
+
#define RGAME_KEY_EQUALS 46
|
|
104
|
+
#define RGAME_KEY_LEFTBRACKET 47
|
|
105
|
+
#define RGAME_KEY_RIGHTBRACKET 48
|
|
106
|
+
#define RGAME_KEY_BACKSLASH 49
|
|
107
|
+
#define RGAME_KEY_SEMICOLON 51
|
|
108
|
+
#define RGAME_KEY_APOSTROPHE 52
|
|
109
|
+
#define RGAME_KEY_GRAVE 53
|
|
110
|
+
#define RGAME_KEY_COMMA 54
|
|
111
|
+
#define RGAME_KEY_PERIOD 55
|
|
112
|
+
#define RGAME_KEY_SLASH 56
|
|
113
|
+
#define RGAME_KEY_CAPSLOCK 57
|
|
60
114
|
#define RGAME_KEY_F1 58
|
|
115
|
+
#define RGAME_KEY_F2 59
|
|
116
|
+
#define RGAME_KEY_F3 60
|
|
117
|
+
#define RGAME_KEY_F4 61
|
|
118
|
+
#define RGAME_KEY_F5 62
|
|
119
|
+
#define RGAME_KEY_F6 63
|
|
120
|
+
#define RGAME_KEY_F7 64
|
|
121
|
+
#define RGAME_KEY_F8 65
|
|
122
|
+
#define RGAME_KEY_F9 66
|
|
123
|
+
#define RGAME_KEY_F10 67
|
|
124
|
+
#define RGAME_KEY_F11 68
|
|
125
|
+
#define RGAME_KEY_F12 69
|
|
126
|
+
#define RGAME_KEY_INSERT 73
|
|
127
|
+
#define RGAME_KEY_HOME 74
|
|
128
|
+
#define RGAME_KEY_PAGEUP 75
|
|
129
|
+
#define RGAME_KEY_DELETE 76
|
|
130
|
+
#define RGAME_KEY_END 77
|
|
131
|
+
#define RGAME_KEY_PAGEDOWN 78
|
|
61
132
|
#define RGAME_KEY_RIGHT 79
|
|
62
133
|
#define RGAME_KEY_LEFT 80
|
|
63
134
|
#define RGAME_KEY_DOWN 81
|
|
64
135
|
#define RGAME_KEY_UP 82
|
|
136
|
+
#define RGAME_KEY_LCTRL 224
|
|
137
|
+
#define RGAME_KEY_LSHIFT 225
|
|
138
|
+
#define RGAME_KEY_LALT 226
|
|
139
|
+
#define RGAME_KEY_RCTRL 228
|
|
140
|
+
#define RGAME_KEY_RSHIFT 229
|
|
141
|
+
#define RGAME_KEY_RALT 230
|
|
65
142
|
|
|
66
143
|
/* Gamepad button ids: the gamepad range plus SDL's controller button number.
|
|
67
144
|
* app.c asserts each against the SDL constant it mirrors, as with the keys. */
|
|
@@ -80,6 +157,12 @@ void rgame_app_destroy(rgame_app *app);
|
|
|
80
157
|
#define RGAME_PAD_DPAD_DOWN (RGAME_BUTTON_GAMEPAD_FIRST + 12)
|
|
81
158
|
#define RGAME_PAD_DPAD_LEFT (RGAME_BUTTON_GAMEPAD_FIRST + 13)
|
|
82
159
|
#define RGAME_PAD_DPAD_RIGHT (RGAME_BUTTON_GAMEPAD_FIRST + 14)
|
|
160
|
+
#define RGAME_PAD_MISC1 (RGAME_BUTTON_GAMEPAD_FIRST + 15)
|
|
161
|
+
#define RGAME_PAD_PADDLE1 (RGAME_BUTTON_GAMEPAD_FIRST + 16)
|
|
162
|
+
#define RGAME_PAD_PADDLE2 (RGAME_BUTTON_GAMEPAD_FIRST + 17)
|
|
163
|
+
#define RGAME_PAD_PADDLE3 (RGAME_BUTTON_GAMEPAD_FIRST + 18)
|
|
164
|
+
#define RGAME_PAD_PADDLE4 (RGAME_BUTTON_GAMEPAD_FIRST + 19)
|
|
165
|
+
#define RGAME_PAD_TOUCHPAD (RGAME_BUTTON_GAMEPAD_FIRST + 20)
|
|
83
166
|
|
|
84
167
|
/*
|
|
85
168
|
* Analog axes are their own small id space rather than part of the button
|
|
@@ -121,6 +204,7 @@ void rgame_app_destroy(rgame_app *app);
|
|
|
121
204
|
typedef void (*rgame_frame_begin_fn)(void *userdata);
|
|
122
205
|
typedef void (*rgame_update_fn)(void *userdata, double dt_seconds);
|
|
123
206
|
typedef void (*rgame_draw_fn)(void *userdata);
|
|
207
|
+
typedef void (*rgame_frame_end_fn)(void *userdata);
|
|
124
208
|
typedef int (*rgame_needs_redraw_fn)(void *userdata);
|
|
125
209
|
typedef void (*rgame_button_fn)(void *userdata, int button_id);
|
|
126
210
|
typedef void (*rgame_resize_fn)(void *userdata, int width, int height);
|
|
@@ -141,6 +225,12 @@ typedef void (*rgame_gamepad_fn)(void *userdata, int slot);
|
|
|
141
225
|
* - `needs_redraw` is polled before drawing; returning 0 skips the draw for
|
|
142
226
|
* that frame (simulation still advances). NULL means always redraw.
|
|
143
227
|
* - `draw` renders one frame.
|
|
228
|
+
* - `frame_end` runs once per rendered frame, after that frame's drawing has
|
|
229
|
+
* been submitted to the GPU but *before* the buffer swap — the only point
|
|
230
|
+
* at which the frame just drawn can be read back deterministically. Never
|
|
231
|
+
* called when `needs_redraw` skipped the draw. Rarely needed: it exists for
|
|
232
|
+
* reading pixels back for a test, not for game code, since a real driver is
|
|
233
|
+
* free to swap however it likes once `draw` returns.
|
|
144
234
|
* - `button_down`/`button_up` report discrete key presses and releases. Key
|
|
145
235
|
* repeats are filtered out, so holding a key reports exactly one press.
|
|
146
236
|
* - `resize` reports a new window size; the GL viewport is already updated.
|
|
@@ -154,6 +244,7 @@ typedef struct {
|
|
|
154
244
|
rgame_update_fn update;
|
|
155
245
|
rgame_needs_redraw_fn needs_redraw;
|
|
156
246
|
rgame_draw_fn draw;
|
|
247
|
+
rgame_frame_end_fn frame_end;
|
|
157
248
|
rgame_button_fn button_down;
|
|
158
249
|
rgame_button_fn button_up;
|
|
159
250
|
rgame_resize_fn resize;
|
|
@@ -360,7 +451,7 @@ int rgame_app_draw_image_rot(rgame_app *app, const rgame_image *image, float cx,
|
|
|
360
451
|
float angle_degrees, float scale, unsigned int color, double z);
|
|
361
452
|
|
|
362
453
|
/*
|
|
363
|
-
* The transform and
|
|
454
|
+
* The transform, clip and layer stacks. Every push is undone by the same
|
|
364
455
|
* `rgame_app_pop`, so a caller can never pop the wrong one; a push that cannot
|
|
365
456
|
* be honoured (a full stack) is still counted, so pops stay balanced and the
|
|
366
457
|
* drawing comes out untransformed rather than desynchronised.
|
|
@@ -378,6 +469,25 @@ void rgame_app_push_scale(rgame_app *app, float sx, float sy);
|
|
|
378
469
|
* always succeed as far as the caller is concerned.
|
|
379
470
|
*/
|
|
380
471
|
int rgame_app_push_clip(rgame_app *app, int x, int y, int width, int height);
|
|
472
|
+
|
|
473
|
+
/*
|
|
474
|
+
* The layer stack: what every subsequent `z` is measured from, until the
|
|
475
|
+
* matching pop. It *replaces* rather than accumulates, and starts at 0, so a
|
|
476
|
+
* caller that never pushes one gets exactly the z it passes.
|
|
477
|
+
*
|
|
478
|
+
* `rgame_app_next_layer_slot` is the other half: one counter per band, reset at
|
|
479
|
+
* the start of each frame, handing out increasing indices. It counts and
|
|
480
|
+
* nothing more — what a band means, and how a slot index becomes a base, is
|
|
481
|
+
* decided a layer up (RGame::Util::Z). Bands outside 0..7 answer 0.
|
|
482
|
+
*
|
|
483
|
+
* Together they are how a scene graph gives each node its own narrow window of
|
|
484
|
+
* z values: the traversal takes the next slot as it reaches a node, so draw
|
|
485
|
+
* order is tree order, and a node's `z:` can only reorder its own drawing.
|
|
486
|
+
*/
|
|
487
|
+
void rgame_app_push_layer(rgame_app *app, double base);
|
|
488
|
+
double rgame_app_layer(rgame_app *app);
|
|
489
|
+
unsigned int rgame_app_next_layer_slot(rgame_app *app, int band);
|
|
490
|
+
|
|
381
491
|
void rgame_app_pop(rgame_app *app);
|
|
382
492
|
|
|
383
493
|
/*
|
|
@@ -11,15 +11,69 @@ _Static_assert(RGAME_GAMEPAD_AXIS_COUNT == SDL_CONTROLLER_AXIS_MAX,
|
|
|
11
11
|
|
|
12
12
|
/* Gamepad button ids are the gamepad range plus SDL's own button number, so
|
|
13
13
|
* each named id must line up with the SDL constant it mirrors. */
|
|
14
|
-
_Static_assert(RGAME_PAD_A ==
|
|
14
|
+
_Static_assert(RGAME_PAD_A ==
|
|
15
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_A,
|
|
15
16
|
"pad id must match SDL controller button");
|
|
16
|
-
_Static_assert(
|
|
17
|
+
_Static_assert(RGAME_PAD_B ==
|
|
18
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_B,
|
|
17
19
|
"pad id must match SDL controller button");
|
|
18
|
-
_Static_assert(
|
|
20
|
+
_Static_assert(RGAME_PAD_X ==
|
|
21
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_X,
|
|
22
|
+
"pad id must match SDL controller button");
|
|
23
|
+
_Static_assert(RGAME_PAD_Y ==
|
|
24
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_Y,
|
|
25
|
+
"pad id must match SDL controller button");
|
|
26
|
+
_Static_assert(RGAME_PAD_BACK ==
|
|
27
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_BACK,
|
|
28
|
+
"pad id must match SDL controller button");
|
|
29
|
+
_Static_assert(RGAME_PAD_GUIDE ==
|
|
30
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_GUIDE,
|
|
31
|
+
"pad id must match SDL controller button");
|
|
32
|
+
_Static_assert(RGAME_PAD_START ==
|
|
33
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_START,
|
|
34
|
+
"pad id must match SDL controller button");
|
|
35
|
+
_Static_assert(RGAME_PAD_LEFT_STICK ==
|
|
36
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
|
37
|
+
"pad id must match SDL controller button");
|
|
38
|
+
_Static_assert(RGAME_PAD_RIGHT_STICK ==
|
|
39
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
|
40
|
+
"pad id must match SDL controller button");
|
|
41
|
+
_Static_assert(RGAME_PAD_LEFT_SHOULDER ==
|
|
42
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
|
43
|
+
"pad id must match SDL controller button");
|
|
44
|
+
_Static_assert(RGAME_PAD_RIGHT_SHOULDER ==
|
|
45
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
|
46
|
+
"pad id must match SDL controller button");
|
|
47
|
+
_Static_assert(RGAME_PAD_DPAD_UP ==
|
|
48
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_UP,
|
|
49
|
+
"pad id must match SDL controller button");
|
|
50
|
+
_Static_assert(RGAME_PAD_DPAD_DOWN ==
|
|
51
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
|
52
|
+
"pad id must match SDL controller button");
|
|
53
|
+
_Static_assert(RGAME_PAD_DPAD_LEFT ==
|
|
54
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
|
19
55
|
"pad id must match SDL controller button");
|
|
20
56
|
_Static_assert(RGAME_PAD_DPAD_RIGHT ==
|
|
21
57
|
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
|
22
58
|
"pad id must match SDL controller button");
|
|
59
|
+
_Static_assert(RGAME_PAD_MISC1 ==
|
|
60
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_MISC1,
|
|
61
|
+
"pad id must match SDL controller button");
|
|
62
|
+
_Static_assert(RGAME_PAD_PADDLE1 ==
|
|
63
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE1,
|
|
64
|
+
"pad id must match SDL controller button");
|
|
65
|
+
_Static_assert(RGAME_PAD_PADDLE2 ==
|
|
66
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE2,
|
|
67
|
+
"pad id must match SDL controller button");
|
|
68
|
+
_Static_assert(RGAME_PAD_PADDLE3 ==
|
|
69
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE3,
|
|
70
|
+
"pad id must match SDL controller button");
|
|
71
|
+
_Static_assert(RGAME_PAD_PADDLE4 ==
|
|
72
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_PADDLE4,
|
|
73
|
+
"pad id must match SDL controller button");
|
|
74
|
+
_Static_assert(RGAME_PAD_TOUCHPAD ==
|
|
75
|
+
RGAME_BUTTON_GAMEPAD_FIRST + SDL_CONTROLLER_BUTTON_TOUCHPAD,
|
|
76
|
+
"pad id must match SDL controller button");
|
|
23
77
|
_Static_assert(RGAME_AXIS_LEFT_X == SDL_CONTROLLER_AXIS_LEFTX, "axis id must match SDL axis");
|
|
24
78
|
_Static_assert(RGAME_AXIS_TRIGGER_RIGHT == SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
|
25
79
|
"axis id must match SDL axis");
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
* def button_down(id); end # discrete key press
|
|
24
24
|
* def button_up(id); end
|
|
25
25
|
* def frame_begin; end # once per frame, before the tick batch
|
|
26
|
+
* def frame_end; end # after draw is submitted, before the swap —
|
|
27
|
+
* # rarely needed; it exists for reading pixels
|
|
28
|
+
* # back in a test, not for game code
|
|
26
29
|
* def resize(w, h); end
|
|
27
30
|
* def gamepad_connected(slot); end
|
|
28
31
|
* def gamepad_disconnected(slot); end
|
|
@@ -47,6 +50,7 @@
|
|
|
47
50
|
static ID id_frame_begin;
|
|
48
51
|
static ID id_update;
|
|
49
52
|
static ID id_draw;
|
|
53
|
+
static ID id_frame_end;
|
|
50
54
|
static ID id_needs_redraw;
|
|
51
55
|
static ID id_button_down;
|
|
52
56
|
static ID id_button_up;
|
|
@@ -265,6 +269,10 @@ static void tramp_draw(void *userdata) {
|
|
|
265
269
|
protected_call((run_state *)userdata, id_draw, 0, NULL);
|
|
266
270
|
}
|
|
267
271
|
|
|
272
|
+
static void tramp_frame_end(void *userdata) {
|
|
273
|
+
protected_call((run_state *)userdata, id_frame_end, 0, NULL);
|
|
274
|
+
}
|
|
275
|
+
|
|
268
276
|
static int tramp_needs_redraw(void *userdata) {
|
|
269
277
|
VALUE result = protected_call((run_state *)userdata, id_needs_redraw, 0, NULL);
|
|
270
278
|
return RTEST(result) ? 1 : 0;
|
|
@@ -308,6 +316,7 @@ static VALUE app_run(VALUE self) {
|
|
|
308
316
|
.update = tramp_update,
|
|
309
317
|
.needs_redraw = tramp_needs_redraw,
|
|
310
318
|
.draw = tramp_draw,
|
|
319
|
+
.frame_end = tramp_frame_end,
|
|
311
320
|
.button_down = tramp_button_down,
|
|
312
321
|
.button_up = tramp_button_up,
|
|
313
322
|
.resize = tramp_resize,
|
|
@@ -421,6 +430,11 @@ static VALUE app_default_draw(VALUE self) {
|
|
|
421
430
|
return Qnil;
|
|
422
431
|
}
|
|
423
432
|
|
|
433
|
+
static VALUE app_default_frame_end(VALUE self) {
|
|
434
|
+
(void)self;
|
|
435
|
+
return Qnil;
|
|
436
|
+
}
|
|
437
|
+
|
|
424
438
|
/* Default is "always redraw", matching a NULL needs_redraw in the C API. */
|
|
425
439
|
static VALUE app_default_needs_redraw(VALUE self) {
|
|
426
440
|
(void)self;
|
|
@@ -457,6 +471,7 @@ void Init_core_ext(void) {
|
|
|
457
471
|
id_frame_begin = rb_intern("frame_begin");
|
|
458
472
|
id_update = rb_intern("update");
|
|
459
473
|
id_draw = rb_intern("draw");
|
|
474
|
+
id_frame_end = rb_intern("frame_end");
|
|
460
475
|
id_needs_redraw = rb_intern("needs_redraw?");
|
|
461
476
|
id_button_down = rb_intern("button_down");
|
|
462
477
|
id_button_up = rb_intern("button_up");
|
|
@@ -498,6 +513,7 @@ void Init_core_ext(void) {
|
|
|
498
513
|
rb_define_method(cApp, "frame_begin", app_default_frame_begin, 0);
|
|
499
514
|
rb_define_method(cApp, "update", app_default_update, 1);
|
|
500
515
|
rb_define_method(cApp, "draw", app_default_draw, 0);
|
|
516
|
+
rb_define_method(cApp, "frame_end", app_default_frame_end, 0);
|
|
501
517
|
rb_define_method(cApp, "needs_redraw?", app_default_needs_redraw, 0);
|
|
502
518
|
rb_define_method(cApp, "button_down", app_default_button, 1);
|
|
503
519
|
rb_define_method(cApp, "button_up", app_default_button, 1);
|
|
@@ -297,6 +297,26 @@ static VALUE renderer_push_clip(VALUE self, VALUE x, VALUE y, VALUE width, VALUE
|
|
|
297
297
|
return self;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
/*
|
|
301
|
+
* #push_layer(base) / #layer — the base every subsequent z is measured from.
|
|
302
|
+
*
|
|
303
|
+
* `base` is a whole number well inside a double's exact range (see
|
|
304
|
+
* RGame::Util::Z), so it crosses as a double and comes back as a Float.
|
|
305
|
+
*/
|
|
306
|
+
static VALUE renderer_push_layer(VALUE self, VALUE base) {
|
|
307
|
+
rgame_app_push_layer(drawing_app(self), NUM2DBL(base));
|
|
308
|
+
return self;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
static VALUE renderer_layer(VALUE self) {
|
|
312
|
+
return DBL2NUM(rgame_app_layer(drawing_app(self)));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* #next_layer_slot(band) -> the next slot index in that band this frame. */
|
|
316
|
+
static VALUE renderer_next_layer_slot(VALUE self, VALUE band) {
|
|
317
|
+
return UINT2NUM(rgame_app_next_layer_slot(drawing_app(self), NUM2INT(band)));
|
|
318
|
+
}
|
|
319
|
+
|
|
300
320
|
/* ------------------------------------------------------------------------- *
|
|
301
321
|
* Recording
|
|
302
322
|
*
|
|
@@ -368,6 +388,9 @@ void rgame_init_renderer(VALUE mCore) {
|
|
|
368
388
|
rb_define_method(cRenderer, "push_rotate", renderer_push_rotate, 3);
|
|
369
389
|
rb_define_method(cRenderer, "push_scale", renderer_push_scale, 2);
|
|
370
390
|
rb_define_method(cRenderer, "push_clip", renderer_push_clip, 4);
|
|
391
|
+
rb_define_method(cRenderer, "push_layer", renderer_push_layer, 1);
|
|
392
|
+
rb_define_method(cRenderer, "layer", renderer_layer, 0);
|
|
393
|
+
rb_define_method(cRenderer, "next_layer_slot", renderer_next_layer_slot, 1);
|
|
371
394
|
rb_define_method(cRenderer, "pop", renderer_pop, 0);
|
|
372
395
|
|
|
373
396
|
rb_define_method(cRenderer, "begin_record", renderer_begin_record, 0);
|
data/ext/rgame_util/color_ext.c
CHANGED
|
@@ -63,10 +63,19 @@ static VALUE color_s_new(int argc, VALUE *argv, VALUE klass) {
|
|
|
63
63
|
NIL_P(a) ? 255 : component(a, "alpha")));
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/* Color.from_packed(0xRRGGBBAA)
|
|
66
|
+
/* Color.from_packed(0xRRGGBBAA)
|
|
67
|
+
*
|
|
68
|
+
* The bounds check below needs a C type wider than 32 bits so a
|
|
69
|
+
* one-bit-too-wide value can be caught rather than silently truncated.
|
|
70
|
+
* `unsigned long` is that on Linux/macOS (LP64: 64 bits) but not on Windows
|
|
71
|
+
* (LLP64: `long` stays 32 bits even in a 64-bit build), where NUM2ULONG would
|
|
72
|
+
* itself raise RangeError on the very value this function means to turn into
|
|
73
|
+
* a clean ArgumentError. NUM2ULL is unsigned long long, 64 bits on every
|
|
74
|
+
* platform this project builds for, so the check below runs before either
|
|
75
|
+
* platform's integer width can get in the way. */
|
|
67
76
|
static VALUE color_s_from_packed(VALUE klass, VALUE packed) {
|
|
68
|
-
unsigned long value =
|
|
69
|
-
if (value >
|
|
77
|
+
unsigned long long value = NUM2ULL(packed);
|
|
78
|
+
if (value > 0xFFFFFFFFull) {
|
|
70
79
|
rb_raise(rb_eArgError, "packed colour must fit in 32 bits");
|
|
71
80
|
}
|
|
72
81
|
return color_wrap(klass, (rgame_color)value);
|
data/lib/rgame/core/app.rb
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
# # fixed step, never wall-clock frame time
|
|
17
17
|
# def needs_redraw?; end # false skips the draw (simulation still runs)
|
|
18
18
|
# def draw; end # render one frame
|
|
19
|
+
# def frame_end; end # after draw is submitted, before the swap —
|
|
20
|
+
# # rarely needed outside a test that reads pixels
|
|
19
21
|
# def button_down(id); end # discrete key press (no repeats)
|
|
20
22
|
# def button_up(id); end
|
|
21
23
|
# def resize(w, h); end
|
data/lib/rgame/core/input.rb
CHANGED
|
@@ -5,72 +5,66 @@ require_relative '../util/controls'
|
|
|
5
5
|
|
|
6
6
|
module RGame
|
|
7
7
|
module Core
|
|
8
|
-
#
|
|
9
|
-
# button ids the engine understands, and asks the app whether they're held.
|
|
8
|
+
# Asks the engine whether a physical input is active, on a given device.
|
|
10
9
|
#
|
|
11
10
|
# input = RGame::Core::Input.new(app)
|
|
12
|
-
# input.down?(
|
|
13
|
-
# input.down?(
|
|
14
|
-
# input.axis(
|
|
11
|
+
# input.down?(Controls::KEY_SPACE) # keyboard
|
|
12
|
+
# input.down?(Controls::PAD_A, device: Controls.gamepad(0)) # player 1's pad
|
|
13
|
+
# input.axis(Controls::AXIS_LEFT_X, device: Controls.gamepad(0))
|
|
15
14
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
15
|
+
# **This is the raw query, and deliberately nothing more.** It used to carry
|
|
16
|
+
# binding tables of its own — `down?(:fire)` resolved `:fire` to a scancode
|
|
17
|
+
# through one of three tables passed to the constructor. Those are gone, and
|
|
18
|
+
# binding now lives one layer up in RGame::Engine::InputMap, for two
|
|
19
|
+
# reasons. A game's rebinding screen has to be able to edit the table, and
|
|
20
|
+
# the engine layer may not name RGame::Core at all; and with a player per
|
|
21
|
+
# device, the table is a per-player value rather than a property of the one
|
|
22
|
+
# object that talks to the hardware.
|
|
20
23
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
24
|
+
# What is left is an argument-order adapter over the app's own
|
|
25
|
+
# `input_down?(device, id)` / `input_axis(device, axis_id)`. It stays a named
|
|
26
|
+
# class rather than collapsing into those, because it is what gets handed to
|
|
27
|
+
# the engine layer as an input backend — and handing it a whole App, which
|
|
28
|
+
# can also close the window and rename it, would be a worse seam.
|
|
25
29
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# `device:` to the keyboard is what lets single-player call sites stay
|
|
29
|
-
# `input.down?(:fire)` with no ceremony.
|
|
30
|
+
# Ids come from RGame::Util::Controls. They are values with nothing behind
|
|
31
|
+
# them, so both layers can name one.
|
|
30
32
|
#
|
|
31
33
|
# **There is no pointer or mouse support, by design.** The layer this
|
|
32
34
|
# replaced had a cursor position and a click button riding the same "is
|
|
33
35
|
# held" path as keys, and none of it was carried over: this engine's input
|
|
34
|
-
# is keyboard and controllers.
|
|
35
|
-
#
|
|
36
|
-
# menus is keyboard and controller navigation instead.
|
|
36
|
+
# is keyboard and controllers. The intended answer for menus is keyboard and
|
|
37
|
+
# controller navigation instead.
|
|
37
38
|
class Input
|
|
38
39
|
Controls = RGame::Util::Controls
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
# `axis_bindings` the analog axes. All default to the standard tables.
|
|
42
|
-
def initialize(app,
|
|
43
|
-
bindings: Controls::DEFAULT_KEYBOARD,
|
|
44
|
-
pad_bindings: Controls::DEFAULT_PAD,
|
|
45
|
-
axis_bindings: Controls::DEFAULT_AXES)
|
|
41
|
+
def initialize(app)
|
|
46
42
|
@app = app
|
|
47
|
-
@bindings = bindings
|
|
48
|
-
@pad_bindings = pad_bindings
|
|
49
|
-
@axis_bindings = axis_bindings
|
|
50
43
|
end
|
|
51
44
|
|
|
52
|
-
# Is
|
|
45
|
+
# Is `id` held on `device`?
|
|
53
46
|
#
|
|
54
47
|
# Reads the engine's per-frame input snapshot, so the answer is identical
|
|
55
48
|
# for every simulation tick within one frame — a key held for a single
|
|
56
49
|
# frame behaves the same whether that frame ran one catch-up tick or five.
|
|
50
|
+
#
|
|
51
|
+
# A device only answers for its own kind of input: asking a gamepad about
|
|
52
|
+
# a keyboard scancode is `false`, never the keyboard's answer. That is
|
|
53
|
+
# what lets one binding table list a key and a pad button for the same
|
|
54
|
+
# action and still keep player two's pad from echoing player one.
|
|
57
55
|
# hot-path
|
|
58
|
-
def down?(
|
|
59
|
-
@app.input_down?(device,
|
|
56
|
+
def down?(id, device: Controls::KEYBOARD)
|
|
57
|
+
@app.input_down?(device, id)
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
# Current value of an analog axis: sticks -1.0..1.0, triggers 0.0..1.0.
|
|
63
61
|
# The keyboard has no axes, so it always reads 0.0.
|
|
62
|
+
#
|
|
63
|
+
# No dead zone is applied here — this is the hardware's answer. Ignoring
|
|
64
|
+
# a resting stick's jitter is RGame::Engine::ActionMapper's job.
|
|
64
65
|
# hot-path
|
|
65
|
-
def axis(
|
|
66
|
-
@app.input_axis(device,
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
# hot-path
|
|
72
|
-
def bindings_for(device)
|
|
73
|
-
device == Controls::KEYBOARD ? @bindings : @pad_bindings
|
|
66
|
+
def axis(axis_id, device: Controls::KEYBOARD)
|
|
67
|
+
@app.input_axis(device, axis_id)
|
|
74
68
|
end
|
|
75
69
|
end
|
|
76
70
|
end
|
data/lib/rgame/core/recording.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rgame/core_ext'
|
|
4
4
|
require_relative '../util/color'
|
|
5
|
+
require_relative '../util/z'
|
|
5
6
|
|
|
6
7
|
module RGame
|
|
7
8
|
module Core
|
|
@@ -37,6 +38,7 @@ module RGame
|
|
|
37
38
|
# renderer.clipped(0, 0, 400, 600) { ground.draw(0, 0) }
|
|
38
39
|
class Recording
|
|
39
40
|
Color = RGame::Util::Color
|
|
41
|
+
Z = RGame::Util::Z
|
|
40
42
|
|
|
41
43
|
# Replays everything that was recorded, with the recording's origin at
|
|
42
44
|
# (x, y).
|
|
@@ -45,7 +47,7 @@ module RGame
|
|
|
45
47
|
# so white (the default) draws the recording unchanged and a colour with
|
|
46
48
|
# alpha fades the whole layer out at once.
|
|
47
49
|
def draw(x = 0, y = 0, z: 0, color: nil)
|
|
48
|
-
draw_at(x, y, z, Color.coerce(color).packed)
|
|
50
|
+
draw_at(x, y, Z.offset(z), Color.coerce(color).packed)
|
|
49
51
|
end
|
|
50
52
|
end
|
|
51
53
|
end
|