rgame 0.4.0-x86_64-linux-gnu
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/CHANGELOG.md +251 -0
- data/LICENSE +26 -0
- data/README.md +325 -0
- data/docs/api/README.md +224 -0
- data/docs/api/app.md +249 -0
- data/docs/api/assets.md +463 -0
- data/docs/api/audio.md +249 -0
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1112 -0
- data/docs/api/drawing.md +398 -0
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +219 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +476 -0
- data/docs/api/internals.md +289 -0
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +539 -0
- data/docs/api/signals.md +147 -0
- data/docs/api/systems.md +231 -0
- data/docs/api/text.md +133 -0
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +493 -0
- data/docs/api/ui.md +927 -0
- data/docs/api/values.md +316 -0
- data/examples/assets/README.md +322 -0
- data/examples/assets/blip.ogg +0 -0
- data/examples/assets/glyphs.json +5 -0
- data/examples/assets/glyphs.png +0 -0
- data/examples/assets/hero.json +12 -0
- data/examples/assets/hero.png +0 -0
- data/examples/assets/icons.json +13 -0
- data/examples/assets/icons.png +0 -0
- data/examples/assets/music.ogg +0 -0
- data/examples/assets/skills.json +10 -0
- data/examples/assets/skills.png +0 -0
- data/examples/assets/tileset.png +0 -0
- data/examples/assets/tileset.tsx +65 -0
- data/examples/assets/town.tmx +26 -0
- data/examples/assets/ui.json +11 -0
- data/examples/assets/ui.png +0 -0
- data/examples/collision/locales/en.yml +8 -0
- data/examples/collision/main.rb +316 -0
- data/examples/collision_tiles/locales/en.yml +9 -0
- data/examples/collision_tiles/main.rb +274 -0
- data/examples/fullscreen/locales/en.yml +10 -0
- data/examples/fullscreen/main.rb +216 -0
- data/examples/game_menu/locales/en.yml +8 -0
- data/examples/game_menu/main.rb +170 -0
- data/examples/input_glyphs/locales/en.yml +14 -0
- data/examples/input_glyphs/main.rb +213 -0
- data/examples/jump_topdown/locales/en.yml +9 -0
- data/examples/jump_topdown/main.rb +178 -0
- data/examples/localization/locales/de.yml +12 -0
- data/examples/localization/locales/en.yml +13 -0
- data/examples/localization/main.rb +158 -0
- data/examples/menu_navigation/locales/en.yml +23 -0
- data/examples/menu_navigation/main.rb +365 -0
- data/examples/music/locales/en.yml +7 -0
- data/examples/music/main.rb +134 -0
- data/examples/pathfinding/locales/en.yml +17 -0
- data/examples/pathfinding/main.rb +298 -0
- data/examples/pooling/locales/en.yml +7 -0
- data/examples/pooling/main.rb +259 -0
- data/examples/quick_wheel/locales/en.yml +16 -0
- data/examples/quick_wheel/main.rb +184 -0
- data/examples/radial_menu/locales/en.yml +16 -0
- data/examples/radial_menu/main.rb +184 -0
- data/examples/save_load/locales/en.yml +11 -0
- data/examples/save_load/main.rb +207 -0
- data/examples/save_load_ids/locales/en.yml +11 -0
- data/examples/save_load_ids/main.rb +322 -0
- data/examples/scroll_map/locales/en.yml +4 -0
- data/examples/scroll_map/main.rb +140 -0
- data/examples/signals/locales/en.yml +6 -0
- data/examples/signals/main.rb +278 -0
- data/examples/skill_bar/locales/en.yml +14 -0
- data/examples/skill_bar/main.rb +159 -0
- data/examples/sound/locales/en.yml +6 -0
- data/examples/sound/main.rb +122 -0
- data/examples/split_screen/locales/en.yml +9 -0
- data/examples/split_screen/main.rb +304 -0
- data/examples/sprite/locales/en.yml +8 -0
- data/examples/sprite/main.rb +180 -0
- data/examples/timer/locales/en.yml +12 -0
- data/examples/timer/main.rb +273 -0
- data/examples/velocity/locales/en.yml +6 -0
- data/examples/velocity/main.rb +196 -0
- data/examples/walk/locales/en.yml +4 -0
- data/examples/walk/main.rb +99 -0
- data/exe/rgame +9 -0
- data/lib/rgame/boot.rb +3 -0
- data/lib/rgame/cli/new_project.rb +139 -0
- data/lib/rgame/cli/templates/Gemfile.tt +23 -0
- data/lib/rgame/cli/templates/README.md.tt +93 -0
- data/lib/rgame/cli/templates/Rakefile.tt +9 -0
- data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
- data/lib/rgame/cli/templates/game.rb.tt +23 -0
- data/lib/rgame/cli/templates/gitignore.tt +12 -0
- data/lib/rgame/cli/templates/main.rb.tt +11 -0
- data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
- data/lib/rgame/cli/templates/rspec.tt +2 -0
- data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
- data/lib/rgame/cli/templates/ruby-version.tt +1 -0
- data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
- data/lib/rgame/cli.rb +66 -0
- data/lib/rgame/core/app.rb +46 -0
- data/lib/rgame/core/asset_manager.rb +206 -0
- data/lib/rgame/core/audio.rb +145 -0
- data/lib/rgame/core/font.rb +46 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +71 -0
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +142 -0
- data/lib/rgame/core/recording.rb +54 -0
- data/lib/rgame/core/renderer.rb +354 -0
- data/lib/rgame/core/sprite_sheet.rb +105 -0
- data/lib/rgame/core/tile_map_renderer.rb +172 -0
- data/lib/rgame/core/ui_atlas.rb +101 -0
- data/lib/rgame/core/virtual_gamepad.rb +26 -0
- data/lib/rgame/core.rb +18 -0
- data/lib/rgame/core_ext.so +0 -0
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +50 -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 +59 -0
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +78 -0
- data/lib/rgame/engine/circle_collider.rb +34 -0
- data/lib/rgame/engine/collision_box.rb +59 -0
- data/lib/rgame/engine/collision_system.rb +132 -0
- data/lib/rgame/engine/component.rb +64 -0
- data/lib/rgame/engine/components/action_trigger.rb +40 -0
- data/lib/rgame/engine/components/animated_sprite.rb +71 -0
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +45 -0
- data/lib/rgame/engine/components/character_body.rb +50 -0
- data/lib/rgame/engine/components/circle_collider.rb +80 -0
- data/lib/rgame/engine/components/collision_world.rb +231 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +42 -0
- data/lib/rgame/engine/components/feet_collider.rb +61 -0
- data/lib/rgame/engine/components/hop.rb +76 -0
- data/lib/rgame/engine/components/identity.rb +73 -0
- data/lib/rgame/engine/components/mover.rb +285 -0
- data/lib/rgame/engine/components/navigator.rb +145 -0
- data/lib/rgame/engine/components/path_follow.rb +176 -0
- data/lib/rgame/engine/components/player_controller.rb +27 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +49 -0
- data/lib/rgame/engine/components/sprite.rb +47 -0
- data/lib/rgame/engine/components/targeting.rb +52 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +109 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +43 -0
- data/lib/rgame/engine/components/wander_controller.rb +64 -0
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +45 -0
- data/lib/rgame/engine/debug_overlay.rb +111 -0
- data/lib/rgame/engine/i18n/plural.rb +45 -0
- data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
- data/lib/rgame/engine/i18n/template.rb +59 -0
- data/lib/rgame/engine/i18n.rb +322 -0
- data/lib/rgame/engine/input/action_mapper.rb +100 -0
- data/lib/rgame/engine/input/actions.rb +92 -0
- data/lib/rgame/engine/input/input_map.rb +196 -0
- data/lib/rgame/engine/layout.rb +82 -0
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +555 -0
- data/lib/rgame/engine/path.rb +76 -0
- data/lib/rgame/engine/player.rb +69 -0
- data/lib/rgame/engine/player_layer.rb +70 -0
- data/lib/rgame/engine/players.rb +205 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +87 -0
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +116 -0
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +147 -0
- data/lib/rgame/engine/tile_map_layer.rb +82 -0
- data/lib/rgame/engine/tileset.rb +99 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine/ui/button.rb +248 -0
- data/lib/rgame/engine/ui/column.rb +20 -0
- data/lib/rgame/engine/ui/icon_button.rb +93 -0
- data/lib/rgame/engine/ui/menu.rb +290 -0
- data/lib/rgame/engine/ui/navigation.rb +57 -0
- data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
- data/lib/rgame/engine/ui/option_button.rb +163 -0
- data/lib/rgame/engine/ui/panel_button.rb +32 -0
- data/lib/rgame/engine/ui/panel_menu.rb +36 -0
- data/lib/rgame/engine/ui/pointing.rb +146 -0
- data/lib/rgame/engine/ui/radial_menu.rb +85 -0
- data/lib/rgame/engine/ui/ring.rb +55 -0
- data/lib/rgame/engine/ui/row.rb +21 -0
- data/lib/rgame/engine/ui/shape_style.rb +102 -0
- data/lib/rgame/engine/ui/stack.rb +58 -0
- data/lib/rgame/engine/ui/stepping.rb +93 -0
- data/lib/rgame/engine/ui/text_button.rb +59 -0
- data/lib/rgame/engine/view.rb +76 -0
- data/lib/rgame/engine/viewports.rb +171 -0
- data/lib/rgame/engine/world_view.rb +71 -0
- data/lib/rgame/engine.rb +86 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +271 -0
- data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
- data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
- data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
- data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
- data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
- data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
- data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
- data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
- data/lib/rgame/rubocop/default.yml +39 -0
- data/lib/rgame/rubocop/plugin.rb +45 -0
- data/lib/rgame/rubocop.rb +11 -0
- data/lib/rgame/util/color.rb +23 -0
- data/lib/rgame/util/controls.rb +169 -0
- data/lib/rgame/util/route_search.rb +27 -0
- data/lib/rgame/util/save_file.rb +107 -0
- data/lib/rgame/util/solid_grid.rb +37 -0
- data/lib/rgame/util/tensor.rb +3 -0
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +123 -0
- data/lib/rgame/util.rb +10 -0
- data/lib/rgame/util_ext.so +0 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +5 -0
- data/licenses/SDL2/LICENSE.txt +18 -0
- metadata +309 -0
data/docs/api/game.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# `RGame::Game`
|
|
2
|
+
|
|
3
|
+
`Game` is a game's entry point, and the one class that knows both halves of the
|
|
4
|
+
engine.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
require 'rgame/game'
|
|
8
|
+
|
|
9
|
+
class HelloScene < RGame::Engine::Node2D
|
|
10
|
+
def on_draw(renderer, _view) = renderer.text('Hello world!', 250, 200)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
RGame::Game.new(root: HelloScene.new, caption: 'Hello').start
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
A root node plus those lines make a complete game. `Game` builds what a running
|
|
17
|
+
game needs and drives the root node. It builds the window and its loop, the
|
|
18
|
+
renderer, the asset manager, the sound device, the input mapper and the debug
|
|
19
|
+
overlay.
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
RGame::Game.new(root:, width: 640, height: 480, caption: 'RGame',
|
|
23
|
+
media_root: 'media', input_map: nil, device: Controls::KEYBOARD,
|
|
24
|
+
players: 1, input: nil, fullscreen: false, scale_mode: :letterbox,
|
|
25
|
+
locales: 'locales')
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Reader | |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `root` | the node tree |
|
|
31
|
+
| `renderer` | what scenes draw through |
|
|
32
|
+
| `players` | who is playing: their devices, bindings and cameras |
|
|
33
|
+
| `viewports` | how the screen is divided between players |
|
|
34
|
+
| `scale_mode`, `scale_mode=` | how the logical size maps onto the window; switchable while the game runs |
|
|
35
|
+
| `assets`, `audio`, `media_root`, `width`, `height`, `fps` | inherited from [App](app.md) |
|
|
36
|
+
|
|
37
|
+
A node reaches `players` and `viewports` as systems:
|
|
38
|
+
`node.system(RGame::Engine::Players)` and `node.system(RGame::Engine::Viewports)`.
|
|
39
|
+
|
|
40
|
+
`input:` replaces the input backend. A test harness passes a scripted backend
|
|
41
|
+
here to drive a game without hardware. A game passes nothing.
|
|
42
|
+
|
|
43
|
+
### `scale_mode:` — what `width` and `height` mean
|
|
44
|
+
|
|
45
|
+
**`width` and `height` are the logical size**: the resolution the game is
|
|
46
|
+
designed in. `Game` maps the whole frame onto the window, whatever its size. The
|
|
47
|
+
view a node draws into keeps the logical size. A layout written against fixed
|
|
48
|
+
numbers therefore works at any window size, fullscreen included.
|
|
49
|
+
|
|
50
|
+
| | |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `:letterbox` | The default. Largest uniform scale that fits, centred, bars on two sides. |
|
|
53
|
+
| `:integer` | The same, rounded down to a whole number. |
|
|
54
|
+
| `:stretch` | Fill the window, distorting if the aspect ratios differ. |
|
|
55
|
+
| `:disabled` | No scaling at all: `width` and `height` are the window, and the view is too. |
|
|
56
|
+
|
|
57
|
+
**`:disabled` turns scaling off and changes what the numbers mean.** The view
|
|
58
|
+
becomes the window, so a bigger window hands every `draw` a bigger view. A layout
|
|
59
|
+
written against `view.width` grows into the space. A layout written against fixed
|
|
60
|
+
numbers stays in the top-left corner. Choose `:disabled` for a program that uses
|
|
61
|
+
whatever space it gets: a tool, an editor, a program that is all HUD. Do not
|
|
62
|
+
choose it for a game with a designed play area. It is the only mode that pushes
|
|
63
|
+
no clip, translate or scale.
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
require 'rgame/game'
|
|
67
|
+
|
|
68
|
+
class Root < RGame::Engine::Node2D; end
|
|
69
|
+
|
|
70
|
+
RGame::Game.new(root: Root.new, width: 320, height: 180, scale_mode: :integer).start
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Choose `:integer` for pixel art.** A whole-number factor draws every source
|
|
74
|
+
pixel as the same square on screen. A fractional factor gives some pixels two
|
|
75
|
+
screen pixels and some three, and the unevenness crawls whenever anything moves.
|
|
76
|
+
The mode can waste a lot of screen. A 640x480 design gets only 1x on a 1600x900
|
|
77
|
+
window, because 2x needs 960 rows. `RGame::Engine::Presentation` holds the
|
|
78
|
+
arithmetic and documents the measurements. It is pure and has its own specs.
|
|
79
|
+
|
|
80
|
+
A resize refits the mode and changes nothing else. Viewports, split-screen rects
|
|
81
|
+
and camera clamps already use logical units.
|
|
82
|
+
|
|
83
|
+
`examples/fullscreen` runs in every mode, chosen by an environment variable.
|
|
84
|
+
|
|
85
|
+
`fullscreen:` opens the window fullscreen, so the game shows no windowed frame
|
|
86
|
+
at startup. `width` and `height` then give the size the window returns to, if
|
|
87
|
+
the game offers a way back. See [Fullscreen](app.md#fullscreen) and
|
|
88
|
+
`examples/fullscreen`.
|
|
89
|
+
|
|
90
|
+
`start` brings the tree live. It hands the game to the root as its `context`,
|
|
91
|
+
mounts `Players` and `Viewports` on the root, and subscribes an `AudioDirector` to
|
|
92
|
+
the [`AudioBus`](toolbox.md#audiobus--decoupled-audio-facts). It then calls
|
|
93
|
+
`enter_tree` and runs the loop until the window closes. When the loop ends, it
|
|
94
|
+
unsubscribes the director. `F1` toggles the debug overlay and `F2` quits.
|
|
95
|
+
|
|
96
|
+
The debug overlay is an `RGame::Engine::DebugOverlay`. It shows frames per
|
|
97
|
+
second, the total objects allocated, and the objects allocated since its last
|
|
98
|
+
frame, in the bottom-right corner. `F1` calls its `toggle`.
|
|
99
|
+
|
|
100
|
+
Each tick, `Game` polls input, runs `control` and `update` on the tree, and sweeps
|
|
101
|
+
freed nodes. It redraws only when a tick ran or the debug overlay is visible.
|
|
102
|
+
|
|
103
|
+
**Both development keys are function keys, and `Esc` stays free.** Players expect
|
|
104
|
+
Escape to back out of a menu, so it belongs to the game. A debug shortcut on it
|
|
105
|
+
would take it away from every game built on `Game`.
|
|
106
|
+
|
|
107
|
+
## Why this class exists
|
|
108
|
+
|
|
109
|
+
`RGame::Engine` holds game concepts and may not name `RGame::Core`.
|
|
110
|
+
`RGame::Core` owns windows, textures and sound devices, and may not know Engine
|
|
111
|
+
exists. **`Game` connects the two.** Keeping that connection in one file lets the
|
|
112
|
+
rule hold everywhere else. Inside rgame's own repository, two RuboCop cops enforce
|
|
113
|
+
it.
|
|
114
|
+
|
|
115
|
+
The tile map shows why. Engine parses a `.tmx`; Core draws it; neither may call
|
|
116
|
+
the other. `Game` installs the loader that joins them, so
|
|
117
|
+
`app.assets.tilemap('map/island.tmx')` works.
|
|
118
|
+
|
|
119
|
+
## Translations and the player's language
|
|
120
|
+
|
|
121
|
+
**`Game.new` loads every translation table and picks the player's language**, so
|
|
122
|
+
a game writes no i18n setup. It lists every `.yml` under `locales:` with
|
|
123
|
+
`AssetManager#glob`, sorted by path, and loads each through the asset manager's
|
|
124
|
+
`:locale` loader into [`RGame::Engine::I18n`](localization.md).
|
|
125
|
+
Two files that define one locale merge in that order, so a key the later file
|
|
126
|
+
sets wins. It then sets `I18n.locale` to
|
|
127
|
+
`I18n.choose(RGame::Core.preferred_locales)`: the first locale the OS prefers
|
|
128
|
+
that a table covers, unshortened, or the default.
|
|
129
|
+
|
|
130
|
+
`locales:` is relative to `media_root` unless it is absolute. A directory that
|
|
131
|
+
does not exist loads nothing, and every key then shows as itself.
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
game = MyGame.new(root: Root.new, media_root: 'media') # loads media/locales/**/*.yml
|
|
135
|
+
RGame::Engine::I18n.locale = saved_language if saved_language
|
|
136
|
+
game.start
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A language the player chose and the game saved belongs between `new` and
|
|
140
|
+
`start`, as above: `new` has already chosen from the OS by then. Loading the same
|
|
141
|
+
file again through `assets.locale(path)` returns the cached result and parses
|
|
142
|
+
nothing.
|
|
143
|
+
|
|
144
|
+
## Reaching the game from a node
|
|
145
|
+
|
|
146
|
+
A node anywhere in the tree reaches the asset manager through the root's
|
|
147
|
+
context. No constructor has to pass it along:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
sheet = node.context.assets.sheet('player.json') # node.context is node.root.context
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A component reaches the same object as `context`.
|
|
154
|
+
|
|
155
|
+
## Input
|
|
156
|
+
|
|
157
|
+
`input_map:` names a game's actions in terms of physical ids from
|
|
158
|
+
[`RGame::Util::Controls`](input.md):
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
require 'rgame/game'
|
|
162
|
+
|
|
163
|
+
Controls = RGame::Util::Controls
|
|
164
|
+
|
|
165
|
+
class Root < RGame::Engine::Node2D; end
|
|
166
|
+
|
|
167
|
+
RGame::Game.new(
|
|
168
|
+
root: Root.new,
|
|
169
|
+
input_map: RGame::Engine::InputMap.new(
|
|
170
|
+
move_x: { axis: [Controls::KEY_LEFT, Controls::KEY_RIGHT], # -1.0 .. 1.0
|
|
171
|
+
stick: Controls::AXIS_LEFT_X },
|
|
172
|
+
fire: { buttons: [Controls::KEY_SPACE, Controls::PAD_A] } # held / pressed / released
|
|
173
|
+
)
|
|
174
|
+
).start
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Without it, `Game` uses [`InputMap.default`](input.md). The default binds
|
|
178
|
+
eight-way `move_x` / `move_y` to the arrows, WASD, the d-pad and the left stick,
|
|
179
|
+
and adds `fire`. Every map merges over the universal UI set, so `ui_confirm` and
|
|
180
|
+
`ui_cancel` work without a declaration.
|
|
181
|
+
|
|
182
|
+
`device:` picks the device that drives player one. It defaults to the keyboard;
|
|
183
|
+
pass `Controls.gamepad(slot)` for a controller.
|
|
184
|
+
|
|
185
|
+
`players:` sets how many seats the game has (default 1). Extra seats start
|
|
186
|
+
empty. A seat fills when someone picks up a controller and presses confirm. An
|
|
187
|
+
empty seat draws no viewport, so one person playing a two-seat game sees an
|
|
188
|
+
ordinary full-screen game. See
|
|
189
|
+
[Players, seats and joining](input.md#players-seats-and-joining).
|
|
190
|
+
|
|
191
|
+
A scene reads the resulting snapshot in `on_control(actions)`, with calls like
|
|
192
|
+
`actions.axis(:move_x)` and `actions.pressed?(:fire)`. It never sees a key.
|
|
193
|
+
|
|
194
|
+
**`Game` polls input once per simulation tick**, not once per rendered frame.
|
|
195
|
+
Edge queries depend on this. `pressed?` means "held now, not held at the previous
|
|
196
|
+
poll", so the poll rate decides what a press *is*. Polling per frame would let a
|
|
197
|
+
fast-rendering loop consume a press between two ticks. Menus would then ignore
|
|
198
|
+
input, but only on fast machines.
|
|
199
|
+
|
|
200
|
+
Polling per tick costs nothing and loses nothing. The C layer snapshots the
|
|
201
|
+
keyboard once per frame, so every tick inside one frame reads identical state.
|
|
202
|
+
The edge lands on the first tick. One press gives one `pressed?`.
|
|
203
|
+
|
|
204
|
+
## Subclassing it
|
|
205
|
+
|
|
206
|
+
`Game` is an [`App`](app.md), so a subclass can override any `App` hook. The
|
|
207
|
+
engine owns the loop, the fixed timestep and the catch-up cap. A subclass adds
|
|
208
|
+
behaviour around the tree; it does not replace the shell.
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
require 'rgame/game'
|
|
212
|
+
|
|
213
|
+
class MyGame < RGame::Game
|
|
214
|
+
def button_down(id)
|
|
215
|
+
super # keeps F1 and F2 working
|
|
216
|
+
@paused = !@paused if id == RGame::Util::Controls::KEY_SPACE
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
data/docs/api/images.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Images
|
|
2
|
+
|
|
3
|
+
`RGame::Core::Image` is a picture on the GPU. Loading one decodes a PNG and
|
|
4
|
+
uploads it once. Subimages, tiles and whole sprite sheets are all *views* of that
|
|
5
|
+
single upload.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require 'rgame/core'
|
|
9
|
+
|
|
10
|
+
img = RGame::Core::Image.new(app, 'hero.png')
|
|
11
|
+
frame = img.subimage(0, 0, 16, 16)
|
|
12
|
+
walk = RGame::Core::Image.load_tiles(app, 'hero.png', 16, 16)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
[Drawing](drawing.md) covers putting images on screen.
|
|
16
|
+
|
|
17
|
+
**A game loads images through the [asset manager](assets.md)**, with
|
|
18
|
+
`app.assets.image(path)`. The manager resolves the path against `media_root` and
|
|
19
|
+
caches the image. `Image.new` and `Image.load_tiles` bypass that cache, and resolve a
|
|
20
|
+
relative path against the working directory.
|
|
21
|
+
|
|
22
|
+
## Loading
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
image = RGame::Core::Image.new(app, 'assets/hero.png')
|
|
26
|
+
image.width # => 64
|
|
27
|
+
image.height # => 32
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**The `app` argument is required and comes first.** A texture lives inside one
|
|
31
|
+
OpenGL context, so an image belongs to a window. Naming the app lets two windows
|
|
32
|
+
work side by side. It also lets the image keep its app alive as long as it needs.
|
|
33
|
+
|
|
34
|
+
rgame reads PNG only. A file it cannot read or decode raises
|
|
35
|
+
`RGame::Core::Image::LoadError`, with the path in the message:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
begin
|
|
39
|
+
RGame::Core::Image.new(app, 'assets/typo.png')
|
|
40
|
+
rescue RGame::Core::Image::LoadError => e
|
|
41
|
+
warn e.message # => "could not read assets/typo.png"
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Greyscale and palette PNGs load too. The loader converts them to RGBA, so the
|
|
46
|
+
engine handles one pixel format.
|
|
47
|
+
|
|
48
|
+
**Images always use nearest-neighbour sampling**, with no setting to change it.
|
|
49
|
+
The engine draws pixel art, and pixel art should never blur when scaled up.
|
|
50
|
+
|
|
51
|
+
## Slicing: subimages and tiles
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
sheet = RGame::Core::Image.new(app, 'tiles.png') # say 64x32
|
|
55
|
+
|
|
56
|
+
sheet.subimage(16, 0, 16, 16) # one 16x16 region
|
|
57
|
+
sheet.tile_count(16, 16) # => 8 (4 columns x 2 rows)
|
|
58
|
+
sheet.tile(16, 16, 5) # the sixth tile
|
|
59
|
+
sheet.tiles(16, 16) # => [Image, Image, ...] all eight
|
|
60
|
+
sheet.each_tile(16, 16) { |t| } # the same, without building the Array
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`Image.load_tiles(app, path, w, h)` combines `new` and `tiles`. It is the usual
|
|
64
|
+
way to open a sprite sheet:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
frames = RGame::Core::Image.load_tiles(app, 'explosion.png', 32, 32)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Three rules apply to all of these methods.
|
|
71
|
+
|
|
72
|
+
**Nothing is decoded or uploaded twice.** A hundred tiles are a hundred small Ruby
|
|
73
|
+
objects over one texture. Slice sheets at load time without worrying about cost.
|
|
74
|
+
|
|
75
|
+
**Tiles come back in reading order**: left to right, then top to bottom. Sprite
|
|
76
|
+
sheets number their frames the same way.
|
|
77
|
+
|
|
78
|
+
**A partial tile at the right or bottom edge is not a tile.** Slicing a 70-pixel
|
|
79
|
+
sheet into 16s yields four columns. The six leftover pixels count as padding,
|
|
80
|
+
because half a sprite is never wanted.
|
|
81
|
+
|
|
82
|
+
### Coordinates are relative to what you cut from
|
|
83
|
+
|
|
84
|
+
`subimage` on a subimage composes, and cannot escape its parent:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
row = sheet.subimage(0, 16, 64, 16) # the bottom row of the sheet
|
|
88
|
+
tile = row.subimage(32, 0, 16, 16) # 32 pixels into *the row*, not the sheet
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Bad coordinates raise; they never return `nil`.** A rectangle that does not fit
|
|
92
|
+
raises `ArgumentError`. An out-of-range tile index raises `IndexError`:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
sheet.subimage(0, 0, 999, 999) # ArgumentError: does not fit in a 64x32 image
|
|
96
|
+
sheet.tile(16, 16, 99) # IndexError: 8 tiles of 16x16
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A `nil` would travel a long way: into an asset table, and out again three scenes
|
|
100
|
+
later. It would finally fail as a `NoMethodError` that no longer points at the
|
|
101
|
+
wrong coordinates.
|
|
102
|
+
|
|
103
|
+
## Lifetime
|
|
104
|
+
|
|
105
|
+
**You never free an image.** The engine releases the texture when the last view
|
|
106
|
+
of it is garbage-collected, in any order. Tiles still in use keep the upload
|
|
107
|
+
alive after the sheet is dropped. Dropping the window first also works.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
sheet = RGame::Core::Image.new(app, 'tiles.png')
|
|
111
|
+
ground = sheet.tile(16, 16, 0)
|
|
112
|
+
sheet = nil # the upload stays — `ground` is still a view of it
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A leaked GPU texture shows no symptoms at first. Nothing slows down and nothing
|
|
116
|
+
looks wrong, while video memory fills over an hour of play.
|
|
117
|
+
`Image.debug_live_textures` returns how many uploads exist. Tests assert against
|
|
118
|
+
it; it is not part of the drawing API.
|