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
data/lib/rgame/core/renderer.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'rgame/core_ext'
|
|
4
4
|
require_relative 'font'
|
|
5
5
|
require_relative '../util/color'
|
|
6
|
+
require_relative '../util/z'
|
|
6
7
|
|
|
7
8
|
module RGame
|
|
8
9
|
module Core
|
|
@@ -25,14 +26,27 @@ module RGame
|
|
|
25
26
|
# raises. That is on purpose: the frame is not open, so the vertices would
|
|
26
27
|
# be silently discarded, and an invisible failure is the worst kind.
|
|
27
28
|
#
|
|
28
|
-
# Nothing is drawn immediately. Calls accumulate and are
|
|
29
|
-
# frame closes, so
|
|
30
|
-
# z keeps call order, which is what stops same-layer sprites
|
|
31
|
-
# between frames.
|
|
29
|
+
# Nothing is drawn immediately. Calls accumulate and are sorted when the
|
|
30
|
+
# frame closes, so what ends up on top is decided by z rather than by call
|
|
31
|
+
# order. Equal z keeps call order, which is what stops same-layer sprites
|
|
32
|
+
# flickering between frames.
|
|
33
|
+
#
|
|
34
|
+
# ## `z:` is an offset inside the current layer, not a global number
|
|
35
|
+
#
|
|
36
|
+
# A `z:` is added to whatever `#layered` most recently pushed, and must be
|
|
37
|
+
# within RGame::Util::Z::Z_MIN..Z_MAX — one *node's* worth of room. It
|
|
38
|
+
# orders that node's own drawing (its panel under its label, a shadow under
|
|
39
|
+
# its sprite) and can reach nothing else. Which node comes before which is
|
|
40
|
+
# the scene graph's business, resolved by the traversal and handed here as a
|
|
41
|
+
# layer; see RGame::Util::Z.
|
|
42
|
+
#
|
|
43
|
+
# A caller outside any `#layered` block draws at the base layer 0, which is
|
|
44
|
+
# what a spec or a bare script gets.
|
|
32
45
|
#
|
|
33
46
|
# The C half of this class (ext/rgame_core/ruby/renderer_ext.c) has the `draw_*`
|
|
34
|
-
# and `push_*` primitives; everything here is the
|
|
35
|
-
# them
|
|
47
|
+
# and `push_*` primitives, registered private; everything here is the
|
|
48
|
+
# surface over them, so a draw cannot skip the z offset or the colour
|
|
49
|
+
# coercion, and a push cannot go without its pop.
|
|
36
50
|
#
|
|
37
51
|
# ## Colours
|
|
38
52
|
#
|
|
@@ -43,22 +57,16 @@ module RGame
|
|
|
43
57
|
class Renderer
|
|
44
58
|
Color = RGame::Util::Color
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# values match the layer this replaces.
|
|
60
|
+
Z = RGame::Util::Z
|
|
61
|
+
|
|
49
62
|
SHAPE_Z = 50
|
|
50
63
|
IMAGE_Z = 0
|
|
51
64
|
|
|
52
|
-
# Enough segments that a circle reads as round at the sizes a 2D game
|
|
53
|
-
# draws one, and few enough that a screenful of them is still one batch.
|
|
54
65
|
CIRCLE_SEGMENTS = 64
|
|
55
66
|
|
|
56
|
-
# Text defaults above sprites but below shapes, and the size matches what
|
|
57
|
-
# the layer this replaces used, so ported UI lays out unchanged.
|
|
58
67
|
TEXT_Z = 10
|
|
59
68
|
FONT_SIZE = 18
|
|
60
69
|
|
|
61
|
-
# Translucent red, for #debug_box.
|
|
62
70
|
DEBUG_BOX_COLOR = Color.new(255, 40, 40, 120)
|
|
63
71
|
|
|
64
72
|
# `assets:` is where a draw id that is not registered gets resolved from,
|
|
@@ -76,32 +84,16 @@ module RGame
|
|
|
76
84
|
|
|
77
85
|
attr_accessor :assets
|
|
78
86
|
|
|
79
|
-
# --- draw-by-id ---------------------------------------------------------
|
|
80
|
-
#
|
|
81
|
-
# Game logic names assets, it does not hold them: the engine layer may
|
|
82
|
-
# hold `RGame::Util` values but no `RGame::Core` handle at all, so a
|
|
83
|
-
# Symbol or a path is the only thing a node *can* carry. Resolving it is
|
|
84
|
-
# this side of the boundary's job.
|
|
85
|
-
#
|
|
86
|
-
# An id is normally a **root-relative path**, resolved through the asset
|
|
87
|
-
# manager and then remembered, so a per-frame draw neither re-resolves nor
|
|
88
|
-
# allocates a lookup key:
|
|
89
|
-
#
|
|
90
|
-
# renderer.sprite('example 09/player.json', row, col, x, y)
|
|
91
|
-
#
|
|
92
|
-
# `register_*` pre-binds an id to a chosen object, for the two things a
|
|
93
|
-
# path cannot name: an id that is not a file (nine-slice ids are *atlas
|
|
94
|
-
# element* names) and an object the game assembled itself.
|
|
95
|
-
|
|
96
87
|
def register_image(id, image) = registry(:image)[id] = image
|
|
97
88
|
def register_sheet(id, sheet) = registry(:sheet)[id] = sheet
|
|
98
89
|
def register_tilemap(id, tilemap) = registry(:tilemap)[id] = tilemap
|
|
99
90
|
def register_nine_slice(id, nine_slice) = registry(:nine_slice)[id] = nine_slice
|
|
100
91
|
|
|
101
|
-
# Registers every element of a UiAtlas
|
|
102
|
-
# names are what a widget asks for.
|
|
92
|
+
# Registers every element of a UiAtlas — its nine-slices and its images —
|
|
93
|
+
# under its own name, since those names are what a widget asks for.
|
|
103
94
|
def register_ui_atlas(atlas)
|
|
104
95
|
atlas.nine_slices.each { |id, nine_slice| register_nine_slice(id, nine_slice) }
|
|
96
|
+
atlas.images.each { |id, image| register_image(id, image) }
|
|
105
97
|
self
|
|
106
98
|
end
|
|
107
99
|
|
|
@@ -117,49 +109,54 @@ module RGame
|
|
|
117
109
|
lookup(:nine_slice, id).draw(self, x, y, width, height, z: z, color: tint)
|
|
118
110
|
end
|
|
119
111
|
|
|
120
|
-
#
|
|
112
|
+
# One layer of a tile map — the layer a Tiled `.tmx` lists at `layer`,
|
|
113
|
+
# counting from the bottom.
|
|
114
|
+
#
|
|
115
|
+
# One layer rather than the whole map, because a scene draws its actors
|
|
116
|
+
# between two of them: trunks under, canopies over. Which is which is the
|
|
117
|
+
# scene tree's business — `RGame::Engine::TileMapLayer` mounts a node per
|
|
118
|
+
# layer — so this takes no `z:`.
|
|
119
|
+
#
|
|
120
|
+
# **Drawn in world coordinates**: a tile at column 3 lands at
|
|
121
|
+
# `3 * tile_width`, and getting it onto the screen is the caller's
|
|
122
|
+
# transform, like every other drawing method here. The rectangle is a
|
|
123
|
+
# **cull rect** — which part of the world is worth drawing — so a camera
|
|
124
|
+
# supplies it but does not move the result. That is what lets one map be
|
|
125
|
+
# drawn through several cameras in a frame.
|
|
121
126
|
#
|
|
122
127
|
# `elapsed` is the seconds its animated tiles have been running for, and
|
|
123
128
|
# is an argument rather than a clock read on purpose — see CLAUDE.md,
|
|
124
129
|
# "`draw` renders state; time enters through `update`". A scene
|
|
125
130
|
# accumulates it in `update`, which is what makes pausing work.
|
|
126
|
-
def tilemap(id,
|
|
131
|
+
def tilemap(id, layer, cull_x, cull_y, cull_width, cull_height, elapsed: 0.0)
|
|
127
132
|
lookup(:tilemap, id)
|
|
128
|
-
.
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Its above-the-actor band (canopies, roofs), at a `z` the scene picks so
|
|
132
|
-
# it lands over the actors.
|
|
133
|
-
def tilemap_overlay(id, camera_x, camera_y, viewport_width, viewport_height,
|
|
134
|
-
z:, elapsed: 0.0)
|
|
135
|
-
lookup(:tilemap, id).draw_overlay(self, camera_x, camera_y, viewport_width,
|
|
136
|
-
viewport_height, z: z, elapsed: elapsed)
|
|
133
|
+
.draw_layer(self, layer, cull_x, cull_y, cull_width, cull_height, elapsed: elapsed)
|
|
137
134
|
end
|
|
138
135
|
|
|
139
136
|
# A filled axis-aligned rectangle.
|
|
140
137
|
def rect(x, y, width, height, z: SHAPE_Z, color: nil)
|
|
141
|
-
draw_rect(x, y, width, height, z, packed(color))
|
|
138
|
+
draw_rect(x, y, width, height, Z.offset(z), packed(color))
|
|
142
139
|
end
|
|
143
140
|
|
|
144
141
|
# Four arbitrary points, in loop order: listing them in Z order gives an
|
|
145
142
|
# hourglass rather than a shape.
|
|
146
143
|
def quad(x1, y1, x2, y2, x3, y3, x4, y4, z: SHAPE_Z, color: nil)
|
|
147
|
-
draw_quad(x1, y1, x2, y2, x3, y3, x4, y4, z, packed(color))
|
|
144
|
+
draw_quad(x1, y1, x2, y2, x3, y3, x4, y4, Z.offset(z), packed(color))
|
|
148
145
|
end
|
|
149
146
|
|
|
150
147
|
def triangle(x1, y1, x2, y2, x3, y3, z: SHAPE_Z, color: nil)
|
|
151
|
-
draw_triangle(x1, y1, x2, y2, x3, y3, z, packed(color))
|
|
148
|
+
draw_triangle(x1, y1, x2, y2, x3, y3, Z.offset(z), packed(color))
|
|
152
149
|
end
|
|
153
150
|
|
|
154
151
|
# A line of real thickness — drawn as a quad, because GL's own line width
|
|
155
152
|
# is a suggestion drivers may ignore above one pixel.
|
|
156
153
|
def line(x1, y1, x2, y2, thickness: 1.0, z: SHAPE_Z, color: nil)
|
|
157
|
-
draw_line(x1, y1, x2, y2, thickness, z, packed(color))
|
|
154
|
+
draw_line(x1, y1, x2, y2, thickness, Z.offset(z), packed(color))
|
|
158
155
|
end
|
|
159
156
|
|
|
160
157
|
# A filled circle, as a fan of triangles around its centre.
|
|
161
158
|
def circle(cx, cy, radius, z: SHAPE_Z, color: nil, segments: CIRCLE_SEGMENTS)
|
|
162
|
-
draw_circle(cx, cy, radius, segments, z, packed(color))
|
|
159
|
+
draw_circle(cx, cy, radius, segments, Z.offset(z), packed(color))
|
|
163
160
|
end
|
|
164
161
|
|
|
165
162
|
# An image centred on (cx, cy), rotated `angle` degrees clockwise about
|
|
@@ -168,7 +165,7 @@ module RGame
|
|
|
168
165
|
#
|
|
169
166
|
# Takes an `Image` or an id for one — see #resolve_image.
|
|
170
167
|
def image(image, cx, cy, angle: 0, scale: 1, z: IMAGE_Z, color: nil)
|
|
171
|
-
draw_image_rot(resolve_image(image), cx, cy, angle, scale, z, packed(color))
|
|
168
|
+
draw_image_rot(resolve_image(image), cx, cy, angle, scale, Z.offset(z), packed(color))
|
|
172
169
|
end
|
|
173
170
|
|
|
174
171
|
# An image with its top-left at (x, y), scaled independently per axis —
|
|
@@ -183,14 +180,14 @@ module RGame
|
|
|
183
180
|
#
|
|
184
181
|
# A zero scale draws nothing.
|
|
185
182
|
def image_at(image, x, y, scale_x: 1, scale_y: 1, z: IMAGE_Z, color: nil)
|
|
186
|
-
draw_image_scaled(resolve_image(image), x, y, scale_x, scale_y, z, packed(color))
|
|
183
|
+
draw_image_scaled(resolve_image(image), x, y, scale_x, scale_y, Z.offset(z), packed(color))
|
|
187
184
|
end
|
|
188
185
|
|
|
189
186
|
# An image with its top-left at (x, y), at its natural size — a
|
|
190
187
|
# full-screen backdrop by default. `image_at` with both scales at 1, kept
|
|
191
188
|
# because "put this at the origin" is worth a name of its own.
|
|
192
189
|
def background(image, x = 0, y = 0, z: IMAGE_Z, color: nil)
|
|
193
|
-
draw_image(resolve_image(image), x, y, z, packed(color))
|
|
190
|
+
draw_image(resolve_image(image), x, y, Z.offset(z), packed(color))
|
|
194
191
|
end
|
|
195
192
|
|
|
196
193
|
# Everything drawn in the block is rotated `angle` degrees about
|
|
@@ -206,9 +203,6 @@ module RGame
|
|
|
206
203
|
begin
|
|
207
204
|
yield
|
|
208
205
|
ensure
|
|
209
|
-
# An ensure, not a plain pop: a scene that raises mid-draw would
|
|
210
|
-
# otherwise leave the stack deeper than it found it, and every
|
|
211
|
-
# later frame would draw askew for a reason nothing points at.
|
|
212
206
|
pop
|
|
213
207
|
end
|
|
214
208
|
end
|
|
@@ -239,6 +233,31 @@ module RGame
|
|
|
239
233
|
end
|
|
240
234
|
end
|
|
241
235
|
|
|
236
|
+
# Everything drawn in the block draws in its own layer: a fresh slot in
|
|
237
|
+
# `band`, which every `z:` inside is then an offset from.
|
|
238
|
+
#
|
|
239
|
+
# renderer.layered(:hud) { renderer.text(score, 12, 10) }
|
|
240
|
+
#
|
|
241
|
+
# Two things fall out of it, and they are the whole of draw order.
|
|
242
|
+
# **Slots are handed out in the order they are asked for**, so nesting
|
|
243
|
+
# this the way a scene graph is nested makes draw order tree order — a
|
|
244
|
+
# node drawn later is in front, and its whole subtree with it. And **a
|
|
245
|
+
# band is a hard partition**: every slot in `:hud` is above every slot in
|
|
246
|
+
# `:world`, whatever either drew, because they are 2**40 apart and a `z:`
|
|
247
|
+
# cannot reach out of one slot. See RGame::Util::Z.
|
|
248
|
+
#
|
|
249
|
+
# A caller that never uses this draws at layer 0 and gets exactly the z it
|
|
250
|
+
# passes, which is what a spec or a one-off script wants.
|
|
251
|
+
def layered(band = Z::DEFAULT)
|
|
252
|
+
index = Z.index(band)
|
|
253
|
+
push_layer(Z.slot_base(index, next_layer_slot(index)))
|
|
254
|
+
begin
|
|
255
|
+
yield
|
|
256
|
+
ensure
|
|
257
|
+
pop
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
242
261
|
# Everything drawn in the block is confined to the given rectangle.
|
|
243
262
|
#
|
|
244
263
|
# A clip only ever narrows: nesting one inside another intersects them, so
|
|
@@ -271,7 +290,7 @@ module RGame
|
|
|
271
290
|
# Newlines are not special. A caller wanting two lines draws two, stepping
|
|
272
291
|
# by #text_height.
|
|
273
292
|
def text(string, x, y, z: TEXT_Z, color: nil, font: nil)
|
|
274
|
-
draw_text(font || self.font, string, x, y, z, packed(color))
|
|
293
|
+
draw_text(font || self.font, string, x, y, Z.offset(z), packed(color))
|
|
275
294
|
end
|
|
276
295
|
|
|
277
296
|
# What #text would occupy, for centring and layout. Unlike the drawing
|
|
@@ -299,9 +318,6 @@ module RGame
|
|
|
299
318
|
yield
|
|
300
319
|
completed = true
|
|
301
320
|
ensure
|
|
302
|
-
# A block that raised leaves a half-built recording open, and the
|
|
303
|
-
# next frame would keep drawing into it. Unwinding here means the
|
|
304
|
-
# exception is the only thing the caller has to deal with.
|
|
305
321
|
cancel_record unless completed
|
|
306
322
|
end
|
|
307
323
|
end_record
|
|
@@ -315,44 +331,19 @@ module RGame
|
|
|
315
331
|
|
|
316
332
|
private
|
|
317
333
|
|
|
318
|
-
# Colours cross into C as a packed integer. `Color.coerce` is the single
|
|
319
|
-
# place nil/array/Color are turned into one, so no drawing method has its
|
|
320
|
-
# own idea of what a colour is.
|
|
321
334
|
def packed(color) = Color.coerce(color).packed
|
|
322
335
|
|
|
323
|
-
# An `Image` is already what it is; anything else is an id for one.
|
|
324
|
-
#
|
|
325
|
-
# The two callers this serves cannot be reconciled any other way. Core's
|
|
326
|
-
# own drawing classes — SpriteSheet, NineSlice — hold real images and pass
|
|
327
|
-
# them; the engine layer is forbidden from holding one and can only pass
|
|
328
|
-
# an id. Dispatching on the type keeps both spellings of `#image` and
|
|
329
|
-
# `#background` working without two parallel method names for the same
|
|
330
|
-
# picture.
|
|
331
336
|
def resolve_image(image) = image.is_a?(Image) ? image : lookup(:image, image)
|
|
332
337
|
|
|
333
338
|
def registry(type) = (@registries ||= {})[type] ||= {}
|
|
334
339
|
|
|
335
|
-
# Prefer an explicit registration, otherwise ask the asset manager, then
|
|
336
|
-
# remember the answer — so a per-frame draw neither re-resolves nor
|
|
337
|
-
# allocates a lookup key.
|
|
338
340
|
def lookup(type, id)
|
|
339
|
-
# `nil` is never an id, and reporting it as one ("no image registered
|
|
340
|
-
# for nil") describes a typo when the actual bug is an asset that
|
|
341
|
-
# resolved to nothing. The two want different fixes.
|
|
342
341
|
raise TypeError, "no implicit conversion of nil into #{type}" if id.nil?
|
|
343
342
|
|
|
344
343
|
table = registry(type)
|
|
345
344
|
table.fetch(id) { table[id] = resolve_asset(type, id) }
|
|
346
345
|
end
|
|
347
346
|
|
|
348
|
-
# Only a String is offered to the asset manager, because only a String can
|
|
349
|
-
# be a path. A Symbol id is a name a game chose, so a missing one is the
|
|
350
|
-
# KeyError below — naming the id — rather than whatever the manager makes
|
|
351
|
-
# of being handed a Symbol where it wanted a filename.
|
|
352
|
-
#
|
|
353
|
-
# `respond_to?` for the same reason one step along: the manager grows an
|
|
354
|
-
# accessor per asset type, and asking for one it does not have yet should
|
|
355
|
-
# be this error rather than a NoMethodError from inside it.
|
|
356
347
|
def resolve_asset(type, id)
|
|
357
348
|
resolved = @assets.public_send(type, id) if id.is_a?(String) && @assets.respond_to?(type)
|
|
358
349
|
resolved || raise(KeyError, "no #{type} registered for #{id.inspect} " \
|
|
@@ -97,9 +97,6 @@ module RGame
|
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
# A typo'd or missing key would otherwise surface as a NoMethodError on nil
|
|
101
|
-
# from inside the slicing arithmetic, which says nothing about the file
|
|
102
|
-
# that is actually wrong.
|
|
103
100
|
def missing(key)
|
|
104
101
|
raise ArgumentError, "sprite sheet descriptor has no #{key}"
|
|
105
102
|
end
|
|
@@ -3,31 +3,53 @@
|
|
|
3
3
|
module RGame
|
|
4
4
|
module Core
|
|
5
5
|
# Draws a tile map: the static layers baked once, the animated tiles drawn
|
|
6
|
-
# per frame
|
|
6
|
+
# per frame, both culled to a rectangle of the world.
|
|
7
7
|
#
|
|
8
8
|
# tiles = RGame::Core::TileMapRenderer.new(map, tileset_images)
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# map.layer_count.times do |layer|
|
|
11
|
+
# tiles.draw_layer(renderer, layer, cull_x, cull_y, cull_w, cull_h,
|
|
12
12
|
# elapsed: seconds)
|
|
13
|
+
# end
|
|
13
14
|
#
|
|
14
|
-
# ##
|
|
15
|
+
# ## It draws in world coordinates
|
|
15
16
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
17
|
+
# A tile at column 3 is drawn at `3 * tile_width`, and getting it onto the
|
|
18
|
+
# screen is the caller's transform — the same deal every other drawable
|
|
19
|
+
# gets. The rectangle passed in is therefore a **cull rect** and nothing
|
|
20
|
+
# else: which part of the world is worth drawing.
|
|
21
|
+
#
|
|
22
|
+
# It used to be both, offsetting the output by `-camera` as well as culling
|
|
23
|
+
# to it, which worked exactly as long as there was one camera. Under
|
|
24
|
+
# split-screen the same map is drawn through several, so a call that bakes
|
|
25
|
+
# placement into its output can only be right for one of them. Culling is
|
|
26
|
+
# genuinely per-camera; placement is the transform stack's job.
|
|
27
|
+
#
|
|
28
|
+
# ## One call per layer, because the actors go between them
|
|
29
|
+
#
|
|
30
|
+
# A layer is drawn on its own, and the order they are drawn in is the
|
|
31
|
+
# caller's. That is what lets a scene put its actors between two of them —
|
|
32
|
+
# tree trunks below, canopies above — which is the whole reason this does
|
|
33
|
+
# not simply draw the map in one go.
|
|
34
|
+
#
|
|
35
|
+
# It is also why nothing here consults the map's `above_layer?` flag any
|
|
36
|
+
# more: which layers cover the actors is a question about where the actors
|
|
37
|
+
# are in the scene, and Tiled already answers "in what order do the layers
|
|
38
|
+
# go" by listing them. `RGame::Engine::TileMapLayer` mounts one node per
|
|
39
|
+
# layer and the tree does the rest.
|
|
22
40
|
#
|
|
23
41
|
# ## What is baked and what is not
|
|
24
42
|
#
|
|
25
|
-
# Within each
|
|
26
|
-
# recording, the first time that
|
|
43
|
+
# Within each layer, every tile that is *not* animated is baked into one
|
|
44
|
+
# recording, the first time that layer is drawn. Scrolling a baked layer is
|
|
27
45
|
# then one call per texture however many thousand tiles went into it. The
|
|
28
46
|
# handful that *are* animated are drawn individually each frame, culled to
|
|
29
47
|
# the viewport — a map far larger than the screen costs only what is on it.
|
|
30
48
|
#
|
|
49
|
+
# Splitting per layer rather than into two bands bakes the same tiles into
|
|
50
|
+
# more recordings, not more vertices: the partition changed, the contents
|
|
51
|
+
# did not.
|
|
52
|
+
#
|
|
31
53
|
# ## It loads nothing and holds no clock
|
|
32
54
|
#
|
|
33
55
|
# The tiles arrive already sliced, so two maps sharing a tileset share one
|
|
@@ -44,9 +66,9 @@ module RGame
|
|
|
44
66
|
# It never names the map's class — the tile map lives a layer *above* this
|
|
45
67
|
# one and Core may not reach up (CLAUDE.md, "The rule points both ways").
|
|
46
68
|
# What it calls is the 'a tile map' contract in
|
|
47
|
-
# `spec/support/shared_examples/`: `layer_count`, `
|
|
48
|
-
# `
|
|
49
|
-
# `
|
|
69
|
+
# `spec/support/shared_examples/`: `layer_count`, `width`, `height`,
|
|
70
|
+
# `tile_width`, `tile_height`, `gid`, and a `tileset` answering `local_id`,
|
|
71
|
+
# `animations` and `frame_local_id`.
|
|
50
72
|
class TileMapRenderer
|
|
51
73
|
# The map this was built from. A scene reads it for collision and world
|
|
52
74
|
# bounds, which are its business rather than this class's.
|
|
@@ -58,50 +80,51 @@ module RGame
|
|
|
58
80
|
@map = map
|
|
59
81
|
@tileset = map.tileset
|
|
60
82
|
@tiles = tiles
|
|
61
|
-
@
|
|
62
|
-
|
|
63
|
-
# is no renderer at construction.
|
|
64
|
-
@static_below = nil
|
|
65
|
-
@static_above = nil
|
|
83
|
+
@animated = collect_animated_tiles
|
|
84
|
+
@static = Array.new(map.layer_count)
|
|
66
85
|
end
|
|
67
86
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
def layer_count = @map.layer_count
|
|
88
|
+
|
|
89
|
+
# One layer, culled to `(cull_x, cull_y, cull_width, cull_height)` in world
|
|
90
|
+
# coordinates and drawn in them.
|
|
91
|
+
#
|
|
92
|
+
# The recording is replayed at its own origin, so it lands wherever the
|
|
93
|
+
# caller's transform puts it. That also makes it **view-independent**: one
|
|
94
|
+
# bake serves every viewport, which is what keeps split-screen affordable
|
|
95
|
+
# and is why the bake is not keyed on a camera. Baking happens on the
|
|
96
|
+
# first draw, and it is safe to do that inside a transform or a clip —
|
|
97
|
+
# recording runs on its own canvas, begun at identity, and captures
|
|
98
|
+
# neither.
|
|
99
|
+
#
|
|
100
|
+
# No `z:`. A layer is drawn by a node of its own, so where it sits is the
|
|
101
|
+
# scene tree's answer; everything this issues belongs to that one node and
|
|
102
|
+
# goes in its slot.
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
105
|
+
def draw_layer(renderer, index, cull_x, cull_y, cull_width, cull_height, elapsed: 0.0)
|
|
106
|
+
unless index.is_a?(Integer) && index >= 0 && index < @static.size
|
|
107
|
+
raise ArgumentError, "no layer #{index.inspect} in this map (it has #{@static.size})"
|
|
108
|
+
end
|
|
74
109
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
draw_animated(renderer, @animated_above, camera_x, camera_y,
|
|
80
|
-
viewport_width, viewport_height, z, elapsed)
|
|
110
|
+
@static[index] ||= bake(renderer, index)
|
|
111
|
+
@static[index].draw
|
|
112
|
+
draw_animated(renderer, @animated[index], cull_x, cull_y,
|
|
113
|
+
cull_width, cull_height, elapsed)
|
|
81
114
|
end
|
|
82
115
|
|
|
83
|
-
# The ground band sits at the bottom; the scene chooses where the overlay
|
|
84
|
-
# goes, because only it knows what its actors are drawn at.
|
|
85
|
-
BELOW_Z = 0
|
|
86
|
-
|
|
87
116
|
private
|
|
88
117
|
|
|
89
|
-
# [col, row, local_id] for every animated tile, split into the two bands.
|
|
90
|
-
# Walked once at construction: a map is thousands of tiles and a handful
|
|
91
|
-
# of animated ones, and finding them again each frame would be the whole
|
|
92
|
-
# cost this class exists to avoid.
|
|
93
118
|
def collect_animated_tiles
|
|
94
|
-
|
|
95
|
-
above = []
|
|
119
|
+
found = Array.new(@map.layer_count) { [] }
|
|
96
120
|
each_tile do |layer, col, row, local|
|
|
97
121
|
next unless @tileset.animations.key?(local)
|
|
98
122
|
|
|
99
|
-
|
|
123
|
+
found[layer] << [col, row, local]
|
|
100
124
|
end
|
|
101
|
-
|
|
125
|
+
found
|
|
102
126
|
end
|
|
103
127
|
|
|
104
|
-
# Every non-empty tile of every layer, as [layer, col, row, local_id].
|
|
105
128
|
def each_tile
|
|
106
129
|
@map.layer_count.times do |layer|
|
|
107
130
|
@map.height.times do |row|
|
|
@@ -115,44 +138,33 @@ module RGame
|
|
|
115
138
|
end
|
|
116
139
|
end
|
|
117
140
|
|
|
118
|
-
|
|
119
|
-
# recording, in layer order so a later layer covers an earlier one.
|
|
120
|
-
def bake(renderer)
|
|
141
|
+
def bake(renderer, index)
|
|
121
142
|
renderer.record do
|
|
122
143
|
each_tile do |layer, col, row, local|
|
|
123
|
-
next unless
|
|
144
|
+
next unless layer == index
|
|
124
145
|
next if @tileset.animations.key?(local)
|
|
125
146
|
|
|
126
|
-
renderer.image_at(@tiles[local], col * @map.tile_width, row * @map.tile_height
|
|
127
|
-
z: BELOW_Z)
|
|
147
|
+
renderer.image_at(@tiles[local], col * @map.tile_width, row * @map.tile_height)
|
|
128
148
|
end
|
|
129
149
|
end
|
|
130
150
|
end
|
|
131
151
|
|
|
132
|
-
def draw_animated(renderer, tiles,
|
|
133
|
-
z, elapsed)
|
|
152
|
+
def draw_animated(renderer, tiles, cull_x, cull_y, cull_width, cull_height, elapsed)
|
|
134
153
|
tile_width = @map.tile_width
|
|
135
154
|
tile_height = @map.tile_height
|
|
136
155
|
|
|
137
|
-
# Tiled writes frame durations in milliseconds; everything here counts
|
|
138
|
-
# in seconds. Converted once per draw rather than once per tile.
|
|
139
156
|
ms = (elapsed * 1000.0).to_i
|
|
140
157
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
# the camera happens to be on a whole pixel.
|
|
146
|
-
col_start = camera_x.fdiv(tile_width).floor
|
|
147
|
-
row_start = camera_y.fdiv(tile_height).floor
|
|
148
|
-
col_end = (camera_x + viewport_width).fdiv(tile_width).ceil
|
|
149
|
-
row_end = (camera_y + viewport_height).fdiv(tile_height).ceil
|
|
158
|
+
col_start = cull_x.fdiv(tile_width).floor
|
|
159
|
+
row_start = cull_y.fdiv(tile_height).floor
|
|
160
|
+
col_end = (cull_x + cull_width).fdiv(tile_width).ceil
|
|
161
|
+
row_end = (cull_y + cull_height).fdiv(tile_height).ceil
|
|
150
162
|
|
|
151
163
|
tiles.each do |col, row, local|
|
|
152
164
|
next if col < col_start || col >= col_end || row < row_start || row >= row_end
|
|
153
165
|
|
|
154
166
|
renderer.image_at(@tiles[@tileset.frame_local_id(local, ms)],
|
|
155
|
-
|
|
167
|
+
col * tile_width, row * tile_height)
|
|
156
168
|
end
|
|
157
169
|
end
|
|
158
170
|
end
|
data/lib/rgame/core/ui_atlas.rb
CHANGED
|
@@ -7,11 +7,12 @@ require_relative 'nine_slice'
|
|
|
7
7
|
|
|
8
8
|
module RGame
|
|
9
9
|
module Core
|
|
10
|
-
# One sheet of UI chrome, cut into named nine-slices.
|
|
10
|
+
# One sheet of UI chrome, cut into named nine-slices and named images.
|
|
11
11
|
#
|
|
12
12
|
# atlas = RGame::Core::UiAtlas.load(app, 'media/ui.json')
|
|
13
13
|
# renderer.register_ui_atlas(atlas)
|
|
14
14
|
# renderer.nine_slice(:button_idle, x, y, width, height)
|
|
15
|
+
# renderer.image(:home, cx, cy)
|
|
15
16
|
#
|
|
16
17
|
# A button has four states, a panel has one, a scrollbar has three pieces —
|
|
17
18
|
# all of them small, and all of them cheaper as sub-rectangles of a single
|
|
@@ -29,27 +30,38 @@ module RGame
|
|
|
29
30
|
# "panel": { "x": 0, "y": 0, "w": 32, "h": 32,
|
|
30
31
|
# "border": { "left": 4, "right": 4, "top": 8, "bottom": 4 },
|
|
31
32
|
# "scale": 2 }
|
|
33
|
+
# },
|
|
34
|
+
# "images": {
|
|
35
|
+
# "home": { "x": 0, "y": 96, "w": 50, "h": 50 },
|
|
36
|
+
# "gear": { "x": 50, "y": 96, "w": 50, "h": 50 }
|
|
32
37
|
# }
|
|
33
38
|
# }
|
|
34
39
|
#
|
|
35
|
-
# `image` is resolved next to the descriptor. Each entry is a
|
|
36
|
-
# rectangle plus a border — a uniform integer or one value per side —
|
|
37
|
-
# optional `scale` overriding the sheet default. See
|
|
40
|
+
# `image` is resolved next to the descriptor. Each nine-slice entry is a
|
|
41
|
+
# source rectangle plus a border — a uniform integer or one value per side —
|
|
42
|
+
# and an optional `scale` overriding the sheet default. See
|
|
38
43
|
# RGame::Core::NineSlice for what those mean when it is drawn.
|
|
39
44
|
#
|
|
45
|
+
# Each `images` entry is a rectangle and nothing else: an icon is drawn
|
|
46
|
+
# whole, so it has no border, and no scale either, because how large to draw
|
|
47
|
+
# it is the draw call's `scale:`. Either section may be absent or `null`.
|
|
48
|
+
#
|
|
40
49
|
# ## Element names, not filenames
|
|
41
50
|
#
|
|
42
|
-
#
|
|
51
|
+
# Both sections are keyed by whatever the descriptor calls each element, and
|
|
43
52
|
# those names are what a widget asks for. That is why nine-slices are the
|
|
44
53
|
# one asset the renderer resolves by registration only: `:button_focus` is
|
|
45
54
|
# not a file and never can be. `Renderer#register_ui_atlas` registers every
|
|
46
|
-
# element in one call.
|
|
55
|
+
# element of both kinds in one call.
|
|
47
56
|
#
|
|
48
57
|
# Parsing happens once, at load. Nothing here is touched again per frame.
|
|
49
58
|
class UiAtlas
|
|
50
|
-
# The elements, by name. Values are `NineSlice`s.
|
|
59
|
+
# The nine-slice elements, by name. Values are `NineSlice`s.
|
|
51
60
|
attr_reader :nine_slices
|
|
52
61
|
|
|
62
|
+
# The image elements, by name. Values are `Image`s cut from the sheet.
|
|
63
|
+
attr_reader :images
|
|
64
|
+
|
|
53
65
|
# Loads a descriptor and the sheet beside it.
|
|
54
66
|
#
|
|
55
67
|
# The `AssetManager` uses `.new` instead, with an image it has already
|
|
@@ -65,19 +77,22 @@ module RGame
|
|
|
65
77
|
sheet_scale = data[:scale] || 1
|
|
66
78
|
|
|
67
79
|
@nine_slices = (data[:nine_slices] || {}).to_h do |id, spec|
|
|
68
|
-
[id,
|
|
80
|
+
[id, element(id) { build_nine_slice(image, spec, sheet_scale) }]
|
|
81
|
+
end
|
|
82
|
+
@images = (data[:images] || {}).to_h do |id, spec|
|
|
83
|
+
[id, element(id) { image.subimage(spec[:x], spec[:y], spec[:w], spec[:h]) }]
|
|
69
84
|
end
|
|
70
85
|
end
|
|
71
86
|
|
|
72
87
|
private
|
|
73
88
|
|
|
74
|
-
|
|
75
|
-
# from inside NineSlice's arithmetic — "undefined method '-' for nil" says
|
|
76
|
-
# nothing about *which* button is wrong. Naming the element is the whole
|
|
77
|
-
# value this wrapper adds, so it catches broadly on purpose.
|
|
78
|
-
def build(image, id, spec, sheet_scale)
|
|
89
|
+
def build_nine_slice(image, spec, sheet_scale)
|
|
79
90
|
NineSlice.new(image, x: spec[:x], y: spec[:y], w: spec[:w], h: spec[:h],
|
|
80
91
|
border: spec[:border], scale: spec[:scale] || sheet_scale)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def element(id)
|
|
95
|
+
yield
|
|
81
96
|
rescue StandardError => e
|
|
82
97
|
raise ArgumentError, "ui atlas element #{id.inspect}: #{e.message}"
|
|
83
98
|
end
|
data/lib/rgame/core.rb
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# RGame::Core — everything that depends on SDL, OpenGL, or anything built on
|
|
4
|
-
# them. Requiring this file loads those libraries into the process, which is why
|
|
5
|
-
# it is NOT required from lib/rgame.rb; ask for it explicitly:
|
|
6
|
-
#
|
|
7
|
-
# require "rgame/core"
|
|
8
|
-
#
|
|
9
|
-
# Its counterpart is RGame::Util (see lib/rgame/util.rb), which stays free of
|
|
10
|
-
# graphics dependencies.
|
|
11
3
|
require_relative 'version'
|
|
12
4
|
require_relative 'core/app'
|
|
5
|
+
require_relative 'core/locale'
|
|
13
6
|
require_relative 'core/input'
|
|
14
7
|
require_relative 'core/gamepad'
|
|
15
8
|
require_relative 'core/image'
|