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
data/docs/api/game.md
CHANGED
|
@@ -7,8 +7,8 @@ engine.
|
|
|
7
7
|
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
8
8
|
require 'rgame/game'
|
|
9
9
|
|
|
10
|
-
class HelloScene < Engine::Node2D
|
|
11
|
-
def on_draw(renderer) = renderer.text('Hello world!', 250, 200)
|
|
10
|
+
class HelloScene < RGame::Engine::Node2D
|
|
11
|
+
def on_draw(renderer, _view) = renderer.text('Hello world!', 250, 200)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
RGame::Game.new(root: HelloScene.new, caption: 'Hello').start
|
|
@@ -20,19 +20,24 @@ sound device, the input mapper, the debug overlay — and drives the root node.
|
|
|
20
20
|
|
|
21
21
|
```ruby
|
|
22
22
|
RGame::Game.new(root:, width: 640, height: 480, caption: 'RGame',
|
|
23
|
-
media_root: 'media',
|
|
23
|
+
media_root: 'media', input_map: nil, device: Controls::KEYBOARD)
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
| Reader | |
|
|
27
27
|
|---|---|
|
|
28
28
|
| `root` | the node tree |
|
|
29
29
|
| `renderer` | what scenes draw through |
|
|
30
|
-
| `
|
|
30
|
+
| `players` | who is playing: their devices, bindings and cameras |
|
|
31
31
|
| `assets`, `audio`, `media_root`, `width`, `height`, `fps` | inherited from [App](app.md) |
|
|
32
32
|
|
|
33
33
|
`start` brings the tree live — it hands the game to the root as its `context`,
|
|
34
|
-
calls `enter_tree`, and runs the loop until the window closes. `
|
|
35
|
-
|
|
34
|
+
calls `enter_tree`, and runs the loop until the window closes. `F1` toggles the
|
|
35
|
+
debug overlay and `F2` quits.
|
|
36
|
+
|
|
37
|
+
**Both development keys are function keys, and `Esc` is deliberately left
|
|
38
|
+
alone.** Escape is the button a player expects to back out of a menu, so it
|
|
39
|
+
belongs to the game rather than to the engine's debug shortcuts — binding it
|
|
40
|
+
here would take it away from every game built on this one.
|
|
36
41
|
|
|
37
42
|
## Why this class exists at all
|
|
38
43
|
|
|
@@ -57,19 +62,35 @@ sheet = node.root.context.assets.sheet('player.json')
|
|
|
57
62
|
|
|
58
63
|
## Input
|
|
59
64
|
|
|
60
|
-
`
|
|
61
|
-
[
|
|
65
|
+
`input_map:` names the actions a game has, in terms of physical ids from
|
|
66
|
+
[`RGame::Util::Controls`](input.md):
|
|
62
67
|
|
|
63
68
|
```ruby
|
|
69
|
+
Controls = RGame::Util::Controls
|
|
70
|
+
|
|
64
71
|
RGame::Game.new(
|
|
65
72
|
root: Root.new,
|
|
66
|
-
|
|
67
|
-
move_x: { axis:
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
input_map: RGame::Engine::InputMap.new(
|
|
74
|
+
move_x: { axis: [Controls::KEY_LEFT, Controls::KEY_RIGHT], # -1.0 .. 1.0
|
|
75
|
+
stick: Controls::AXIS_LEFT_X },
|
|
76
|
+
fire: { buttons: [Controls::KEY_SPACE, Controls::PAD_A] } # held / pressed / released
|
|
77
|
+
)
|
|
70
78
|
)
|
|
71
79
|
```
|
|
72
80
|
|
|
81
|
+
Pass nothing and you get [`InputMap.default`](input.md): eight-way `move_x` /
|
|
82
|
+
`move_y` on the arrows or the left stick, plus `fire`. Either way the map is
|
|
83
|
+
merged over the universal UI set, so `ui_confirm` and `ui_cancel` work without
|
|
84
|
+
being declared.
|
|
85
|
+
|
|
86
|
+
`device:` picks what drives player one — the keyboard by default, or
|
|
87
|
+
`Controls.gamepad(slot)` for a controller.
|
|
88
|
+
|
|
89
|
+
`players:` is how many seats the game has (default 1). Extra seats start empty
|
|
90
|
+
and fill when somebody picks up a controller and presses confirm; an empty seat
|
|
91
|
+
draws no viewport, so a two-seat game played by one person is an ordinary
|
|
92
|
+
full-screen game. See [Players, seats and joining](input.md#players-seats-and-joining).
|
|
93
|
+
|
|
73
94
|
A scene reads the resulting snapshot in `on_control(actions)` — `actions.axis(:move_x)`,
|
|
74
95
|
`actions.pressed?(:fire)` — and never sees a key.
|
|
75
96
|
|
|
@@ -92,7 +113,7 @@ behaviour around the tree rather than replacing the shell.
|
|
|
92
113
|
```ruby
|
|
93
114
|
class MyGame < RGame::Game
|
|
94
115
|
def button_down(id)
|
|
95
|
-
super # keeps
|
|
116
|
+
super # keeps F1 and F2 working
|
|
96
117
|
@paused = !@paused if id == RGame::Util::Controls::KEY_SPACE
|
|
97
118
|
end
|
|
98
119
|
end
|
data/docs/api/input.md
CHANGED
|
@@ -1,85 +1,240 @@
|
|
|
1
1
|
# Input
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Four pieces, in two layers:
|
|
4
4
|
|
|
5
5
|
- **`RGame::Util::Controls`** — the vocabulary: which number means "the left
|
|
6
6
|
arrow key", "the A button", "player 2's controller". Plain values, usable
|
|
7
7
|
without loading any graphics library.
|
|
8
|
-
- **`RGame::Core::Input`** —
|
|
9
|
-
|
|
10
|
-
- **`RGame::
|
|
8
|
+
- **`RGame::Core::Input`** — the raw query: is *this id* active on *this
|
|
9
|
+
device*.
|
|
10
|
+
- **`RGame::Engine::InputMap`** — what those ids *mean*: one table per player,
|
|
11
|
+
mapping a game's actions onto physical ids.
|
|
12
|
+
- **`RGame::Engine::ActionMapper`** — polls one player's device through their
|
|
13
|
+
map once per tick and produces an `Actions` snapshot.
|
|
14
|
+
|
|
15
|
+
Plus **`RGame::Core::Gamepad`**, a readout of which controllers are plugged in,
|
|
16
|
+
for menus.
|
|
11
17
|
|
|
12
18
|
There is no mouse support, by design.
|
|
13
19
|
|
|
14
|
-
##
|
|
20
|
+
## Which layer do I want?
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
input
|
|
22
|
+
Almost always the engine layer. A game declares its actions, reads
|
|
23
|
+
`actions.held?(:fire)`, and never names a scancode outside its input map.
|
|
24
|
+
`RGame::Core::Input` is what the mapper polls; you reach for it directly only
|
|
25
|
+
when writing against `RGame::Core` alone, with no scene graph.
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
```ruby
|
|
28
|
+
RGame::Game.new(
|
|
29
|
+
root: MyRoot.new,
|
|
30
|
+
input_map: RGame::Engine::InputMap.new(
|
|
31
|
+
fire: { buttons: [Controls::KEY_SPACE, Controls::PAD_A] }
|
|
32
|
+
)
|
|
33
|
+
)
|
|
22
34
|
```
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
## `RGame::Engine::InputMap`
|
|
37
|
+
|
|
38
|
+
One entry per action, naming physical ids directly. **This is the single table a
|
|
39
|
+
rebinding screen edits.**
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
Controls = RGame::Util::Controls
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
map = RGame::Engine::InputMap.new(
|
|
45
|
+
turn: { axis: [Controls::KEY_LEFT, Controls::KEY_RIGHT], stick: Controls::AXIS_LEFT_X },
|
|
46
|
+
thrust: { axis: [Controls::KEY_DOWN, Controls::KEY_UP], stick: Controls::AXIS_TRIGGER_RIGHT },
|
|
47
|
+
fire: { buttons: [Controls::KEY_SPACE, Controls::PAD_A] }
|
|
48
|
+
)
|
|
49
|
+
```
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
table, so the same game code works for a keyboard player and a controller
|
|
34
|
-
player:
|
|
51
|
+
Three kinds of source, and one action may combine them:
|
|
35
52
|
|
|
36
|
-
|
|
|
53
|
+
| Key | Read with | Meaning |
|
|
37
54
|
|---|---|---|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
55
|
+
| `buttons:` | `held?` / `pressed?` / `released?` | down if **any** listed id is down |
|
|
56
|
+
| `axis:` | `axis` | `[negative_id, positive_id]` — a digital axis from two buttons |
|
|
57
|
+
| `stick:` | `axis` | an analog axis id, for a real stick or a trigger |
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
When an action binds both `axis:` and `stick:`, **the larger deflection wins**.
|
|
60
|
+
That needs no per-device branching: a keyboard reads `0.0` for every axis and a
|
|
61
|
+
stick reads `false` for every key, so whichever device a player is on, the other
|
|
62
|
+
source contributes nothing.
|
|
43
63
|
|
|
44
|
-
###
|
|
64
|
+
### One table serves every device
|
|
45
65
|
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
Listing a key and a pad button in the same entry is safe, because **a device
|
|
67
|
+
only answers for its own kind of input** — asking a gamepad about a keyboard
|
|
68
|
+
scancode is `false`, never the keyboard's answer. So `fire` can be "Space or A",
|
|
69
|
+
and each player's device picks out the half that applies to it.
|
|
70
|
+
|
|
71
|
+
### A stick's sign is the device's
|
|
72
|
+
|
|
73
|
+
`AXIS_LEFT_Y` is positive **downwards**, like screen coordinates. An action that
|
|
74
|
+
wants the opposite ("thrust", "climb") negates at the call site or binds a
|
|
75
|
+
trigger instead — the map stays declarative rather than growing an inversion
|
|
76
|
+
flag every reader would have to check for.
|
|
77
|
+
|
|
78
|
+
### The universal UI set
|
|
79
|
+
|
|
80
|
+
Every map is merged over a universal set, so these exist whether or not a game
|
|
81
|
+
declares them:
|
|
82
|
+
|
|
83
|
+
`ui_up`, `ui_down`, `ui_left`, `ui_right`, `ui_confirm`, `ui_cancel`
|
|
84
|
+
|
|
85
|
+
Keyboard navigation and menus rely on them being there for **every** player. They
|
|
86
|
+
are prefixed so a game is free to use `:up` for something of its own, and a game
|
|
87
|
+
that wants different bindings just declares one:
|
|
48
88
|
|
|
49
89
|
```ruby
|
|
50
|
-
|
|
90
|
+
InputMap.new(ui_confirm: { buttons: [Controls::PAD_X] })
|
|
91
|
+
```
|
|
51
92
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
`ui_cancel` is Escape — which is why `RGame::Game`'s quit key is `F2`. The button
|
|
94
|
+
a player expects to back out of a menu belongs to the menu.
|
|
95
|
+
|
|
96
|
+
### Defaults and rebinding
|
|
97
|
+
|
|
98
|
+
`InputMap.default` is the UI set plus eight-way movement (`move_x`, `move_y`) on
|
|
99
|
+
the arrows or the left stick, and `fire`. A game wanting exactly that passes no
|
|
100
|
+
`input_map:` at all.
|
|
101
|
+
|
|
102
|
+
`#merge` returns a copy with some actions replaced, which is how a config screen
|
|
103
|
+
rebinds one without restating the rest:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
map = RGame::Engine::InputMap.default.merge(fire: { buttons: [Controls::KEY_RETURN] })
|
|
56
107
|
```
|
|
57
108
|
|
|
58
|
-
A
|
|
59
|
-
|
|
60
|
-
|
|
109
|
+
A malformed entry raises at construction — an unknown source key, an entry with
|
|
110
|
+
no source, an empty button list, an axis that is not a pair. That is deliberate:
|
|
111
|
+
the alternative is an action that reads as "never pressed" for the rest of the
|
|
112
|
+
program, discovered as a frame nobody can move in.
|
|
61
113
|
|
|
62
|
-
|
|
114
|
+
## `RGame::Engine::ActionMapper`
|
|
63
115
|
|
|
64
|
-
|
|
65
|
-
|
|
116
|
+
One per player. It polls that player's device through their map and returns the
|
|
117
|
+
`Actions` snapshot game logic reads.
|
|
66
118
|
|
|
67
119
|
```ruby
|
|
68
|
-
|
|
69
|
-
|
|
120
|
+
mapper = RGame::Engine::ActionMapper.new(map, device: Controls.gamepad(0))
|
|
121
|
+
actions = mapper.poll(input)
|
|
70
122
|
|
|
71
|
-
|
|
123
|
+
actions.held?(:fire) # is it down now
|
|
124
|
+
actions.pressed?(:fire) # did it go down this tick
|
|
125
|
+
actions.released?(:fire) # did it come up this tick
|
|
126
|
+
actions.axis(:turn) # -1.0..1.0
|
|
72
127
|
```
|
|
73
128
|
|
|
74
|
-
|
|
129
|
+
**The device is what makes two players work.** Every query carries it, so two
|
|
130
|
+
mappers over the *same* map read two different controllers, and each keeps its
|
|
131
|
+
own previous-frame state so their edge queries are independent. Reassign
|
|
132
|
+
`mapper.device` to follow a hot-plug.
|
|
133
|
+
|
|
134
|
+
`dead_zone:` (default `0.15`) ignores a resting stick, which genuinely reports
|
|
135
|
+
small non-zero values. It **rescales** rather than merely cutting off, so a stick
|
|
136
|
+
leaving the dead zone ramps from zero instead of jumping to `0.15`.
|
|
137
|
+
|
|
138
|
+
`RGame::Game` builds one of these for you and polls it once per tick; a game
|
|
139
|
+
normally sees only the `Actions` handed to `control`.
|
|
140
|
+
|
|
141
|
+
## Players, seats and joining
|
|
142
|
+
|
|
143
|
+
`RGame::Engine::Players` is a root-scoped system holding who is playing. Each
|
|
144
|
+
`RGame::Engine::Player` owns a device, an `InputMap`, a camera and a UI root —
|
|
145
|
+
the action *names* are the game's and shared, the buttons behind them are not.
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
RGame::Game.new(root: MyRoot.new, players: 2)
|
|
149
|
+
```
|
|
75
150
|
|
|
76
|
-
|
|
151
|
+
`players:` is how many **seats** the game has, and therefore the most people who
|
|
152
|
+
can play it. Player 0 starts on the keyboard; the rest start empty. An empty
|
|
153
|
+
seat draws no viewport, so a two-seat game with nobody in the second one is an
|
|
154
|
+
ordinary full-screen single-player game.
|
|
155
|
+
|
|
156
|
+
### A device is seated when someone uses it
|
|
157
|
+
|
|
158
|
+
Not when it is plugged in. Plugging a controller in says something about
|
|
159
|
+
hardware; seating a player creates a camera, a viewport and a screen split, and
|
|
160
|
+
that follows a statement of intent — a **`ui_confirm` press** on the device.
|
|
161
|
+
|
|
162
|
+
One action rather than "any input", because a stick resting slightly off centre
|
|
163
|
+
must never seat a player. An edge rather than held, so one press does one thing.
|
|
164
|
+
It is read through the map of whoever would receive the device, so rebinding
|
|
165
|
+
`ui_confirm` rebinds "press to join" with it.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
players = node.system(RGame::Engine::Players)
|
|
169
|
+
|
|
170
|
+
players.on_unassigned_input = :join # :join | :takeover | :ignore
|
|
171
|
+
players.accepting_joins = false # temporarily refuse
|
|
172
|
+
players.on_joined { |player| spawn(player) }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Policy | A press on a device nobody holds | Default when |
|
|
77
176
|
|---|---|---|
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
177
|
+
| `:join` | fills the next free seat | there is more than one seat |
|
|
178
|
+
| `:takeover` | becomes the **primary** player's device | there is one seat |
|
|
179
|
+
| `:ignore` | nothing; the game calls `players.seat(device)` itself | — |
|
|
180
|
+
|
|
181
|
+
`:takeover` is single-player's answer: one person already playing who picks up a
|
|
182
|
+
controller is not a second person arriving. Their keyboard becomes unassigned,
|
|
183
|
+
so using it again takes them back — last device used wins. And if their
|
|
184
|
+
controller is unplugged they fall back to the keyboard rather than the game
|
|
185
|
+
going dead in their hands.
|
|
186
|
+
|
|
187
|
+
`accepting_joins = false` refuses both, which is what a cutscene or a mid-round
|
|
188
|
+
lockout wants.
|
|
189
|
+
|
|
190
|
+
`on_joined` fires with the player who got the device, which is how a scene
|
|
191
|
+
spawns their character without polling for one. See `examples/15_tiled_world`.
|
|
192
|
+
|
|
193
|
+
## `RGame::Core::Input`
|
|
194
|
+
|
|
195
|
+
The raw query, and deliberately nothing more.
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
input = RGame::Core::Input.new(app)
|
|
199
|
+
|
|
200
|
+
input.down?(Controls::KEY_SPACE) # keyboard
|
|
201
|
+
input.down?(Controls::PAD_A, device: Controls.gamepad(0)) # player 1's pad
|
|
202
|
+
input.axis(Controls::AXIS_LEFT_X, device: Controls.gamepad(0))
|
|
203
|
+
```
|
|
81
204
|
|
|
82
|
-
|
|
205
|
+
`down?` and `axis` read a snapshot the engine takes **once per frame**, when it
|
|
206
|
+
pumps events. That is what makes them safe to call from `update`: a frame can run
|
|
207
|
+
several simulation ticks, and every tick sees the same answer. Reading hardware
|
|
208
|
+
directly would make a held key behave differently depending on how slow the
|
|
209
|
+
previous frame was.
|
|
210
|
+
|
|
211
|
+
Ids are numbers, and they cross into C, so passing anything else raises
|
|
212
|
+
`TypeError`. No dead zone is applied here — this is the hardware's answer.
|
|
213
|
+
|
|
214
|
+
### It used to hold the binding tables
|
|
215
|
+
|
|
216
|
+
It took `down?(:fire)` and resolved `:fire` through one of three tables passed to
|
|
217
|
+
its constructor. Those tables are gone. Binding moved up to `InputMap` for two
|
|
218
|
+
reasons: a rebinding screen has to be able to edit the table, and the engine
|
|
219
|
+
layer may not name `RGame::Core` at all; and with a player per device, the table
|
|
220
|
+
is a per-player value rather than a property of the one object that talks to the
|
|
221
|
+
hardware.
|
|
222
|
+
|
|
223
|
+
### Devices
|
|
224
|
+
|
|
225
|
+
Device 0 is the keyboard, and it is the default — so single-player code never
|
|
226
|
+
mentions devices at all. Controllers follow, one per player slot:
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
Controls::KEYBOARD # => 0
|
|
230
|
+
Controls.gamepad(0) # the first controller
|
|
231
|
+
Controls.gamepad(1) # the second
|
|
232
|
+
Controls::MAX_GAMEPADS # how many slots exist
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
A device only answers for its own kind of input. Asking a gamepad about a
|
|
236
|
+
keyboard key is `false`, never the keyboard's answer — otherwise player two's pad
|
|
237
|
+
would echo player one. The keyboard has no axes, so `axis` on it is `0.0`.
|
|
83
238
|
|
|
84
239
|
## `RGame::Util::Controls`
|
|
85
240
|
|
|
@@ -87,14 +242,39 @@ The id vocabulary. Available from `require 'rgame'` **and** from
|
|
|
87
242
|
`require 'rgame/core'`, because these are plain integers with nothing behind
|
|
88
243
|
them — a game's configuration screen can name a key without pulling in a window.
|
|
89
244
|
|
|
90
|
-
**Keys** —
|
|
91
|
-
|
|
245
|
+
**Keys** — what a Western keyboard can be relied on to have, 81 of them:
|
|
246
|
+
|
|
247
|
+
| | |
|
|
248
|
+
|---|---|
|
|
249
|
+
| Letters | `KEY_A` … `KEY_Z` |
|
|
250
|
+
| Digits | `KEY_1` … `KEY_9`, `KEY_0` |
|
|
251
|
+
| Whitespace and editing | `KEY_RETURN`, `KEY_ESCAPE`, `KEY_BACKSPACE`, `KEY_TAB`, `KEY_SPACE` |
|
|
252
|
+
| Punctuation | `KEY_MINUS`, `KEY_EQUALS`, `KEY_LEFTBRACKET`, `KEY_RIGHTBRACKET`, `KEY_BACKSLASH`, `KEY_SEMICOLON`, `KEY_APOSTROPHE`, `KEY_GRAVE`, `KEY_COMMA`, `KEY_PERIOD`, `KEY_SLASH` |
|
|
253
|
+
| Function row | `KEY_CAPSLOCK`, `KEY_F1` … `KEY_F12` |
|
|
254
|
+
| Navigation | `KEY_INSERT`, `KEY_HOME`, `KEY_PAGEUP`, `KEY_DELETE`, `KEY_END`, `KEY_PAGEDOWN` |
|
|
255
|
+
| Arrows | `KEY_LEFT`, `KEY_RIGHT`, `KEY_UP`, `KEY_DOWN` |
|
|
256
|
+
| Modifiers | `KEY_LCTRL`, `KEY_LSHIFT`, `KEY_LALT`, `KEY_RCTRL`, `KEY_RSHIFT`, `KEY_RALT` |
|
|
257
|
+
|
|
258
|
+
**A scancode is a position, not a letter.** `KEY_A` is the key marked A on a
|
|
259
|
+
QWERTY board and Q on AZERTY — which is what you want for `WASD` movement, and
|
|
260
|
+
what a rebinding screen has to explain to the player. The engine only ever
|
|
261
|
+
compares numbers.
|
|
262
|
+
|
|
263
|
+
**Deliberately absent**: the numpad (most laptops have none), the GUI key
|
|
264
|
+
(Windows on a PC, Command on a Mac), the print-screen cluster, and anything
|
|
265
|
+
whose position depends on the layout. Adding one is a `#define` in
|
|
266
|
+
`ext/rgame_core/include/rgame/core.h`, a `_Static_assert` against the SDL
|
|
267
|
+
scancode, and a constant here — and the spec below checks all three agree.
|
|
92
268
|
|
|
93
269
|
**Gamepad buttons** — `PAD_A`, `PAD_B`, `PAD_X`, `PAD_Y`, `PAD_BACK`,
|
|
94
270
|
`PAD_GUIDE`, `PAD_START`, `PAD_LEFT_STICK`, `PAD_RIGHT_STICK`,
|
|
95
271
|
`PAD_LEFT_SHOULDER`, `PAD_RIGHT_SHOULDER`, `PAD_DPAD_UP`, `PAD_DPAD_DOWN`,
|
|
96
272
|
`PAD_DPAD_LEFT`, `PAD_DPAD_RIGHT`.
|
|
97
273
|
|
|
274
|
+
Plus the ones only some hardware has, which read as never pressed on a pad
|
|
275
|
+
without them: `PAD_MISC1` (share/capture/microphone), `PAD_PADDLE1` …
|
|
276
|
+
`PAD_PADDLE4` (Xbox Elite), `PAD_TOUCHPAD` (PS4/PS5).
|
|
277
|
+
|
|
98
278
|
**Axes** — `AXIS_LEFT_X`, `AXIS_LEFT_Y`, `AXIS_RIGHT_X`, `AXIS_RIGHT_Y`,
|
|
99
279
|
`AXIS_TRIGGER_LEFT`, `AXIS_TRIGGER_RIGHT`. Sticks read −1.0 to 1.0 with **y
|
|
100
280
|
positive downwards**; triggers read 0.0 to 1.0. No dead zone is applied — where
|
|
@@ -104,7 +284,8 @@ non-zero values.
|
|
|
104
284
|
**Devices** — `KEYBOARD`, `GAMEPAD_FIRST`, `MAX_GAMEPADS`, and
|
|
105
285
|
`Controls.gamepad(slot)`.
|
|
106
286
|
|
|
107
|
-
**
|
|
287
|
+
This module is the **vocabulary only**. It carries no binding tables — what an id
|
|
288
|
+
*means* is `RGame::Engine::InputMap`, one per player.
|
|
108
289
|
|
|
109
290
|
Buttons and keys share one numbering, partitioned into ranges, so a single
|
|
110
291
|
"is it held" query serves every device. You never need the numbers themselves —
|
|
@@ -170,7 +351,7 @@ class MyGame < RGame::Core::App
|
|
|
170
351
|
end
|
|
171
352
|
|
|
172
353
|
def update(_dt)
|
|
173
|
-
@moving_left = @input.down?(
|
|
354
|
+
@moving_left = @input.down?(RGame::Util::Controls::KEY_LEFT, device: @device)
|
|
174
355
|
end
|
|
175
356
|
end
|
|
176
357
|
```
|