rgame 0.1.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 +228 -0
- data/README.md +142 -243
- data/docs/api/README.md +141 -90
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -166
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1045 -254
- data/docs/api/drawing.md +187 -119
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +165 -45
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +344 -85
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +503 -123
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +197 -64
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +400 -147
- data/docs/api/ui.md +927 -0
- data/docs/api/values.md +234 -19
- 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/README.md +6 -5
- data/ext/rgame_core/app/app.c +215 -11
- 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 +113 -2
- data/ext/rgame_core/example.rb +18 -56
- data/ext/rgame_core/extconf.rb +16 -113
- 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 +151 -5
- data/ext/rgame_core/input/gamepad.c +57 -3
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +46 -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 +42 -19
- data/ext/rgame_util/color_ext.c +12 -3
- 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 -42
- 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/input.rb +35 -41
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/recording.rb +3 -1
- data/lib/rgame/core/renderer.rb +75 -84
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +77 -65
- 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 +55 -10
- 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 +35 -1
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +31 -23
- 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 +21 -41
- 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 +22 -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 +70 -29
- 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 +45 -0
- data/lib/rgame/engine/debug_overlay.rb +23 -18
- 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 +76 -22
- data/lib/rgame/engine/input/actions.rb +63 -12
- 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 +364 -80
- data/lib/rgame/engine/path.rb +4 -6
- 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/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +29 -7
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +53 -8
- 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 +82 -0
- 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 +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 +43 -25
- data/lib/rgame/game.rb +164 -22
- 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 +106 -44
- 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 +123 -0
- data/lib/rgame/util.rb +5 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +176 -20
- 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/camera_view.rb +0 -28
- 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
|
@@ -12,7 +12,7 @@ module RGame
|
|
|
12
12
|
# app.assets.release(:level1)
|
|
13
13
|
#
|
|
14
14
|
# The built-in types are `image`, `sound`, `song` and `read`, plus the
|
|
15
|
-
# composites `sheet` and `ui_atlas
|
|
15
|
+
# composites `sheet` and `ui_atlas`; `glob` lists what is there to load. A game or its glue adds more with
|
|
16
16
|
# {#add_loader}, which is how a tile map gets loaded without Core having to
|
|
17
17
|
# know what one is.
|
|
18
18
|
#
|
|
@@ -55,7 +55,6 @@ module RGame
|
|
|
55
55
|
# `loaders:` is what lets the caching, path resolution and grouping be
|
|
56
56
|
# specced with no window, no GL context and no files at all.
|
|
57
57
|
class AssetManager
|
|
58
|
-
# Owner of every ungrouped load. Never released, only cleared.
|
|
59
58
|
PERMANENT = :__permanent__
|
|
60
59
|
|
|
61
60
|
# `app` is what images are loaded into and where the audio device comes
|
|
@@ -65,7 +64,7 @@ module RGame
|
|
|
65
64
|
@app = app
|
|
66
65
|
@loaders = {}
|
|
67
66
|
@cache = {}
|
|
68
|
-
@owners = {}
|
|
67
|
+
@owners = {}
|
|
69
68
|
|
|
70
69
|
(loaders || default_loaders).each { |type, loader| add_loader(type, &loader) }
|
|
71
70
|
end
|
|
@@ -91,9 +90,6 @@ module RGame
|
|
|
91
90
|
# offering it an id.
|
|
92
91
|
def add_loader(type, &loader)
|
|
93
92
|
@loaders[type] = loader
|
|
94
|
-
# A singleton method rather than a class-level one: two managers may
|
|
95
|
-
# know different types, and a game that adds `:tilemap` should not be
|
|
96
|
-
# teaching it to everyone else's.
|
|
97
93
|
define_singleton_method(type) { |path, group = PERMANENT| leaf(type, path, group) }
|
|
98
94
|
self
|
|
99
95
|
end
|
|
@@ -130,10 +126,6 @@ module RGame
|
|
|
130
126
|
# Takes `group` off every asset's owner set and drops whatever no group
|
|
131
127
|
# still holds.
|
|
132
128
|
def release(group)
|
|
133
|
-
# Without this, `release(PERMANENT)` would empty every ungrouped
|
|
134
|
-
# asset's owner set and drop the lot — the exact opposite of what the
|
|
135
|
-
# constant's name promises, and silent. It is only reachable by naming
|
|
136
|
-
# the sentinel, so saying what to use instead beats ignoring the call.
|
|
137
129
|
raise ArgumentError, 'ungrouped assets are dropped by #clear, not #release' if group == PERMANENT
|
|
138
130
|
|
|
139
131
|
@owners.each_value { |groups| groups.delete(group) }
|
|
@@ -153,15 +145,23 @@ module RGame
|
|
|
153
145
|
self
|
|
154
146
|
end
|
|
155
147
|
|
|
148
|
+
# The paths under the media root that match `pattern`, relative to the
|
|
149
|
+
# root and sorted, so what a caller loads from the list — and the order it
|
|
150
|
+
# merges in — is the same on every platform and file system:
|
|
151
|
+
#
|
|
152
|
+
# assets.glob('locales/**/*.yml') # => ["locales/de.yml", "locales/en.yml"]
|
|
153
|
+
#
|
|
154
|
+
# A directory that does not exist matches nothing, so a game without one
|
|
155
|
+
# gets `[]` rather than an error. An absolute pattern is used as it
|
|
156
|
+
# stands, and its matches come back absolute. Nothing is loaded or cached.
|
|
157
|
+
def glob(pattern) = Dir.glob(pattern, base: File.expand_path(@root)).sort
|
|
158
|
+
|
|
156
159
|
# How many assets are cached. For tests and for a debug overlay; a game
|
|
157
160
|
# has no reason to ask.
|
|
158
161
|
def size = @cache.size
|
|
159
162
|
|
|
160
163
|
private
|
|
161
164
|
|
|
162
|
-
# The app is reached through the ivar rather than captured, so
|
|
163
|
-
# `app.audio` is not called until a sound is actually asked for — loading
|
|
164
|
-
# an image opens no sound device.
|
|
165
165
|
def default_loaders
|
|
166
166
|
{
|
|
167
167
|
image: ->(path) { Image.new(@app, path) },
|
|
@@ -175,20 +175,8 @@ module RGame
|
|
|
175
175
|
fetch(type, path, group) { @loaders.fetch(type).call(resolve(path)) }
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
# Where a path actually is on disk.
|
|
179
|
-
#
|
|
180
|
-
# `expand_path` rather than `join` for two reasons. An **absolute** path
|
|
181
|
-
# is used as it stands, which is what lets a loader hand one back — the
|
|
182
|
-
# tile-map loader gets its tileset image that way, since the path comes
|
|
183
|
-
# out of a `.tsx` that was itself found on disk. And `'a/./b.png'`,
|
|
184
|
-
# `'a/../b.png'` and `'b.png'` all land on one cache key rather than
|
|
185
|
-
# three, so a file cannot be loaded twice by being named twice.
|
|
186
178
|
def resolve(path) = File.expand_path(path, @root)
|
|
187
179
|
|
|
188
|
-
# Memoise on miss, then record the owning group — so a cache *hit* under a
|
|
189
|
-
# new group is tagged too. Tagging after the load rather than before is
|
|
190
|
-
# what leaves a failed load with no owner behind it, which makes a retry a
|
|
191
|
-
# clean retry rather than a permanently half-registered asset.
|
|
192
180
|
def fetch(type, path, group)
|
|
193
181
|
key = [type, resolve(path)]
|
|
194
182
|
object = (@cache[key] ||= yield)
|
|
@@ -204,17 +192,11 @@ module RGame
|
|
|
204
192
|
UiAtlas.new(*composite_parts(relative_path, group))
|
|
205
193
|
end
|
|
206
194
|
|
|
207
|
-
# The two halves a composite is made of, both through the cache: the
|
|
208
|
-
# descriptor as a cached `read`, and the image it names as a cached
|
|
209
|
-
# `image` resolved beside it.
|
|
210
195
|
def composite_parts(relative_path, group)
|
|
211
196
|
data = JSON.parse(read(relative_path, group), symbolize_names: true)
|
|
212
197
|
[image(sibling(relative_path, data[:image]), group), data]
|
|
213
198
|
end
|
|
214
199
|
|
|
215
|
-
# A path next to `relative_path`, still relative to the root — so a
|
|
216
|
-
# descriptor's image lands on the same `[:image, path]` cache key a
|
|
217
|
-
# standalone `image` of that file would.
|
|
218
200
|
def sibling(relative_path, name)
|
|
219
201
|
directory = File.dirname(relative_path)
|
|
220
202
|
directory == '.' ? name : File.join(directory, name)
|
data/lib/rgame/core/audio.rb
CHANGED
|
@@ -19,6 +19,21 @@ module RGame
|
|
|
19
19
|
# the alternative is a crash at startup for something nobody asked for.
|
|
20
20
|
# `#backend` says which one was chosen.
|
|
21
21
|
class Audio
|
|
22
|
+
# `assets` is what a path id is resolved through, and may be nil — a
|
|
23
|
+
# device built by hand plays only what it is handed. `RGame::Core::App`
|
|
24
|
+
# passes its own manager, so a game never sets this.
|
|
25
|
+
#
|
|
26
|
+
# A Ruby `self.new` because the C `initialize` takes no arguments and has
|
|
27
|
+
# no business knowing what an asset manager is — the same shape
|
|
28
|
+
# `Renderer.new` uses for the same reason.
|
|
29
|
+
def self.new(assets: nil)
|
|
30
|
+
audio = super()
|
|
31
|
+
audio.assets = assets
|
|
32
|
+
audio
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_accessor :assets
|
|
36
|
+
|
|
22
37
|
# Loads a short sound to play over itself. The same thing as
|
|
23
38
|
# `Sample.new(audio, path)`, and the form to prefer: it reads in the
|
|
24
39
|
# direction the objects depend, and it is the form a stand-in device can
|
|
@@ -28,15 +43,6 @@ module RGame
|
|
|
28
43
|
# Loads a long one to stream. See {Song}.
|
|
29
44
|
def song(path) = Song.new(self, path)
|
|
30
45
|
|
|
31
|
-
# --- play-by-id -----------------------------------------------------
|
|
32
|
-
#
|
|
33
|
-
# The same boundary the renderer's draw-by-id serves: game logic emits
|
|
34
|
-
# facts — "the ship was hit" — and names the sound, because a scene may
|
|
35
|
-
# not hold a `Sample`. Registration only, unlike the renderer: a sound id
|
|
36
|
-
# is whatever a game wants to call it, and there is no per-frame path to
|
|
37
|
-
# make resolving one worth caching.
|
|
38
|
-
#
|
|
39
|
-
# audio.register_sound(:hit, app.assets.sound('example 09/hurt.ogg'))
|
|
40
46
|
# audio.play_sound(:hit)
|
|
41
47
|
|
|
42
48
|
def register_sound(id, sample)
|
|
@@ -49,17 +55,17 @@ module RGame
|
|
|
49
55
|
self
|
|
50
56
|
end
|
|
51
57
|
|
|
52
|
-
# Plays a
|
|
53
|
-
#
|
|
58
|
+
# Plays a sample by id. Each call is another voice, layered over the ones
|
|
59
|
+
# already sounding.
|
|
54
60
|
def play_sound(id)
|
|
55
|
-
|
|
61
|
+
lookup(:sound, id).play
|
|
56
62
|
end
|
|
57
63
|
|
|
58
|
-
# Starts a
|
|
59
|
-
#
|
|
60
|
-
#
|
|
64
|
+
# Starts a song looping, and does **nothing** if it is already playing —
|
|
65
|
+
# so a scene that re-emits the same request every time it is entered never
|
|
66
|
+
# restarts the music mid-loop.
|
|
61
67
|
def play_music(id)
|
|
62
|
-
song =
|
|
68
|
+
song = lookup(:song, id)
|
|
63
69
|
return song if song.playing?
|
|
64
70
|
|
|
65
71
|
@playing_song = song
|
|
@@ -81,6 +87,21 @@ module RGame
|
|
|
81
87
|
|
|
82
88
|
def samples = @samples ||= {}
|
|
83
89
|
def songs = @songs ||= {}
|
|
90
|
+
|
|
91
|
+
def registry(type) = type == :sound ? samples : songs
|
|
92
|
+
|
|
93
|
+
def lookup(type, id)
|
|
94
|
+
raise TypeError, "no implicit conversion of nil into #{type}" if id.nil?
|
|
95
|
+
|
|
96
|
+
table = registry(type)
|
|
97
|
+
table.fetch(id) { table[id] = resolve_asset(type, id) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def resolve_asset(type, id)
|
|
101
|
+
resolved = @assets.public_send(type, id) if id.is_a?(String) && @assets.respond_to?(type)
|
|
102
|
+
resolved || raise(KeyError, "no #{type} registered for #{id.inspect} " \
|
|
103
|
+
'and no AssetManager to resolve it')
|
|
104
|
+
end
|
|
84
105
|
end
|
|
85
106
|
|
|
86
107
|
# A short sound, decoded once and played many times over.
|
data/lib/rgame/core/font.rb
CHANGED
|
@@ -34,9 +34,6 @@ module RGame
|
|
|
34
34
|
# Scripts outside the shipped font's coverage — CJK, Arabic, Hebrew — need
|
|
35
35
|
# their own file. No font of this size covers them.
|
|
36
36
|
class Font
|
|
37
|
-
# Shipped with the gem, alongside its SIL OFL 1.1 licence. Resolved from
|
|
38
|
-
# this file's location so it works the same from a checkout and from an
|
|
39
|
-
# installed gem.
|
|
40
37
|
DEFAULT_PATH = File.expand_path('../fonts/LiberationSans-Regular.ttf', __dir__)
|
|
41
38
|
|
|
42
39
|
# `path:` is a keyword for callers but positional for the C initialize,
|
data/lib/rgame/core/input.rb
CHANGED
|
@@ -5,72 +5,66 @@ require_relative '../util/controls'
|
|
|
5
5
|
|
|
6
6
|
module RGame
|
|
7
7
|
module Core
|
|
8
|
-
#
|
|
9
|
-
# button ids the engine understands, and asks the app whether they're held.
|
|
8
|
+
# Asks the engine whether a physical input is active, on a given device.
|
|
10
9
|
#
|
|
11
10
|
# input = RGame::Core::Input.new(app)
|
|
12
|
-
# input.down?(
|
|
13
|
-
# input.down?(
|
|
14
|
-
# input.axis(
|
|
11
|
+
# input.down?(Controls::KEY_SPACE) # keyboard
|
|
12
|
+
# input.down?(Controls::PAD_A, device: Controls.gamepad(0)) # player 1's pad
|
|
13
|
+
# input.axis(Controls::AXIS_LEFT_X, device: Controls.gamepad(0))
|
|
15
14
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
15
|
+
# **This is the raw query, and deliberately nothing more.** It used to carry
|
|
16
|
+
# binding tables of its own — `down?(:fire)` resolved `:fire` to a scancode
|
|
17
|
+
# through one of three tables passed to the constructor. Those are gone, and
|
|
18
|
+
# binding now lives one layer up in RGame::Engine::InputMap, for two
|
|
19
|
+
# reasons. A game's rebinding screen has to be able to edit the table, and
|
|
20
|
+
# the engine layer may not name RGame::Core at all; and with a player per
|
|
21
|
+
# device, the table is a per-player value rather than a property of the one
|
|
22
|
+
# object that talks to the hardware.
|
|
20
23
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
24
|
+
# What is left is an argument-order adapter over the app's own
|
|
25
|
+
# `input_down?(device, id)` / `input_axis(device, axis_id)`. It stays a named
|
|
26
|
+
# class rather than collapsing into those, because it is what gets handed to
|
|
27
|
+
# the engine layer as an input backend — and handing it a whole App, which
|
|
28
|
+
# can also close the window and rename it, would be a worse seam.
|
|
25
29
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# `device:` to the keyboard is what lets single-player call sites stay
|
|
29
|
-
# `input.down?(:fire)` with no ceremony.
|
|
30
|
+
# Ids come from RGame::Util::Controls. They are values with nothing behind
|
|
31
|
+
# them, so both layers can name one.
|
|
30
32
|
#
|
|
31
33
|
# **There is no pointer or mouse support, by design.** The layer this
|
|
32
34
|
# replaced had a cursor position and a click button riding the same "is
|
|
33
35
|
# held" path as keys, and none of it was carried over: this engine's input
|
|
34
|
-
# is keyboard and controllers.
|
|
35
|
-
#
|
|
36
|
-
# menus is keyboard and controller navigation instead.
|
|
36
|
+
# is keyboard and controllers. The intended answer for menus is keyboard and
|
|
37
|
+
# controller navigation instead.
|
|
37
38
|
class Input
|
|
38
39
|
Controls = RGame::Util::Controls
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
# `axis_bindings` the analog axes. All default to the standard tables.
|
|
42
|
-
def initialize(app,
|
|
43
|
-
bindings: Controls::DEFAULT_KEYBOARD,
|
|
44
|
-
pad_bindings: Controls::DEFAULT_PAD,
|
|
45
|
-
axis_bindings: Controls::DEFAULT_AXES)
|
|
41
|
+
def initialize(app)
|
|
46
42
|
@app = app
|
|
47
|
-
@bindings = bindings
|
|
48
|
-
@pad_bindings = pad_bindings
|
|
49
|
-
@axis_bindings = axis_bindings
|
|
50
43
|
end
|
|
51
44
|
|
|
52
|
-
# Is
|
|
45
|
+
# Is `id` held on `device`?
|
|
53
46
|
#
|
|
54
47
|
# Reads the engine's per-frame input snapshot, so the answer is identical
|
|
55
48
|
# for every simulation tick within one frame — a key held for a single
|
|
56
49
|
# frame behaves the same whether that frame ran one catch-up tick or five.
|
|
50
|
+
#
|
|
51
|
+
# A device only answers for its own kind of input: asking a gamepad about
|
|
52
|
+
# a keyboard scancode is `false`, never the keyboard's answer. That is
|
|
53
|
+
# what lets one binding table list a key and a pad button for the same
|
|
54
|
+
# action and still keep player two's pad from echoing player one.
|
|
57
55
|
# hot-path
|
|
58
|
-
def down?(
|
|
59
|
-
@app.input_down?(device,
|
|
56
|
+
def down?(id, device: Controls::KEYBOARD)
|
|
57
|
+
@app.input_down?(device, id)
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
# Current value of an analog axis: sticks -1.0..1.0, triggers 0.0..1.0.
|
|
63
61
|
# The keyboard has no axes, so it always reads 0.0.
|
|
62
|
+
#
|
|
63
|
+
# No dead zone is applied here — this is the hardware's answer. Ignoring
|
|
64
|
+
# a resting stick's jitter is RGame::Engine::ActionMapper's job.
|
|
64
65
|
# hot-path
|
|
65
|
-
def axis(
|
|
66
|
-
@app.input_axis(device,
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
# hot-path
|
|
72
|
-
def bindings_for(device)
|
|
73
|
-
device == Controls::KEYBOARD ? @bindings : @pad_bindings
|
|
66
|
+
def axis(axis_id, device: Controls::KEYBOARD)
|
|
67
|
+
@app.input_axis(device, axis_id)
|
|
74
68
|
end
|
|
75
69
|
end
|
|
76
70
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rgame/core_ext'
|
|
4
|
+
|
|
5
|
+
module RGame
|
|
6
|
+
# `RGame::Core.preferred_locales` is C (`ext/rgame_core/app/locale.c`, bound in
|
|
7
|
+
# `ruby/locale_ext.c`): the user's preferred locales as the operating system
|
|
8
|
+
# reports them through SDL, most preferred first.
|
|
9
|
+
#
|
|
10
|
+
# RGame::Core.preferred_locales # => ["de-AT", "en"]
|
|
11
|
+
#
|
|
12
|
+
# Each is a language, then a hyphen and a country when the OS names one. The
|
|
13
|
+
# list is `[]` when the OS names none — on Linux, under `LANG=C`. It needs no
|
|
14
|
+
# app: SDL reads it without being initialised, so it is a module function
|
|
15
|
+
# rather than an `App` method.
|
|
16
|
+
#
|
|
17
|
+
# What it returns is taken as given. SDL on Linux reads `LANG` and then
|
|
18
|
+
# `LANGUAGE`, and ignores `LC_ALL`, so the list can repeat a locale; choosing
|
|
19
|
+
# among them is `RGame::Engine::I18n.choose`, which the glue calls.
|
|
20
|
+
module Core
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -41,10 +41,6 @@ module RGame
|
|
|
41
41
|
# (x, y, w, h) is the source rectangle inside `image`, so one sheet can
|
|
42
42
|
# hold many of these — which is what UiAtlas does with it.
|
|
43
43
|
def initialize(image, x:, y:, w:, h:, border:, scale: 1)
|
|
44
|
-
# A zero or negative scale would give every tile a step of zero pixels,
|
|
45
|
-
# and `#draw`'s tiling loops would never advance. Refusing it here costs
|
|
46
|
-
# nothing; a guard inside the loop would cost a branch per tile, per
|
|
47
|
-
# widget, per frame.
|
|
48
44
|
raise ArgumentError, "nine-slice scale must be positive, got #{scale}" unless scale.positive?
|
|
49
45
|
|
|
50
46
|
@scale = scale
|
|
@@ -69,10 +65,6 @@ module RGame
|
|
|
69
65
|
r = @r * s
|
|
70
66
|
t = @t * s
|
|
71
67
|
b = @b * s
|
|
72
|
-
# These go negative for a rectangle narrower than its own borders, and
|
|
73
|
-
# are not clamped: #band refuses a non-positive size anyway, so clamping
|
|
74
|
-
# here would be a second guard that cannot change any outcome — and
|
|
75
|
-
# would read as if the case were handled in two places.
|
|
76
68
|
inner_w = dw - l - r
|
|
77
69
|
inner_h = dh - t - b
|
|
78
70
|
|
|
@@ -82,8 +74,6 @@ module RGame
|
|
|
82
74
|
band(renderer, @left, dx, dy + t, l, inner_h, z, color)
|
|
83
75
|
band(renderer, @right, dx + dw - r, dy + t, r, inner_h, z, color)
|
|
84
76
|
|
|
85
|
-
# Last, so a tile that reached the edge of its band is covered rather
|
|
86
|
-
# than showing through a corner's transparent pixels.
|
|
87
77
|
piece(renderer, @tl, dx, dy, z, color)
|
|
88
78
|
piece(renderer, @tr, dx + dw - r, dy, z, color)
|
|
89
79
|
piece(renderer, @bl, dx, dy + dh - b, z, color)
|
|
@@ -92,10 +82,6 @@ module RGame
|
|
|
92
82
|
|
|
93
83
|
private
|
|
94
84
|
|
|
95
|
-
# A uniform integer becomes four equal sides. Accepting both shapes here
|
|
96
|
-
# rather than in the callers is what makes `NineSlice.new(image, border: 7,
|
|
97
|
-
# ...)` work on its own — a bare integer would otherwise fail as
|
|
98
|
-
# `7[:left]`, which names nothing that appears in the caller's code.
|
|
99
85
|
def normalize_border(border)
|
|
100
86
|
return border.transform_keys(&:to_sym) unless border.is_a?(Integer)
|
|
101
87
|
|
|
@@ -122,11 +108,6 @@ module RGame
|
|
|
122
108
|
@centre = cut(image, x + @l, y + @t, centre_w, centre_h)
|
|
123
109
|
end
|
|
124
110
|
|
|
125
|
-
# A piece with no pixels is a legitimate part of a legitimate slice — a
|
|
126
|
-
# border with no centre column is a bar that only stretches vertically,
|
|
127
|
-
# and a zero border is one that does not stretch at all on that side. Such
|
|
128
|
-
# a piece is `nil` and simply never drawn; asking `Image#subimage` for it
|
|
129
|
-
# would raise.
|
|
130
111
|
def cut(image, x, y, width, height)
|
|
131
112
|
return nil unless width.positive? && height.positive?
|
|
132
113
|
|
|
@@ -139,8 +120,6 @@ module RGame
|
|
|
139
120
|
renderer.image_at(image, x, y, scale_x: @scale, scale_y: @scale, z: z, color: color)
|
|
140
121
|
end
|
|
141
122
|
|
|
142
|
-
# Repeats `image` across (bx, by, bw, bh), clipped to it so the trailing
|
|
143
|
-
# tile is cropped instead of overrunning into the next band.
|
|
144
123
|
def band(renderer, image, bx, by, bw, bh, z, color)
|
|
145
124
|
return if image.nil? || bw <= 0 || bh <= 0
|
|
146
125
|
|
data/lib/rgame/core/recording.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rgame/core_ext'
|
|
4
4
|
require_relative '../util/color'
|
|
5
|
+
require_relative '../util/z'
|
|
5
6
|
|
|
6
7
|
module RGame
|
|
7
8
|
module Core
|
|
@@ -37,6 +38,7 @@ module RGame
|
|
|
37
38
|
# renderer.clipped(0, 0, 400, 600) { ground.draw(0, 0) }
|
|
38
39
|
class Recording
|
|
39
40
|
Color = RGame::Util::Color
|
|
41
|
+
Z = RGame::Util::Z
|
|
40
42
|
|
|
41
43
|
# Replays everything that was recorded, with the recording's origin at
|
|
42
44
|
# (x, y).
|
|
@@ -45,7 +47,7 @@ module RGame
|
|
|
45
47
|
# so white (the default) draws the recording unchanged and a colour with
|
|
46
48
|
# alpha fades the whole layer out at once.
|
|
47
49
|
def draw(x = 0, y = 0, z: 0, color: nil)
|
|
48
|
-
draw_at(x, y, z, Color.coerce(color).packed)
|
|
50
|
+
draw_at(x, y, Z.offset(z), Color.coerce(color).packed)
|
|
49
51
|
end
|
|
50
52
|
end
|
|
51
53
|
end
|