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.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/README.md +406 -0
- data/docs/api/README.md +167 -0
- data/docs/api/app.md +192 -0
- data/docs/api/assets.md +426 -0
- data/docs/api/audio.md +208 -0
- data/docs/api/components.md +321 -0
- data/docs/api/drawing.md +330 -0
- data/docs/api/game.md +99 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +179 -0
- data/docs/api/internals.md +110 -0
- data/docs/api/scene_graph.md +159 -0
- data/docs/api/signals.md +142 -0
- data/docs/api/systems.md +98 -0
- data/docs/api/text.md +116 -0
- data/docs/api/toolbox.md +240 -0
- data/docs/api/values.md +101 -0
- data/ext/README.md +225 -0
- data/ext/rgame_core/app/app.c +721 -0
- data/ext/rgame_core/app/app_gl.h +64 -0
- data/ext/rgame_core/app/frame_loop.c +42 -0
- data/ext/rgame_core/app/frame_loop.h +54 -0
- data/ext/rgame_core/audio/audio.c +466 -0
- data/ext/rgame_core/audio/audio_internal.h +45 -0
- data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
- data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
- data/ext/rgame_core/example.rb +188 -0
- data/ext/rgame_core/extconf.rb +167 -0
- data/ext/rgame_core/graphics/backend.c +52 -0
- data/ext/rgame_core/graphics/backend.h +64 -0
- data/ext/rgame_core/graphics/canvas.c +247 -0
- data/ext/rgame_core/graphics/canvas.h +143 -0
- data/ext/rgame_core/graphics/clip.c +87 -0
- data/ext/rgame_core/graphics/clip.h +89 -0
- data/ext/rgame_core/graphics/draw_queue.c +216 -0
- data/ext/rgame_core/graphics/draw_queue.h +174 -0
- data/ext/rgame_core/graphics/gl_backend.c +122 -0
- data/ext/rgame_core/graphics/gl_backend.h +43 -0
- data/ext/rgame_core/graphics/image.c +304 -0
- data/ext/rgame_core/graphics/image_internal.h +30 -0
- data/ext/rgame_core/graphics/primitives.c +189 -0
- data/ext/rgame_core/graphics/primitives.h +111 -0
- data/ext/rgame_core/graphics/recording.c +119 -0
- data/ext/rgame_core/graphics/recording.h +88 -0
- data/ext/rgame_core/graphics/texture.c +181 -0
- data/ext/rgame_core/graphics/texture.h +165 -0
- data/ext/rgame_core/graphics/transform.c +128 -0
- data/ext/rgame_core/graphics/transform.h +106 -0
- data/ext/rgame_core/include/rgame/core.h +577 -0
- data/ext/rgame_core/input/device_slots.c +103 -0
- data/ext/rgame_core/input/device_slots.h +93 -0
- data/ext/rgame_core/input/gamepad.c +145 -0
- data/ext/rgame_core/input/gamepad.h +63 -0
- data/ext/rgame_core/input/input.c +109 -0
- data/ext/rgame_core/input/input.h +99 -0
- data/ext/rgame_core/ruby/audio_ext.c +321 -0
- data/ext/rgame_core/ruby/core_ext.c +513 -0
- data/ext/rgame_core/ruby/core_ext.h +51 -0
- data/ext/rgame_core/ruby/font_ext.c +168 -0
- data/ext/rgame_core/ruby/image_ext.c +230 -0
- data/ext/rgame_core/ruby/recording_ext.c +186 -0
- data/ext/rgame_core/ruby/renderer_ext.c +376 -0
- data/ext/rgame_core/text/atlas.c +59 -0
- data/ext/rgame_core/text/atlas.h +85 -0
- data/ext/rgame_core/text/font.c +281 -0
- data/ext/rgame_core/text/font.h +139 -0
- data/ext/rgame_core/text/font_atlas.c +385 -0
- data/ext/rgame_core/text/font_internal.h +47 -0
- data/ext/rgame_core/text/glyph_cache.c +142 -0
- data/ext/rgame_core/text/glyph_cache.h +89 -0
- data/ext/rgame_core/vendor/README.md +159 -0
- data/ext/rgame_core/vendor/miniaudio.h +95864 -0
- data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
- data/ext/rgame_core/vendor/stb_image.h +7988 -0
- data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
- data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
- data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
- data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
- data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
- data/ext/rgame_util/color.c +19 -0
- data/ext/rgame_util/color.h +60 -0
- data/ext/rgame_util/color_ext.c +156 -0
- data/ext/rgame_util/extconf.rb +27 -0
- data/ext/rgame_util/tensor.c +186 -0
- data/ext/rgame_util/util_ext.c +27 -0
- data/ext/rgame_util/util_ext.h +16 -0
- data/lib/rgame/boot.rb +13 -0
- data/lib/rgame/core/app.rb +82 -0
- data/lib/rgame/core/asset_manager.rb +224 -0
- data/lib/rgame/core/audio.rb +124 -0
- data/lib/rgame/core/font.rb +49 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +77 -0
- data/lib/rgame/core/nine_slice.rb +163 -0
- data/lib/rgame/core/recording.rb +52 -0
- data/lib/rgame/core/renderer.rb +363 -0
- data/lib/rgame/core/sprite_sheet.rb +108 -0
- data/lib/rgame/core/tile_map_renderer.rb +160 -0
- data/lib/rgame/core/ui_atlas.rb +86 -0
- data/lib/rgame/core.rb +24 -0
- data/lib/rgame/engine/actor.rb +53 -0
- data/lib/rgame/engine/animation_set.rb +49 -0
- data/lib/rgame/engine/animator.rb +44 -0
- data/lib/rgame/engine/audio_bus.rb +24 -0
- data/lib/rgame/engine/audio_director.rb +29 -0
- data/lib/rgame/engine/body.rb +49 -0
- data/lib/rgame/engine/cached_label.rb +33 -0
- data/lib/rgame/engine/camera.rb +33 -0
- data/lib/rgame/engine/camera_view.rb +28 -0
- data/lib/rgame/engine/circle_collider.rb +32 -0
- data/lib/rgame/engine/collision_box.rb +34 -0
- data/lib/rgame/engine/collision_system.rb +44 -0
- data/lib/rgame/engine/component.rb +30 -0
- data/lib/rgame/engine/components/action_trigger.rb +41 -0
- data/lib/rgame/engine/components/animated_sprite.rb +63 -0
- data/lib/rgame/engine/components/character_body.rb +70 -0
- data/lib/rgame/engine/components/circle_collider.rb +44 -0
- data/lib/rgame/engine/components/collision_world.rb +103 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
- data/lib/rgame/engine/components/path_follow.rb +84 -0
- data/lib/rgame/engine/components/player_controller.rb +24 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +27 -0
- data/lib/rgame/engine/components/sprite.rb +31 -0
- data/lib/rgame/engine/components/targeting.rb +54 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +68 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +27 -0
- data/lib/rgame/engine/components/wander_controller.rb +60 -0
- data/lib/rgame/engine/debug_overlay.rb +106 -0
- data/lib/rgame/engine/i18n.rb +97 -0
- data/lib/rgame/engine/input/action_mapper.rb +46 -0
- data/lib/rgame/engine/input/actions.rb +41 -0
- data/lib/rgame/engine/input/player_controller.rb +14 -0
- data/lib/rgame/engine/matrix.rb +32 -0
- data/lib/rgame/engine/node2d.rb +271 -0
- data/lib/rgame/engine/path.rb +78 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/resettable.rb +67 -0
- data/lib/rgame/engine/scene/scene_stack.rb +65 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +71 -0
- data/lib/rgame/engine/tile_collision.rb +78 -0
- data/lib/rgame/engine/tile_map.rb +149 -0
- data/lib/rgame/engine/tileset.rb +101 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine.rb +68 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +129 -0
- data/lib/rgame/util/color.rb +27 -0
- data/lib/rgame/util/controls.rb +107 -0
- data/lib/rgame/util/tensor.rb +12 -0
- data/lib/rgame/util.rb +8 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +20 -0
- metadata +215 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#ifndef RGAME_TRANSFORM_H
|
|
2
|
+
#define RGAME_TRANSFORM_H
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* The 2D affine transform stack — pure arithmetic, no SDL, no GL, no I/O.
|
|
6
|
+
*
|
|
7
|
+
* This is what lets a node spin its whole subtree about its own origin, and
|
|
8
|
+
* what lets a camera shift a world subtree by (-camera_x, -camera_y) without
|
|
9
|
+
* any world-space node knowing a camera exists. Split-screen is the same
|
|
10
|
+
* mechanism used twice in one frame, which is why this is a real stack rather
|
|
11
|
+
* than one "current transform" global.
|
|
12
|
+
*
|
|
13
|
+
* ---------------------------------------------------------------------------
|
|
14
|
+
* What an affine transform is, since this is the first one in the project
|
|
15
|
+
* ---------------------------------------------------------------------------
|
|
16
|
+
*
|
|
17
|
+
* Every operation we need — move, rotate, scale, and any combination — can be
|
|
18
|
+
* written as six numbers applied to a point like this:
|
|
19
|
+
*
|
|
20
|
+
* x' = a*x + c*y + tx
|
|
21
|
+
* y' = b*x + d*y + ty
|
|
22
|
+
*
|
|
23
|
+
* `a b c d` are the "linear" part (rotation and scale); `tx ty` are the
|
|
24
|
+
* translation. Six floats, and the same six lines of arithmetic no matter how
|
|
25
|
+
* many transforms are nested — because *composing* two transforms produces
|
|
26
|
+
* another six numbers. That is the whole reason to use matrices here: nesting
|
|
27
|
+
* ten transforms costs the same per-vertex work as nesting one.
|
|
28
|
+
*
|
|
29
|
+
* ---------------------------------------------------------------------------
|
|
30
|
+
* Rotation direction — measured, not assumed
|
|
31
|
+
* ---------------------------------------------------------------------------
|
|
32
|
+
*
|
|
33
|
+
* A positive angle rotates **clockwise on screen**: a point to the right of the
|
|
34
|
+
* pivot moves to below it. Established by rendering a rotated image off-screen
|
|
35
|
+
* and reading back which pixel the ink landed on, not by reasoning about the
|
|
36
|
+
* matrix — `spec_core/rgame/core/renderer_spec.rb` still does exactly that.
|
|
37
|
+
*
|
|
38
|
+
* It falls out of the standard rotation matrix without a sign flip, because
|
|
39
|
+
* screen y points *down*: the same matrix that turns anticlockwise on graph
|
|
40
|
+
* paper turns clockwise here. Worth knowing rather than rediscovering — get it
|
|
41
|
+
* backwards and every rotated sprite in the game mirrors.
|
|
42
|
+
*
|
|
43
|
+
* Angles are degrees, which is what `Renderer#rotated` callers pass and what
|
|
44
|
+
* a level editor writes.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/* Deep enough for any sane scene graph; a bounded stack cannot run away, and
|
|
48
|
+
* an overflow is reported rather than silently scribbling past the end. */
|
|
49
|
+
#define RGAME_TRANSFORM_STACK_DEPTH 32
|
|
50
|
+
|
|
51
|
+
typedef struct {
|
|
52
|
+
float a, b, c, d; /* linear part: rotation and scale */
|
|
53
|
+
float tx, ty; /* translation */
|
|
54
|
+
} rgame_transform;
|
|
55
|
+
|
|
56
|
+
typedef struct {
|
|
57
|
+
/* entries[0] is always identity — the un-transformed base — so `depth` is
|
|
58
|
+
* the number of pushes outstanding and entries[depth] is the current top. */
|
|
59
|
+
rgame_transform entries[RGAME_TRANSFORM_STACK_DEPTH];
|
|
60
|
+
int depth;
|
|
61
|
+
} rgame_transform_stack;
|
|
62
|
+
|
|
63
|
+
rgame_transform rgame_transform_identity(void);
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* Composition: the result applies `inner` first, then `outer`. That is,
|
|
67
|
+
*
|
|
68
|
+
* apply(multiply(outer, inner), p) == apply(outer, apply(inner, p))
|
|
69
|
+
*
|
|
70
|
+
* which is the order nesting reads in: in `translated { rotated { draw } }`
|
|
71
|
+
* the rotation happens to the point first and the translation moves the
|
|
72
|
+
* already-rotated result.
|
|
73
|
+
*/
|
|
74
|
+
rgame_transform rgame_transform_multiply(rgame_transform outer, rgame_transform inner);
|
|
75
|
+
|
|
76
|
+
void rgame_transform_stack_init(rgame_transform_stack *stack);
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* Each push composes with the current top and pushes the *result*, so `apply`
|
|
80
|
+
* stays a single six-multiply operation however deep the nesting goes.
|
|
81
|
+
*
|
|
82
|
+
* All three return 1 on success and 0 if the stack is full. A full stack leaves
|
|
83
|
+
* the existing entries untouched — the caller's drawing comes out unrotated
|
|
84
|
+
* rather than corrupt — but it does mean a matching pop must not be issued, so
|
|
85
|
+
* check the return value if the depth is not statically known.
|
|
86
|
+
*/
|
|
87
|
+
int rgame_transform_push_translate(rgame_transform_stack *stack, float dx, float dy);
|
|
88
|
+
int rgame_transform_push_rotate(rgame_transform_stack *stack, float degrees,
|
|
89
|
+
float pivot_x, float pivot_y);
|
|
90
|
+
int rgame_transform_push_scale(rgame_transform_stack *stack, float sx, float sy);
|
|
91
|
+
|
|
92
|
+
/* Pops the current top. Popping the base is a no-op rather than an underflow. */
|
|
93
|
+
void rgame_transform_pop(rgame_transform_stack *stack);
|
|
94
|
+
|
|
95
|
+
rgame_transform rgame_transform_current(const rgame_transform_stack *stack);
|
|
96
|
+
int rgame_transform_depth(const rgame_transform_stack *stack);
|
|
97
|
+
|
|
98
|
+
/* Maps a point through the current top. This is the per-vertex hot path. */
|
|
99
|
+
void rgame_transform_apply(const rgame_transform_stack *stack, float x, float y,
|
|
100
|
+
float *out_x, float *out_y);
|
|
101
|
+
|
|
102
|
+
/* Whether the current top would leave every point where it is. Lets a caller
|
|
103
|
+
* skip work that would be a no-op — the common case for un-transformed draws. */
|
|
104
|
+
int rgame_transform_is_identity(const rgame_transform_stack *stack);
|
|
105
|
+
|
|
106
|
+
#endif /* RGAME_TRANSFORM_H */
|
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
#ifndef RGAME_CORE_H
|
|
2
|
+
#define RGAME_CORE_H
|
|
3
|
+
|
|
4
|
+
#ifdef __cplusplus
|
|
5
|
+
extern "C" {
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
/* size_t only — this header still exposes no SDL or GL types. */
|
|
9
|
+
#include <stddef.h>
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Public API of the core engine.
|
|
13
|
+
*
|
|
14
|
+
* This header intentionally exposes no SDL or OpenGL types. Callers (the
|
|
15
|
+
* standalone app in src/main.c, the Ruby C extension in core_ext.c) only ever
|
|
16
|
+
* see an opaque handle. That keeps the door open for wrapping this library
|
|
17
|
+
* from Ruby without leaking C-library-specific types into ruby.h-using code.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
typedef struct rgame_app rgame_app;
|
|
21
|
+
|
|
22
|
+
/* Creates the window, GL context and internal state. Returns NULL on failure. */
|
|
23
|
+
rgame_app *rgame_app_create(int width, int height, const char *title);
|
|
24
|
+
|
|
25
|
+
/* Destroys the GL context/window and frees the app. Safe to call with NULL. */
|
|
26
|
+
void rgame_app_destroy(rgame_app *app);
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* ---------------------------------------------------------------------------
|
|
30
|
+
* Input: devices and buttons
|
|
31
|
+
* ---------------------------------------------------------------------------
|
|
32
|
+
*
|
|
33
|
+
* One flat button-id space covers every input device, so a single "is this
|
|
34
|
+
* held" query serves them all. It is partitioned into ranges rather than
|
|
35
|
+
* packed into bit fields, which means adding a device class later appends to
|
|
36
|
+
* an unused range instead of renumbering anything that already exists:
|
|
37
|
+
*
|
|
38
|
+
* 0x0000–0x0FFF keyboard (SDL scancode values)
|
|
39
|
+
* 0x1000–0x10FF gamepad buttons and dpad
|
|
40
|
+
*
|
|
41
|
+
* (The range a mouse would have occupied is deliberately left unused. Mouse
|
|
42
|
+
* input is not part of this engine — see RGame::Core::Input — and leaving the
|
|
43
|
+
* gap costs nothing while renumbering later would cost every saved keybinding.)
|
|
44
|
+
*
|
|
45
|
+
* Keyboard ids are SDL scancodes, but callers must not need SDL to name them:
|
|
46
|
+
* src/main.c includes only this header, so the constants it uses have to live
|
|
47
|
+
* here. app.c carries a _Static_assert for every one, so these can never
|
|
48
|
+
* silently drift from the SDL values they mirror.
|
|
49
|
+
*/
|
|
50
|
+
#define RGAME_BUTTON_KEYBOARD_FIRST 0x0000
|
|
51
|
+
#define RGAME_BUTTON_KEYBOARD_LAST 0x0FFF
|
|
52
|
+
#define RGAME_BUTTON_GAMEPAD_FIRST 0x1000
|
|
53
|
+
#define RGAME_BUTTON_GAMEPAD_LAST 0x10FF
|
|
54
|
+
|
|
55
|
+
/* Keyboard ids == SDL scancodes. Only the keys the engine actually binds are
|
|
56
|
+
* named; adding one is a #define plus a _Static_assert in app.c. */
|
|
57
|
+
#define RGAME_KEY_RETURN 40
|
|
58
|
+
#define RGAME_KEY_ESCAPE 41
|
|
59
|
+
#define RGAME_KEY_SPACE 44
|
|
60
|
+
#define RGAME_KEY_F1 58
|
|
61
|
+
#define RGAME_KEY_RIGHT 79
|
|
62
|
+
#define RGAME_KEY_LEFT 80
|
|
63
|
+
#define RGAME_KEY_DOWN 81
|
|
64
|
+
#define RGAME_KEY_UP 82
|
|
65
|
+
|
|
66
|
+
/* Gamepad button ids: the gamepad range plus SDL's controller button number.
|
|
67
|
+
* app.c asserts each against the SDL constant it mirrors, as with the keys. */
|
|
68
|
+
#define RGAME_PAD_A (RGAME_BUTTON_GAMEPAD_FIRST + 0)
|
|
69
|
+
#define RGAME_PAD_B (RGAME_BUTTON_GAMEPAD_FIRST + 1)
|
|
70
|
+
#define RGAME_PAD_X (RGAME_BUTTON_GAMEPAD_FIRST + 2)
|
|
71
|
+
#define RGAME_PAD_Y (RGAME_BUTTON_GAMEPAD_FIRST + 3)
|
|
72
|
+
#define RGAME_PAD_BACK (RGAME_BUTTON_GAMEPAD_FIRST + 4)
|
|
73
|
+
#define RGAME_PAD_GUIDE (RGAME_BUTTON_GAMEPAD_FIRST + 5)
|
|
74
|
+
#define RGAME_PAD_START (RGAME_BUTTON_GAMEPAD_FIRST + 6)
|
|
75
|
+
#define RGAME_PAD_LEFT_STICK (RGAME_BUTTON_GAMEPAD_FIRST + 7)
|
|
76
|
+
#define RGAME_PAD_RIGHT_STICK (RGAME_BUTTON_GAMEPAD_FIRST + 8)
|
|
77
|
+
#define RGAME_PAD_LEFT_SHOULDER (RGAME_BUTTON_GAMEPAD_FIRST + 9)
|
|
78
|
+
#define RGAME_PAD_RIGHT_SHOULDER (RGAME_BUTTON_GAMEPAD_FIRST + 10)
|
|
79
|
+
#define RGAME_PAD_DPAD_UP (RGAME_BUTTON_GAMEPAD_FIRST + 11)
|
|
80
|
+
#define RGAME_PAD_DPAD_DOWN (RGAME_BUTTON_GAMEPAD_FIRST + 12)
|
|
81
|
+
#define RGAME_PAD_DPAD_LEFT (RGAME_BUTTON_GAMEPAD_FIRST + 13)
|
|
82
|
+
#define RGAME_PAD_DPAD_RIGHT (RGAME_BUTTON_GAMEPAD_FIRST + 14)
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* Analog axes are their own small id space rather than part of the button
|
|
86
|
+
* space: they are float-valued and read through a different call, so folding
|
|
87
|
+
* them in would only invite asking for an axis as if it were a button.
|
|
88
|
+
*
|
|
89
|
+
* Stick axes read -1.0 to 1.0 (Y is positive *downwards*, as SDL reports it);
|
|
90
|
+
* triggers read 0.0 to 1.0. No dead zone is applied — where to put one is a
|
|
91
|
+
* game decision, and a resting stick genuinely does report small non-zero
|
|
92
|
+
* values.
|
|
93
|
+
*/
|
|
94
|
+
#define RGAME_AXIS_LEFT_X 0
|
|
95
|
+
#define RGAME_AXIS_LEFT_Y 1
|
|
96
|
+
#define RGAME_AXIS_RIGHT_X 2
|
|
97
|
+
#define RGAME_AXIS_RIGHT_Y 3
|
|
98
|
+
#define RGAME_AXIS_TRIGGER_LEFT 4
|
|
99
|
+
#define RGAME_AXIS_TRIGGER_RIGHT 5
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* Which device a query is about. The keyboard is device 0 so single-player
|
|
103
|
+
* code can ignore the parameter entirely; gamepads follow, one per player
|
|
104
|
+
* slot, in the stable slot order the hot-plug table hands out.
|
|
105
|
+
*/
|
|
106
|
+
#define RGAME_INPUT_KEYBOARD 0
|
|
107
|
+
#define RGAME_INPUT_GAMEPAD_FIRST 1
|
|
108
|
+
#define RGAME_INPUT_MAX_GAMEPADS 4
|
|
109
|
+
#define RGAME_INPUT_GAMEPAD(slot) (RGAME_INPUT_GAMEPAD_FIRST + (slot))
|
|
110
|
+
#define RGAME_INPUT_DEVICE_COUNT (RGAME_INPUT_GAMEPAD_FIRST + RGAME_INPUT_MAX_GAMEPADS)
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* Per-frame and event callbacks.
|
|
114
|
+
*
|
|
115
|
+
* These are deliberately fixed-arity (each takes exactly the arguments it
|
|
116
|
+
* needs, plus an opaque userdata pointer) rather than variadic. A variadic
|
|
117
|
+
* calling convention on a per-frame hot path allocates/marshals on every
|
|
118
|
+
* single call for no benefit — see docs/c_engine_feature_specs.md section 4,
|
|
119
|
+
* which calls this out as a concrete cost worth designing out from the start.
|
|
120
|
+
*/
|
|
121
|
+
typedef void (*rgame_frame_begin_fn)(void *userdata);
|
|
122
|
+
typedef void (*rgame_update_fn)(void *userdata, double dt_seconds);
|
|
123
|
+
typedef void (*rgame_draw_fn)(void *userdata);
|
|
124
|
+
typedef int (*rgame_needs_redraw_fn)(void *userdata);
|
|
125
|
+
typedef void (*rgame_button_fn)(void *userdata, int button_id);
|
|
126
|
+
typedef void (*rgame_resize_fn)(void *userdata, int width, int height);
|
|
127
|
+
typedef void (*rgame_gamepad_fn)(void *userdata, int slot);
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* The callbacks rgame_app_run drives, gathered into one struct rather than
|
|
131
|
+
* passed as an ever-growing list of positional arguments. Every member may be
|
|
132
|
+
* NULL, in which case that hook is simply skipped.
|
|
133
|
+
*
|
|
134
|
+
* - `frame_begin` runs once per rendered frame, before that frame's
|
|
135
|
+
* simulation ticks. It is the place to sample input once and reuse the
|
|
136
|
+
* result across every catch-up tick, so a key held for one frame behaves
|
|
137
|
+
* the same however many ticks that frame ends up running.
|
|
138
|
+
* - `update` runs at a fixed timestep via an internal accumulator, so it may
|
|
139
|
+
* be called zero or several times per rendered frame; dt_seconds is that
|
|
140
|
+
* fixed step, not wall-clock frame time.
|
|
141
|
+
* - `needs_redraw` is polled before drawing; returning 0 skips the draw for
|
|
142
|
+
* that frame (simulation still advances). NULL means always redraw.
|
|
143
|
+
* - `draw` renders one frame.
|
|
144
|
+
* - `button_down`/`button_up` report discrete key presses and releases. Key
|
|
145
|
+
* repeats are filtered out, so holding a key reports exactly one press.
|
|
146
|
+
* - `resize` reports a new window size; the GL viewport is already updated.
|
|
147
|
+
* - `gamepad_connected`/`gamepad_disconnected` report a controller arriving
|
|
148
|
+
* at or leaving a player slot. A slot is stable across a momentary
|
|
149
|
+
* unplug/replug, so "player 2" stays player 2.
|
|
150
|
+
* - `userdata` is forwarded to every callback; may be NULL.
|
|
151
|
+
*/
|
|
152
|
+
typedef struct {
|
|
153
|
+
rgame_frame_begin_fn frame_begin;
|
|
154
|
+
rgame_update_fn update;
|
|
155
|
+
rgame_needs_redraw_fn needs_redraw;
|
|
156
|
+
rgame_draw_fn draw;
|
|
157
|
+
rgame_button_fn button_down;
|
|
158
|
+
rgame_button_fn button_up;
|
|
159
|
+
rgame_resize_fn resize;
|
|
160
|
+
rgame_gamepad_fn gamepad_connected;
|
|
161
|
+
rgame_gamepad_fn gamepad_disconnected;
|
|
162
|
+
void *userdata;
|
|
163
|
+
} rgame_app_callbacks;
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* Runs the main loop until the window is closed or rgame_app_close is called.
|
|
167
|
+
*
|
|
168
|
+
* The engine owns the loop and calls back out through `callbacks`. Note there
|
|
169
|
+
* is no built-in quit key: closing on Escape is a game decision, so it belongs
|
|
170
|
+
* in a button_down handler rather than in here.
|
|
171
|
+
*/
|
|
172
|
+
void rgame_app_run(rgame_app *app, const rgame_app_callbacks *callbacks);
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
* Asks the loop to stop. Safe to call from inside a callback — the loop checks
|
|
176
|
+
* between steps, so it exits without starting further work this frame.
|
|
177
|
+
*/
|
|
178
|
+
void rgame_app_close(rgame_app *app);
|
|
179
|
+
|
|
180
|
+
/* Current window size, in window coordinates. */
|
|
181
|
+
int rgame_app_width(const rgame_app *app);
|
|
182
|
+
int rgame_app_height(const rgame_app *app);
|
|
183
|
+
|
|
184
|
+
/* Window title. The returned string is owned by the window, not the caller. */
|
|
185
|
+
const char *rgame_app_title(const rgame_app *app);
|
|
186
|
+
void rgame_app_set_title(rgame_app *app, const char *title);
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* Is `button_id` currently held on `device`?
|
|
190
|
+
*
|
|
191
|
+
* This reads a snapshot taken once per frame, when the event queue is pumped —
|
|
192
|
+
* not live hardware state. That is deliberate: the answer is then identical
|
|
193
|
+
* for every simulation tick within one frame, so a key held for a single frame
|
|
194
|
+
* behaves the same whether that frame ran one catch-up tick or five. Sampling
|
|
195
|
+
* live state would make the result depend on tick count, which is exactly the
|
|
196
|
+
* nondeterminism a fixed timestep exists to remove.
|
|
197
|
+
*
|
|
198
|
+
* A device only answers for buttons in its own range, so asking a gamepad
|
|
199
|
+
* about a keyboard key is 0 rather than an error.
|
|
200
|
+
*/
|
|
201
|
+
int rgame_app_input_down(const rgame_app *app, int device, int button_id);
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
* Current value of an analog axis on `device`, read from the same per-frame
|
|
205
|
+
* snapshot as rgame_app_input_down. Sticks read -1.0..1.0, triggers 0.0..1.0.
|
|
206
|
+
* An unknown device or axis, or a slot with no controller, reads 0.0.
|
|
207
|
+
*/
|
|
208
|
+
float rgame_app_input_axis(const rgame_app *app, int device, int axis_id);
|
|
209
|
+
|
|
210
|
+
/* Is a controller plugged into player `slot` (0-based, < RGAME_INPUT_MAX_GAMEPADS)? */
|
|
211
|
+
int rgame_app_gamepad_connected(const rgame_app *app, int slot);
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
* Human-readable name of the controller in `slot`, for "Player 2: connect a
|
|
215
|
+
* controller" UI. Returns NULL when the slot is empty. The string is owned by
|
|
216
|
+
* the engine and is only valid while that controller stays connected.
|
|
217
|
+
*/
|
|
218
|
+
const char *rgame_app_gamepad_name(const rgame_app *app, int slot);
|
|
219
|
+
|
|
220
|
+
/* How many controllers are currently connected. */
|
|
221
|
+
int rgame_app_gamepad_count(const rgame_app *app);
|
|
222
|
+
|
|
223
|
+
/* Monotonic milliseconds since startup. For time-based animation phase, etc. */
|
|
224
|
+
unsigned int rgame_app_ticks_ms(const rgame_app *app);
|
|
225
|
+
|
|
226
|
+
/* Most recent frames-per-second reading (updated ~once per second). */
|
|
227
|
+
double rgame_app_fps(const rgame_app *app);
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
* ---------------------------------------------------------------------------
|
|
231
|
+
* Images
|
|
232
|
+
* ---------------------------------------------------------------------------
|
|
233
|
+
*
|
|
234
|
+
* An image is a rectangle of an uploaded texture. `rgame_image_load` decodes a
|
|
235
|
+
* PNG and uploads it; `rgame_image_subimage` and `rgame_image_tile` carve that
|
|
236
|
+
* upload into sprites *without decoding or uploading anything again* — they
|
|
237
|
+
* are views sharing one GPU texture, which is what makes slicing a sprite
|
|
238
|
+
* sheet into hundreds of frames cheap.
|
|
239
|
+
*
|
|
240
|
+
* Every handle returned here, views included, must be passed to
|
|
241
|
+
* `rgame_image_destroy`. The shared texture is deleted when the last of them
|
|
242
|
+
* goes, in whatever order they go — so a sheet can be dropped while its tiles
|
|
243
|
+
* are still in use.
|
|
244
|
+
*
|
|
245
|
+
* Images belong to the `app` whose GL context they were uploaded into, but they
|
|
246
|
+
* do not have to be destroyed before it. Destroying the app first closes the
|
|
247
|
+
* window immediately and takes its textures with it — a GL context frees
|
|
248
|
+
* everything in it — and the images left behind stay valid handles that free
|
|
249
|
+
* cleanly afterwards. Either order works, which matters because a garbage
|
|
250
|
+
* collector picks the order, not the programmer.
|
|
251
|
+
*
|
|
252
|
+
* Images are scaled with nearest-neighbour sampling, always: the engine exists
|
|
253
|
+
* to draw pixel art, and smoothing it is never the intent.
|
|
254
|
+
*/
|
|
255
|
+
typedef struct rgame_image rgame_image;
|
|
256
|
+
|
|
257
|
+
/*
|
|
258
|
+
* Decodes the PNG at `path` and uploads it. Returns NULL on failure, writing a
|
|
259
|
+
* human-readable reason into `err` (which may be NULL if the caller does not
|
|
260
|
+
* want one). Failure is ordinary — a missing or corrupt asset file — so it is
|
|
261
|
+
* reported rather than logged and swallowed.
|
|
262
|
+
*/
|
|
263
|
+
rgame_image *rgame_image_load(rgame_app *app, const char *path, char *err, size_t err_size);
|
|
264
|
+
|
|
265
|
+
/*
|
|
266
|
+
* A view of part of `image`, in pixels relative to `image` itself — so a
|
|
267
|
+
* subimage of a subimage composes, and none of them can address pixels outside
|
|
268
|
+
* what they were cut from. Returns NULL if the rectangle does not fit.
|
|
269
|
+
*/
|
|
270
|
+
rgame_image *rgame_image_subimage(const rgame_image *image, int x, int y, int width, int height);
|
|
271
|
+
|
|
272
|
+
/*
|
|
273
|
+
* Grid slicing, for sprite sheets. `rgame_image_tile_count` reports how many
|
|
274
|
+
* whole tiles of that size fit (a partial tile at the right or bottom edge is
|
|
275
|
+
* padding and is not counted); `rgame_image_tile` returns the index-th of
|
|
276
|
+
* them, counting left to right and then top to bottom, or NULL if the index is
|
|
277
|
+
* out of range.
|
|
278
|
+
*/
|
|
279
|
+
int rgame_image_tile_count(const rgame_image *image, int tile_width, int tile_height);
|
|
280
|
+
rgame_image *rgame_image_tile(const rgame_image *image, int tile_width, int tile_height,
|
|
281
|
+
int index);
|
|
282
|
+
|
|
283
|
+
int rgame_image_width(const rgame_image *image);
|
|
284
|
+
int rgame_image_height(const rgame_image *image);
|
|
285
|
+
|
|
286
|
+
/* Releases this handle's share of the texture. Safe to call with NULL. */
|
|
287
|
+
void rgame_image_destroy(rgame_image *image);
|
|
288
|
+
|
|
289
|
+
/*
|
|
290
|
+
* ---------------------------------------------------------------------------
|
|
291
|
+
* Drawing
|
|
292
|
+
* ---------------------------------------------------------------------------
|
|
293
|
+
*
|
|
294
|
+
* Every call here is only valid **inside the draw callback**. The app opens a
|
|
295
|
+
* frame before calling it and closes the frame afterwards, sorting and
|
|
296
|
+
* submitting what was drawn; outside that window there is no frame to draw
|
|
297
|
+
* into, and these calls do nothing. `rgame_app_is_drawing` says which state the
|
|
298
|
+
* app is in, so a binding can raise instead of silently discarding.
|
|
299
|
+
*
|
|
300
|
+
* Drawing is not immediate. A call appends to a queue that is z-sorted and
|
|
301
|
+
* batched when the frame closes, so the order calls are made in does not
|
|
302
|
+
* decide what ends up on top — `z` does, and equal z keeps call order.
|
|
303
|
+
*
|
|
304
|
+
* Colours are packed 0xRRGGBBAA, matching RGame::Util::Color#packed.
|
|
305
|
+
*
|
|
306
|
+
* Coordinates are in screen pixels with (0,0) at the top-left and y growing
|
|
307
|
+
* downwards, transformed by whatever is on the stack (see the push/pop calls
|
|
308
|
+
* below). Angles are in degrees, and positive turns clockwise on screen.
|
|
309
|
+
*/
|
|
310
|
+
|
|
311
|
+
/* Is the app between opening and closing a frame — that is, inside `draw`? */
|
|
312
|
+
int rgame_app_is_drawing(const rgame_app *app);
|
|
313
|
+
|
|
314
|
+
void rgame_app_draw_rect(rgame_app *app, float x, float y, float width, float height,
|
|
315
|
+
unsigned int color, double z);
|
|
316
|
+
|
|
317
|
+
/*
|
|
318
|
+
* `xy8` is four points, `xy6` three, as flat x,y pairs. A quad's corners are
|
|
319
|
+
* taken in loop order — for a rectangle: top-left, top-right, bottom-right,
|
|
320
|
+
* bottom-left — so listing them in Z order gives an hourglass, not a shape.
|
|
321
|
+
*/
|
|
322
|
+
void rgame_app_draw_quad(rgame_app *app, const float *xy8, unsigned int color, double z);
|
|
323
|
+
void rgame_app_draw_triangle(rgame_app *app, const float *xy6, unsigned int color, double z);
|
|
324
|
+
|
|
325
|
+
/* A line of real thickness, drawn as a quad — GL's own line width is a
|
|
326
|
+
* suggestion drivers may ignore above 1px. */
|
|
327
|
+
void rgame_app_draw_line(rgame_app *app, float x1, float y1, float x2, float y2,
|
|
328
|
+
float thickness, unsigned int color, double z);
|
|
329
|
+
|
|
330
|
+
/* A filled circle as a fan of `segments` triangles. */
|
|
331
|
+
void rgame_app_draw_circle(rgame_app *app, float cx, float cy, float radius, int segments,
|
|
332
|
+
unsigned int color, double z);
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
* Images. Both return 0 without drawing if the image belongs to a *different*
|
|
336
|
+
* app, and 1 otherwise.
|
|
337
|
+
*
|
|
338
|
+
* That check is not pedantry. A GL texture lives in one context and is not
|
|
339
|
+
* shared with another, so drawing another window's image samples nothing and
|
|
340
|
+
* paints a plain white quad — no GL error, nothing in a log, just a white
|
|
341
|
+
* rectangle where the sprite should be. Reporting it lets a binding raise.
|
|
342
|
+
*/
|
|
343
|
+
|
|
344
|
+
/* An image with its top-left at (x, y), at its natural size. */
|
|
345
|
+
int rgame_app_draw_image(rgame_app *app, const rgame_image *image, float x, float y,
|
|
346
|
+
unsigned int color, double z);
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* An image with its top-left at (x, y), scaled independently per axis. A
|
|
350
|
+
* negative scale mirrors the image *inside the same rectangle* rather than
|
|
351
|
+
* about the anchor, so (x, y) is the top-left corner whatever the sign; a zero
|
|
352
|
+
* scale draws nothing.
|
|
353
|
+
*/
|
|
354
|
+
int rgame_app_draw_image_scaled(rgame_app *app, const rgame_image *image, float x, float y,
|
|
355
|
+
float scale_x, float scale_y, unsigned int color, double z);
|
|
356
|
+
|
|
357
|
+
/* An image centred on (cx, cy), rotated clockwise about that centre and
|
|
358
|
+
* uniformly scaled. */
|
|
359
|
+
int rgame_app_draw_image_rot(rgame_app *app, const rgame_image *image, float cx, float cy,
|
|
360
|
+
float angle_degrees, float scale, unsigned int color, double z);
|
|
361
|
+
|
|
362
|
+
/*
|
|
363
|
+
* The transform and clip stacks. Every push is undone by the same
|
|
364
|
+
* `rgame_app_pop`, so a caller can never pop the wrong one; a push that cannot
|
|
365
|
+
* be honoured (a full stack) is still counted, so pops stay balanced and the
|
|
366
|
+
* drawing comes out untransformed rather than desynchronised.
|
|
367
|
+
*
|
|
368
|
+
* A clip *narrows*: pushing one can only shrink the visible region, never widen
|
|
369
|
+
* it, so a child can never draw outside what its parent allowed. That is what
|
|
370
|
+
* makes split-screen a matter of one push per viewport.
|
|
371
|
+
*/
|
|
372
|
+
void rgame_app_push_translate(rgame_app *app, float dx, float dy);
|
|
373
|
+
void rgame_app_push_rotate(rgame_app *app, float degrees, float pivot_x, float pivot_y);
|
|
374
|
+
void rgame_app_push_scale(rgame_app *app, float sx, float sy);
|
|
375
|
+
/*
|
|
376
|
+
* Returns 0 without pushing if a recording is open — see the recording section
|
|
377
|
+
* below for why a clip cannot be baked — and 1 otherwise. The other pushes
|
|
378
|
+
* always succeed as far as the caller is concerned.
|
|
379
|
+
*/
|
|
380
|
+
int rgame_app_push_clip(rgame_app *app, int x, int y, int width, int height);
|
|
381
|
+
void rgame_app_pop(rgame_app *app);
|
|
382
|
+
|
|
383
|
+
/*
|
|
384
|
+
* ---------------------------------------------------------------------------
|
|
385
|
+
* Text
|
|
386
|
+
* ---------------------------------------------------------------------------
|
|
387
|
+
*
|
|
388
|
+
* A font is a typeface at **one pixel size**, with a glyph atlas behind it.
|
|
389
|
+
* Two sizes are two fonts. Glyphs are rasterised the first time they are drawn
|
|
390
|
+
* and kept, so the cost is bounded by the character set a game uses rather than
|
|
391
|
+
* by how many strings it draws — a score that changes every frame costs nothing
|
|
392
|
+
* after the first ten digits.
|
|
393
|
+
*
|
|
394
|
+
* Like an image, a font belongs to the app whose GL context its atlas lives in,
|
|
395
|
+
* and either may be destroyed first.
|
|
396
|
+
*
|
|
397
|
+
* Measuring needs no GL and works outside a frame, which is where laying out a
|
|
398
|
+
* menu happens. Drawing, like everything else, is only valid inside `draw`.
|
|
399
|
+
*/
|
|
400
|
+
typedef struct rgame_font rgame_font;
|
|
401
|
+
|
|
402
|
+
/*
|
|
403
|
+
* Loads a TrueType font at `pixel_height`. Returns NULL on failure, writing a
|
|
404
|
+
* reason into `err` (which may be NULL). There is no font-*name* lookup and no
|
|
405
|
+
* system font database: a caller names a file. The engine ships one — see
|
|
406
|
+
* lib/rgame/fonts/ — and the Ruby binding defaults to it.
|
|
407
|
+
*/
|
|
408
|
+
rgame_font *rgame_font_load(rgame_app *app, const char *path, int pixel_height, char *err,
|
|
409
|
+
size_t err_size);
|
|
410
|
+
void rgame_font_destroy(rgame_font *font);
|
|
411
|
+
|
|
412
|
+
/* The size the font was loaded at, which is also the line height to step by for
|
|
413
|
+
* a second line. */
|
|
414
|
+
int rgame_font_height(const rgame_font *font);
|
|
415
|
+
|
|
416
|
+
/*
|
|
417
|
+
* The width a string would draw at, in pixels, kerning included. `text` is
|
|
418
|
+
* UTF-8; malformed bytes measure as one replacement character each.
|
|
419
|
+
*
|
|
420
|
+
* This and `rgame_app_draw_text` walk the same code, so a label measured and
|
|
421
|
+
* then centred lands where it was measured to.
|
|
422
|
+
*/
|
|
423
|
+
float rgame_font_measure(const rgame_font *font, const char *text, size_t length);
|
|
424
|
+
|
|
425
|
+
/*
|
|
426
|
+
* Draws one line of UTF-8 text with its top-left corner at (x, y) — the top of
|
|
427
|
+
* the line box, not the baseline, so a caller places text the way it places
|
|
428
|
+
* everything else.
|
|
429
|
+
*
|
|
430
|
+
* Newlines are not special: this draws one line. A caller wanting two splits
|
|
431
|
+
* the string and steps by `rgame_font_height`.
|
|
432
|
+
*
|
|
433
|
+
* Returns 0 without drawing if the font belongs to a different app, for the
|
|
434
|
+
* same reason images do.
|
|
435
|
+
*/
|
|
436
|
+
int rgame_app_draw_text(rgame_app *app, rgame_font *font, const char *text, size_t length,
|
|
437
|
+
float x, float y, unsigned int color, double z);
|
|
438
|
+
|
|
439
|
+
/*
|
|
440
|
+
* ---------------------------------------------------------------------------
|
|
441
|
+
* Audio
|
|
442
|
+
* ---------------------------------------------------------------------------
|
|
443
|
+
*
|
|
444
|
+
* The one subsystem that touches neither SDL nor OpenGL. It talks to the
|
|
445
|
+
* platform's sound system directly — ALSA or PulseAudio on Linux, found at
|
|
446
|
+
* runtime — so a sound belongs to an `rgame_audio`, not to an `rgame_app`, and
|
|
447
|
+
* none of the window-lifetime rules that govern images and fonts apply here.
|
|
448
|
+
*
|
|
449
|
+
* With no working sound device the engine falls back to a silent one rather
|
|
450
|
+
* than failing: a game with no audio hardware runs, quietly. That is also what
|
|
451
|
+
* lets the whole stack be tested with no sound card.
|
|
452
|
+
*
|
|
453
|
+
* Two kinds of sound:
|
|
454
|
+
*
|
|
455
|
+
* rgame_sample short, decoded up front, played many times and overlapping
|
|
456
|
+
* rgame_song long, streamed from disk, one at a time, stoppable
|
|
457
|
+
*
|
|
458
|
+
* A three-minute track decoded up front would be some forty megabytes of PCM,
|
|
459
|
+
* and a footstep re-decoded on every step would be silly. Hence two types
|
|
460
|
+
* rather than one with a flag: each has only the operations that make sense for
|
|
461
|
+
* it, so there is no `playing?` on a fire-and-forget effect to answer wrongly.
|
|
462
|
+
*
|
|
463
|
+
* Ogg Vorbis and WAV. Both are read by code the engine ships; nothing has to be
|
|
464
|
+
* installed for sound to work.
|
|
465
|
+
*/
|
|
466
|
+
typedef struct rgame_audio rgame_audio;
|
|
467
|
+
typedef struct rgame_sample rgame_sample;
|
|
468
|
+
typedef struct rgame_song rgame_song;
|
|
469
|
+
|
|
470
|
+
/*
|
|
471
|
+
* Opens the sound device. Returns NULL only if the audio engine could not be
|
|
472
|
+
* created at all, writing a reason into `err` (which may be NULL) — a machine
|
|
473
|
+
* with no sound hardware still gets a working, silent `rgame_audio`.
|
|
474
|
+
*/
|
|
475
|
+
rgame_audio *rgame_audio_create(char *err, size_t err_size);
|
|
476
|
+
void rgame_audio_destroy(rgame_audio *audio);
|
|
477
|
+
|
|
478
|
+
/*
|
|
479
|
+
* Master volume, applied to everything. 1.0 is unchanged, 0.0 is silence, and
|
|
480
|
+
* above 1.0 amplifies — which can clip, and is the caller's business. Negative
|
|
481
|
+
* values are clamped to zero.
|
|
482
|
+
*/
|
|
483
|
+
void rgame_audio_set_volume(rgame_audio *audio, float volume);
|
|
484
|
+
float rgame_audio_volume(const rgame_audio *audio);
|
|
485
|
+
|
|
486
|
+
/* Which sound system is in use — "PulseAudio", "ALSA", "Null" and so on. For
|
|
487
|
+
* diagnostics and for tests that want to know whether they are hearing
|
|
488
|
+
* anything. */
|
|
489
|
+
const char *rgame_audio_backend(const rgame_audio *audio);
|
|
490
|
+
|
|
491
|
+
/*
|
|
492
|
+
* A short sound, decoded into memory once. Playing it again while it is still
|
|
493
|
+
* sounding starts a second voice rather than restarting it, which is what makes
|
|
494
|
+
* a rapid-fire effect sound right.
|
|
495
|
+
*
|
|
496
|
+
* There is deliberately no stop and no `playing?`: a one-shot has no single
|
|
497
|
+
* voice to ask about. Volume is per sample rather than per play, and applies to
|
|
498
|
+
* voices already sounding.
|
|
499
|
+
*/
|
|
500
|
+
rgame_sample *rgame_sample_load(rgame_audio *audio, const char *path, char *err,
|
|
501
|
+
size_t err_size);
|
|
502
|
+
void rgame_sample_destroy(rgame_sample *sample);
|
|
503
|
+
void rgame_sample_play(rgame_sample *sample);
|
|
504
|
+
void rgame_sample_set_volume(rgame_sample *sample, float volume);
|
|
505
|
+
float rgame_sample_volume(const rgame_sample *sample);
|
|
506
|
+
|
|
507
|
+
/*
|
|
508
|
+
* A long sound, streamed from disk. One voice, so playing it again while it
|
|
509
|
+
* sounds restarts it rather than layering.
|
|
510
|
+
*
|
|
511
|
+
* "Only one song at a time" is *not* enforced here — that is a policy a game
|
|
512
|
+
* decides, and the Ruby layer owns it.
|
|
513
|
+
*/
|
|
514
|
+
rgame_song *rgame_song_load(rgame_audio *audio, const char *path, char *err, size_t err_size);
|
|
515
|
+
void rgame_song_destroy(rgame_song *song);
|
|
516
|
+
void rgame_song_play(rgame_song *song, int looping);
|
|
517
|
+
void rgame_song_stop(rgame_song *song);
|
|
518
|
+
int rgame_song_playing(const rgame_song *song);
|
|
519
|
+
/* Whether the last `play` asked for looping. */
|
|
520
|
+
int rgame_song_looping(const rgame_song *song);
|
|
521
|
+
void rgame_song_set_volume(rgame_song *song, float volume);
|
|
522
|
+
float rgame_song_volume(const rgame_song *song);
|
|
523
|
+
|
|
524
|
+
/*
|
|
525
|
+
* ---------------------------------------------------------------------------
|
|
526
|
+
* Recordings: drawing baked once and replayed cheaply
|
|
527
|
+
* ---------------------------------------------------------------------------
|
|
528
|
+
*
|
|
529
|
+
* A tile layer is a couple of thousand quads that have not changed since the
|
|
530
|
+
* level loaded. Between `rgame_app_begin_record` and `rgame_app_end_record`,
|
|
531
|
+
* draw calls are captured instead of added to the frame; what comes back is the
|
|
532
|
+
* finished, already-batched geometry, which replays as one call per texture
|
|
533
|
+
* however many draws went into it.
|
|
534
|
+
*
|
|
535
|
+
* Recording happens *inside* a frame (there is drawing to capture, after all),
|
|
536
|
+
* and nests nowhere: begin_record returns 0 if the app is not drawing or is
|
|
537
|
+
* already recording.
|
|
538
|
+
*
|
|
539
|
+
* Transforms inside the block are baked in; the transform in effect at replay
|
|
540
|
+
* is applied on top, which is what lets a baked layer scroll under a camera.
|
|
541
|
+
* Clips are not baked — clipping happens when pixels are rasterised, so a clip
|
|
542
|
+
* rectangle captured at one place on screen would be wrong everywhere else the
|
|
543
|
+
* recording is drawn. `rgame_app_push_clip` refuses while recording; clip the
|
|
544
|
+
* replay instead.
|
|
545
|
+
*/
|
|
546
|
+
typedef struct rgame_recording rgame_recording;
|
|
547
|
+
|
|
548
|
+
/* Starts capturing. Returns 1 on success, 0 if not drawing or already
|
|
549
|
+
* recording. */
|
|
550
|
+
int rgame_app_begin_record(rgame_app *app);
|
|
551
|
+
|
|
552
|
+
/*
|
|
553
|
+
* Ends capturing and returns the baked result, which the caller owns and must
|
|
554
|
+
* pass to `rgame_recording_free`. Returns NULL if no recording was open or if
|
|
555
|
+
* memory ran out.
|
|
556
|
+
*/
|
|
557
|
+
rgame_recording *rgame_app_end_record(rgame_app *app);
|
|
558
|
+
|
|
559
|
+
/* Ends capturing and throws the result away — for unwinding when whatever was
|
|
560
|
+
* being recorded failed part-way through. */
|
|
561
|
+
void rgame_app_cancel_record(rgame_app *app);
|
|
562
|
+
|
|
563
|
+
void rgame_recording_free(rgame_recording *recording);
|
|
564
|
+
|
|
565
|
+
/*
|
|
566
|
+
* Replays a recording with its origin at (x, y), at `z`, tinted by `color`
|
|
567
|
+
* (0xFFFFFFFF leaves the recorded colours alone). Unlike the layer this
|
|
568
|
+
* replaces, a recording is not limited to drawing white.
|
|
569
|
+
*/
|
|
570
|
+
void rgame_app_draw_recording(rgame_app *app, const rgame_recording *recording, float x,
|
|
571
|
+
float y, unsigned int color, double z);
|
|
572
|
+
|
|
573
|
+
#ifdef __cplusplus
|
|
574
|
+
}
|
|
575
|
+
#endif
|
|
576
|
+
|
|
577
|
+
#endif /* RGAME_CORE_H */
|