rgame 0.2.0 → 0.3.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 +135 -1
- data/README.md +67 -65
- data/docs/api/README.md +86 -82
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -165
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1032 -265
- data/docs/api/drawing.md +160 -160
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +149 -50
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +226 -148
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +397 -244
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +186 -73
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +395 -146
- data/docs/api/ui.md +883 -54
- data/docs/api/values.md +216 -33
- 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/ext/rgame_core/app/app.c +33 -3
- data/ext/rgame_core/app/locale.c +67 -0
- data/ext/rgame_core/app/locale.h +28 -0
- data/ext/rgame_core/audio/audio.c +39 -2
- data/ext/rgame_core/example.rb +0 -49
- data/ext/rgame_core/extconf.rb +0 -125
- data/ext/rgame_core/include/rgame/core.h +38 -2
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +30 -7
- data/ext/rgame_core/ruby/core_ext.h +3 -0
- data/ext/rgame_core/ruby/locale_ext.c +44 -0
- data/ext/rgame_core/ruby/recording_ext.c +1 -1
- data/ext/rgame_core/ruby/renderer_ext.c +20 -20
- data/ext/rgame_util/extconf.rb +2 -20
- data/ext/rgame_util/route_search.c +305 -0
- data/ext/rgame_util/route_search.h +86 -0
- data/ext/rgame_util/route_search_ext.c +150 -0
- data/ext/rgame_util/solid_grid.c +58 -0
- data/ext/rgame_util/solid_grid.h +49 -0
- data/ext/rgame_util/solid_grid_ext.c +161 -0
- data/ext/rgame_util/tile_sweep.c +164 -0
- data/ext/rgame_util/tile_sweep.h +62 -0
- data/ext/rgame_util/tile_sweep_ext.c +155 -0
- data/ext/rgame_util/util_ext.c +3 -0
- data/ext/rgame_util/util_ext.h +15 -0
- data/lib/rgame/boot.rb +0 -10
- 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 +6 -44
- data/lib/rgame/core/asset_manager.rb +13 -31
- data/lib/rgame/core/audio.rb +37 -16
- data/lib/rgame/core/font.rb +0 -3
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/renderer.rb +6 -63
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +2 -19
- data/lib/rgame/core/ui_atlas.rb +28 -13
- data/lib/rgame/core.rb +1 -8
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +1 -0
- data/lib/rgame/engine/audio_director.rb +36 -6
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +3 -3
- data/lib/rgame/engine/circle_collider.rb +4 -2
- data/lib/rgame/engine/collision_box.rb +26 -1
- data/lib/rgame/engine/collision_system.rb +110 -22
- data/lib/rgame/engine/component.rb +34 -10
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +27 -25
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +6 -5
- data/lib/rgame/engine/components/character_body.rb +20 -61
- data/lib/rgame/engine/components/circle_collider.rb +47 -11
- data/lib/rgame/engine/components/collision_world.rb +159 -31
- data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
- 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 +123 -31
- data/lib/rgame/engine/components/player_controller.rb +5 -2
- data/lib/rgame/engine/components/pool.rb +1 -1
- data/lib/rgame/engine/components/screen_wrap.rb +33 -11
- data/lib/rgame/engine/components/sprite.rb +12 -6
- data/lib/rgame/engine/components/targeting.rb +9 -11
- data/lib/rgame/engine/components/thrust_controller.rb +1 -1
- data/lib/rgame/engine/components/tile_world.rb +41 -13
- data/lib/rgame/engine/components/timer.rb +1 -1
- data/lib/rgame/engine/components/velocity.rb +23 -7
- data/lib/rgame/engine/components/wander_controller.rb +6 -2
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +4 -6
- data/lib/rgame/engine/debug_overlay.rb +5 -11
- 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 +276 -51
- data/lib/rgame/engine/input/action_mapper.rb +0 -26
- data/lib/rgame/engine/input/actions.rb +2 -8
- data/lib/rgame/engine/input/input_map.rb +39 -21
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +248 -133
- data/lib/rgame/engine/path.rb +4 -6
- data/lib/rgame/engine/players.rb +6 -13
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +4 -4
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +51 -19
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +2 -3
- data/lib/rgame/engine/tile_map_layer.rb +0 -2
- data/lib/rgame/engine/tileset.rb +2 -4
- data/lib/rgame/engine/timer.rb +2 -2
- 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 +246 -71
- 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/viewports.rb +2 -5
- data/lib/rgame/engine/world_view.rb +5 -4
- data/lib/rgame/engine.rb +31 -25
- data/lib/rgame/game.rb +99 -27
- 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 +20 -24
- data/lib/rgame/util/controls.rb +14 -28
- 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 +0 -9
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +4 -14
- data/lib/rgame/util.rb +4 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +152 -11
- data/lib/rgame/engine/actor.rb +0 -53
- data/lib/rgame/engine/body.rb +0 -49
- data/lib/rgame/engine/cached_label.rb +0 -33
- data/lib/rgame/engine/input/player_controller.rb +0 -14
- data/lib/rgame/engine/matrix.rb +0 -32
- data/lib/rgame/engine/resettable.rb +0 -67
- data/lib/rgame/engine/tile_collision.rb +0 -78
- data/lib/rgame/engine/ui/menu_item.rb +0 -84
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
`examples/` holds one small program per concept. Each is a single `main.rb` that
|
|
4
|
+
runs on its own:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
ruby examples/walk/main.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
**An example answers "how do I do *X*".** The complete games under
|
|
11
|
+
`test_projects/` answer that poorly. An example shows one concept in one file,
|
|
12
|
+
and its header comment explains the concept at length. This page is the index;
|
|
13
|
+
the file is the long version.
|
|
14
|
+
|
|
15
|
+
Every example has an acceptance test as well. A scripted input track lives at
|
|
16
|
+
`tools/drive/examples/<name>.rb`. The harness feeds that track to the unmodified
|
|
17
|
+
program and reports what the game asked for:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
ruby tools/drive_test_project.rb examples/walk/main.rb --ticks 240
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The examples' assets live under `examples/assets/` and are redistributable, so
|
|
24
|
+
every example runs from a fresh clone. Most examples need no art at all.
|
|
25
|
+
|
|
26
|
+
## Movement and drawing
|
|
27
|
+
|
|
28
|
+
### walk
|
|
29
|
+
|
|
30
|
+
A player-controlled sprite: the smallest complete game. It is a plain node with
|
|
31
|
+
three components, and no component knows about the others.
|
|
32
|
+
|
|
33
|
+
**Uses:** `Node2D`, `Components::AnimatedSprite`, `Components::CharacterBody`,
|
|
34
|
+
`Components::PlayerController`, `InputMap.default`.
|
|
35
|
+
|
|
36
|
+
### sprite
|
|
37
|
+
|
|
38
|
+
One frame drawn at a node, with no animation. Most things in a game look like
|
|
39
|
+
this. The draw passes no position and no angle, because the node's transform
|
|
40
|
+
already applies.
|
|
41
|
+
|
|
42
|
+
**Uses:** `Components::Sprite`, `Image#subimage`, `renderer.register_image`.
|
|
43
|
+
|
|
44
|
+
### velocity
|
|
45
|
+
|
|
46
|
+
The second way a node moves. A character has an *intent*. A rock has a velocity,
|
|
47
|
+
and a component integrates it, spin included.
|
|
48
|
+
|
|
49
|
+
**Uses:** `Components::Velocity`, `Components::ScreenWrap`, `Components::World`,
|
|
50
|
+
`Components::WorldBounds`.
|
|
51
|
+
|
|
52
|
+
## The world
|
|
53
|
+
|
|
54
|
+
### scroll_map
|
|
55
|
+
|
|
56
|
+
A Tiled map larger than the window, scrolled by a player. No call pans the
|
|
57
|
+
camera. A component on a node points the camera, so scrolling is walking.
|
|
58
|
+
|
|
59
|
+
**Uses:** the `:tilemap` asset loader, `Components::TileWorld`, `TileMapLayer`,
|
|
60
|
+
`WorldView`, `Camera`, `Components::CameraFollow`.
|
|
61
|
+
|
|
62
|
+
### collision
|
|
63
|
+
|
|
64
|
+
Object-to-object collision. A scene-scoped system pairs up shapes each step and
|
|
65
|
+
tells both sides they overlapped. It never learns what either object is.
|
|
66
|
+
|
|
67
|
+
**Uses:** `Components::CollisionWorld`, `Components::CircleCollider`,
|
|
68
|
+
`Components::BoxCollider`, `Components::Velocity`, `Engine::Text`.
|
|
69
|
+
|
|
70
|
+
### collision_tiles
|
|
71
|
+
|
|
72
|
+
A character against a grid of solid tiles, sliding along a wall while walking
|
|
73
|
+
diagonally. East of the start, a spiky ball stops the hero and costs a life. One
|
|
74
|
+
feet box is thus stopped by two indexes: the map's grid and the broadphase.
|
|
75
|
+
`blocked_by: %i[tiles spike]` is the only place the difference shows. The life is
|
|
76
|
+
spent in `on_blocked`, because a blocked pair ends up touching, not overlapping.
|
|
77
|
+
`on_hit` never fires for it.
|
|
78
|
+
|
|
79
|
+
**Uses:** `Components::TileWorld`, `Components::CollisionWorld`,
|
|
80
|
+
`Components::FeetCollider`, `Components::CharacterBody` with
|
|
81
|
+
`blocked_by: %i[tiles spike]` and `on_blocked`, `Components::CameraFollow`,
|
|
82
|
+
`Engine::Text`.
|
|
83
|
+
|
|
84
|
+
### jump_topdown
|
|
85
|
+
|
|
86
|
+
A hop in a top-down view. "Up" on screen is north, so a jump cannot move the
|
|
87
|
+
character. The sprite rises along `Hop`'s parabola. The feet box, the shadow and
|
|
88
|
+
the camera stay on the ground. A hop at the fence therefore does not clear it,
|
|
89
|
+
because the part that collides never leaves the ground. The game decides what a
|
|
90
|
+
hop may cross, through `airborne?`.
|
|
91
|
+
|
|
92
|
+
**Uses:** `Components::Hop`, `Node2D#elevation`, `Components::AnimatedSprite`,
|
|
93
|
+
`Components::FeetCollider`, `Components::CharacterBody`, `Components::TileWorld`,
|
|
94
|
+
`Components::CameraFollow`, `InputMap.default.merge`.
|
|
95
|
+
|
|
96
|
+
### pathfinding
|
|
97
|
+
|
|
98
|
+
Pick a tile, and the hero works out how to get there. Small dots show the route
|
|
99
|
+
the search found, one per tile. Lines show the route the hero walks. The
|
|
100
|
+
navigator pulls the route tight, keeping each line straight as long as the hero's
|
|
101
|
+
feet box fits.
|
|
102
|
+
|
|
103
|
+
**Uses:** `Components::Navigator`, `Components::TileWorld#nav_grid`,
|
|
104
|
+
`Components::AnimatedSprite`, `Components::ActionTrigger`,
|
|
105
|
+
`Components::CameraFollow`, `Engine::Text.computed` over `I18n.t` with plurals.
|
|
106
|
+
|
|
107
|
+
## Structure
|
|
108
|
+
|
|
109
|
+
### signals
|
|
110
|
+
|
|
111
|
+
Declaring your own signal. A pressure plate announces that it was pressed, and
|
|
112
|
+
does nothing more. The door and the lamp connect to it; the plate never names
|
|
113
|
+
them.
|
|
114
|
+
|
|
115
|
+
**Uses:** `Signal::DSL`, `Signal.define`, `Components::ActionTrigger`, the
|
|
116
|
+
connect handle.
|
|
117
|
+
|
|
118
|
+
### timer
|
|
119
|
+
|
|
120
|
+
Periodic behaviour that no input drives: a spawn cadence and a one-shot, with two
|
|
121
|
+
cadences on one node.
|
|
122
|
+
|
|
123
|
+
**Uses:** `Components::Timer` (repeating and `repeating: false`, and `as:`),
|
|
124
|
+
`Engine::Timer`.
|
|
125
|
+
|
|
126
|
+
### pooling
|
|
127
|
+
|
|
128
|
+
Spawning many things without building any of them. The allocation count on
|
|
129
|
+
screen makes the case.
|
|
130
|
+
|
|
131
|
+
**Uses:** `Components::Pool`, `Engine::Pool`, `Components::DespawnOffscreen`,
|
|
132
|
+
`Components::Timer`, `Engine::Text`.
|
|
133
|
+
|
|
134
|
+
## UI
|
|
135
|
+
|
|
136
|
+
### game_menu
|
|
137
|
+
|
|
138
|
+
A menu that opens over a running world. Pausing belongs to a node, so only the
|
|
139
|
+
hero stops while the villagers walk on.
|
|
140
|
+
|
|
141
|
+
**Uses:** `PlayerLayer`, `UI::PanelMenu`, `UI::PanelButton`, `UI::Menu#open` /
|
|
142
|
+
`#close`, `Node2D#paused`, `renderer.nine_slice`.
|
|
143
|
+
|
|
144
|
+
### menu_navigation
|
|
145
|
+
|
|
146
|
+
Several screens (title, settings, back) and settings that change something real
|
|
147
|
+
and survive a restart. It contrasts pushing a scene with replacing one.
|
|
148
|
+
|
|
149
|
+
**Uses:** `Scene::SceneStack`, `UI::OptionButton`, `Util::SaveFile`,
|
|
150
|
+
`RGame::Game`'s fullscreen, scale mode and volume.
|
|
151
|
+
|
|
152
|
+
### radial_menu
|
|
153
|
+
|
|
154
|
+
Choosing by pointing. Eight icons sit on a wheel, focused by the direction of the
|
|
155
|
+
stick or the arrow keys. A released stick selects nothing, so pressing A at rest
|
|
156
|
+
never picks what the stick passed on its way back.
|
|
157
|
+
|
|
158
|
+
**Uses:** `UI::RadialMenu`, `UI::IconButton` on a disc `UI::ShapeStyle`,
|
|
159
|
+
`ui_radial_x` / `ui_radial_y`, and a UI atlas's `images` (`icons.json`).
|
|
160
|
+
|
|
161
|
+
### quick_wheel
|
|
162
|
+
|
|
163
|
+
The same eight icons, on a wheel held open by Tab or the left shoulder button.
|
|
164
|
+
Releasing the button chooses. A stick released a moment before the button still
|
|
165
|
+
chooses; a stick at rest chooses nothing. The world drifts at a quarter speed
|
|
166
|
+
while the wheel is open.
|
|
167
|
+
|
|
168
|
+
**Uses:** `UI::RadialMenu` with `trigger:`, `UI::Pointing`'s grace window,
|
|
169
|
+
`UI::Menu#on_opened` / `#on_closed`, `InputMap.default.merge`.
|
|
170
|
+
|
|
171
|
+
### skill_bar
|
|
172
|
+
|
|
173
|
+
A tool bar with five tools in a row. Left and right step through them and Enter
|
|
174
|
+
uses one. The number keys use a tool directly, without moving the focus. Holding
|
|
175
|
+
a number uses its tool once. Pressing a tool's number and Enter together also
|
|
176
|
+
uses it once.
|
|
177
|
+
|
|
178
|
+
**Uses:** `UI::Row`, `UI::Stepping` taking its axis from the layout,
|
|
179
|
+
`UI::Button`'s `hotkey:` and `activate_on: :press`, captioned `UI::IconButton`s
|
|
180
|
+
on a disc `UI::ShapeStyle`, `InputMap.default.merge`, and a UI atlas's `images`
|
|
181
|
+
(`skills.json`).
|
|
182
|
+
|
|
183
|
+
## Audio
|
|
184
|
+
|
|
185
|
+
### sound
|
|
186
|
+
|
|
187
|
+
A sound effect fired by a button, and the path it travels. A node may not name
|
|
188
|
+
the audio device, so it emits a fact and a director plays it.
|
|
189
|
+
|
|
190
|
+
**Uses:** `Core::Sample`, `Engine::AudioBus`, `Engine::AudioDirector`,
|
|
191
|
+
`Engine::Text`.
|
|
192
|
+
|
|
193
|
+
### music
|
|
194
|
+
|
|
195
|
+
The other kind of sound: one streamed voice. You can stop it and ask whether it
|
|
196
|
+
plays, and starting it again does not restart it.
|
|
197
|
+
|
|
198
|
+
**Uses:** `Core::Song`, `AudioBus.play_music` / `.stop_music`,
|
|
199
|
+
`Engine::AudioDirector`.
|
|
200
|
+
|
|
201
|
+
## Players and input
|
|
202
|
+
|
|
203
|
+
### split_screen
|
|
204
|
+
|
|
205
|
+
Two players in one world. `WorldView` draws the world once per viewport, through
|
|
206
|
+
that viewport's camera. The world never knows how often it is drawn.
|
|
207
|
+
|
|
208
|
+
**Uses:** `Game.new(players: 2)`, `Engine::Players`, `Engine::WorldView`,
|
|
209
|
+
`Engine::PlayerLayer`, `Engine::Camera`, `Components::CameraFollow`,
|
|
210
|
+
`input_owner`.
|
|
211
|
+
|
|
212
|
+
### input_glyphs
|
|
213
|
+
|
|
214
|
+
Prompts that match the device in the player's hands. They switch between
|
|
215
|
+
keyboard and controller mid-session. Nothing listens for a plugged-in pad; using
|
|
216
|
+
the pad takes the seat.
|
|
217
|
+
|
|
218
|
+
**Uses:** `Controls.gamepad?`, `InputMap#button_for`, `Engine::Players` with
|
|
219
|
+
`on_unassigned_input` defaulting to `:takeover`, `renderer.sprite`.
|
|
220
|
+
|
|
221
|
+
## The window
|
|
222
|
+
|
|
223
|
+
### fullscreen
|
|
224
|
+
|
|
225
|
+
Opening fullscreen, switching while the game runs, and all four scale modes with
|
|
226
|
+
the layout following each.
|
|
227
|
+
|
|
228
|
+
**Uses:** `RGame::Game.new(fullscreen:)`, `App#fullscreen?` / `#fullscreen=`,
|
|
229
|
+
`RGame::Game#scale_mode=`, the `view` a node is drawn with,
|
|
230
|
+
`InputMap.default.merge`.
|
|
231
|
+
|
|
232
|
+
## Language
|
|
233
|
+
|
|
234
|
+
### localization
|
|
235
|
+
|
|
236
|
+
The same screen in English and German. A `Text` with a `count` picks its plural
|
|
237
|
+
form, and one with a variable shows the current locale. German lacks one key, so
|
|
238
|
+
that line falls back to English. The player's language choice overrides the
|
|
239
|
+
operating system's and is saved.
|
|
240
|
+
|
|
241
|
+
**Uses:** `Engine::I18n`, `Engine::Text` and `Text.literal`, `UI::Menu`'s
|
|
242
|
+
`scope:`, `RGame::Game.new(locales:)`, `RGame::Core.preferred_locales`,
|
|
243
|
+
`Util::SaveFile`.
|
|
244
|
+
|
|
245
|
+
## Persistence
|
|
246
|
+
|
|
247
|
+
### save_load
|
|
248
|
+
|
|
249
|
+
Writing game state to disk and restoring it. The tree is not saved: a scene is a
|
|
250
|
+
recipe, and a save file is state. The variable holding a singular thing restores
|
|
251
|
+
it, and array order restores a flock.
|
|
252
|
+
|
|
253
|
+
**Uses:** `Util::SaveFile`, the `examples/walk` composition with a
|
|
254
|
+
`WanderController`.
|
|
255
|
+
|
|
256
|
+
### save_load_ids
|
|
257
|
+
|
|
258
|
+
The case `save_load` leaves out: a collection whose members can be lost, and one
|
|
259
|
+
saved object that refers to another. A reference forces ids; a changing
|
|
260
|
+
collection alone does not.
|
|
261
|
+
|
|
262
|
+
**Uses:** `Components::Identity`, `Util::SaveFile`, a save of records rather
|
|
263
|
+
than positions.
|
data/docs/api/game.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# `RGame::Game`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`Game` is a game's entry point, and the one class that knows both halves of the
|
|
4
4
|
engine.
|
|
5
5
|
|
|
6
6
|
```ruby
|
|
7
|
-
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
8
7
|
require 'rgame/game'
|
|
9
8
|
|
|
10
9
|
class HelloScene < RGame::Engine::Node2D
|
|
@@ -14,13 +13,16 @@ end
|
|
|
14
13
|
RGame::Game.new(root: HelloScene.new, caption: 'Hello').start
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
A
|
|
18
|
-
needs
|
|
19
|
-
|
|
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
20
|
|
|
21
21
|
```ruby
|
|
22
22
|
RGame::Game.new(root:, width: 640, height: 480, caption: 'RGame',
|
|
23
|
-
media_root: 'media', input_map: nil, device: Controls::KEYBOARD
|
|
23
|
+
media_root: 'media', input_map: nil, device: Controls::KEYBOARD,
|
|
24
|
+
players: 1, input: nil, fullscreen: false, scale_mode: :letterbox,
|
|
25
|
+
locales: 'locales')
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
| Reader | |
|
|
@@ -28,46 +30,140 @@ RGame::Game.new(root:, width: 640, height: 480, caption: 'RGame',
|
|
|
28
30
|
| `root` | the node tree |
|
|
29
31
|
| `renderer` | what scenes draw through |
|
|
30
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 |
|
|
31
35
|
| `assets`, `audio`, `media_root`, `width`, `height`, `fps` | inherited from [App](app.md) |
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
debug overlay and `F2` quits.
|
|
37
|
+
A node reaches `players` and `viewports` as systems:
|
|
38
|
+
`node.system(RGame::Engine::Players)` and `node.system(RGame::Engine::Viewports)`.
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
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.
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
### `scale_mode:` — what `width` and `height` mean
|
|
43
44
|
|
|
44
|
-
`
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
file is what makes the rule checkable everywhere else.
|
|
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
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
53
143
|
|
|
54
144
|
## Reaching the game from a node
|
|
55
145
|
|
|
56
|
-
A node
|
|
57
|
-
|
|
146
|
+
A node anywhere in the tree reaches the asset manager through the root's
|
|
147
|
+
context. No constructor has to pass it along:
|
|
58
148
|
|
|
59
149
|
```ruby
|
|
60
|
-
sheet = node.
|
|
150
|
+
sheet = node.context.assets.sheet('player.json') # node.context is node.root.context
|
|
61
151
|
```
|
|
62
152
|
|
|
153
|
+
A component reaches the same object as `context`.
|
|
154
|
+
|
|
63
155
|
## Input
|
|
64
156
|
|
|
65
|
-
`input_map:` names
|
|
157
|
+
`input_map:` names a game's actions in terms of physical ids from
|
|
66
158
|
[`RGame::Util::Controls`](input.md):
|
|
67
159
|
|
|
68
160
|
```ruby
|
|
161
|
+
require 'rgame/game'
|
|
162
|
+
|
|
69
163
|
Controls = RGame::Util::Controls
|
|
70
164
|
|
|
165
|
+
class Root < RGame::Engine::Node2D; end
|
|
166
|
+
|
|
71
167
|
RGame::Game.new(
|
|
72
168
|
root: Root.new,
|
|
73
169
|
input_map: RGame::Engine::InputMap.new(
|
|
@@ -75,42 +171,45 @@ RGame::Game.new(
|
|
|
75
171
|
stick: Controls::AXIS_LEFT_X },
|
|
76
172
|
fire: { buttons: [Controls::KEY_SPACE, Controls::PAD_A] } # held / pressed / released
|
|
77
173
|
)
|
|
78
|
-
)
|
|
174
|
+
).start
|
|
79
175
|
```
|
|
80
176
|
|
|
81
|
-
|
|
82
|
-
`move_y`
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
85
181
|
|
|
86
|
-
`device:` picks
|
|
87
|
-
`Controls.gamepad(slot)` for a controller.
|
|
182
|
+
`device:` picks the device that drives player one. It defaults to the keyboard;
|
|
183
|
+
pass `Controls.gamepad(slot)` for a controller.
|
|
88
184
|
|
|
89
|
-
`players:`
|
|
90
|
-
|
|
91
|
-
draws no viewport, so a two-seat game
|
|
92
|
-
full-screen game. See
|
|
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).
|
|
93
190
|
|
|
94
|
-
A scene reads the resulting snapshot in `on_control(actions)
|
|
95
|
-
`actions.pressed?(:fire)
|
|
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.
|
|
96
193
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
poll", so
|
|
100
|
-
|
|
101
|
-
|
|
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.
|
|
102
199
|
|
|
103
|
-
Polling per tick costs nothing and loses nothing
|
|
104
|
-
|
|
105
|
-
|
|
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?`.
|
|
106
203
|
|
|
107
204
|
## Subclassing it
|
|
108
205
|
|
|
109
|
-
`Game` is an [`App`](app.md), so
|
|
110
|
-
loop, the fixed timestep and the catch-up cap
|
|
111
|
-
behaviour around the tree
|
|
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.
|
|
112
209
|
|
|
113
210
|
```ruby
|
|
211
|
+
require 'rgame/game'
|
|
212
|
+
|
|
114
213
|
class MyGame < RGame::Game
|
|
115
214
|
def button_down(id)
|
|
116
215
|
super # keeps F1 and F2 working
|