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/util/color.rb
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# RGame::Util::Color is implemented in C — see ext/rgame_util/color_ext.c for
|
|
4
|
-
# the Ruby binding and ext/rgame_util/color.c for the packing arithmetic.
|
|
5
|
-
#
|
|
6
|
-
# Color = RGame::Util::Color
|
|
7
|
-
# Color.new(255, 128, 0) # r, g, b, a defaults to 255
|
|
8
|
-
# Color.rgba(255, 128, 0, 200) # the same thing, named
|
|
9
|
-
# Color.from_packed(0xFF8000FF) # 0xRRGGBBAA
|
|
10
|
-
# Color::WHITE # also BLACK and TRANSPARENT
|
|
11
|
-
#
|
|
12
|
-
# c.r; c.g; c.b; c.a # => Integer, 0..255
|
|
13
|
-
# c.packed # => Integer, 0xRRGGBBAA
|
|
14
|
-
#
|
|
15
|
-
# Instances are frozen and compare by value, so a Color works as a Hash key and
|
|
16
|
-
# can be shared without anyone tinting it out from under its other holders.
|
|
17
|
-
#
|
|
18
|
-
# Every draw call accepts a colour as nil (white), [r, g, b], [r, g, b, a] or a
|
|
19
|
-
# Color; `Color.coerce` is the single place that conversion happens.
|
|
20
|
-
#
|
|
21
|
-
# A colour is a *value* — no window, no GPU handle — so it lives in Util rather
|
|
22
|
-
# than Core. That is what lets the engine layer hold one as an attribute; see
|
|
23
|
-
# CLAUDE.md, "Value objects go in Util; only handle-owners go in Core".
|
|
24
|
-
#
|
|
25
|
-
# It loads from lib/rgame/util_ext.so, which `make ext-util` copies out of
|
|
26
|
-
# ext/rgame_util/.
|
|
27
3
|
require 'rgame/util_ext'
|
|
4
|
+
|
|
5
|
+
module RGame
|
|
6
|
+
module Util
|
|
7
|
+
class Color
|
|
8
|
+
RED = new(255, 0, 0)
|
|
9
|
+
GREEN = new(0, 255, 0)
|
|
10
|
+
BLUE = new(0, 0, 255)
|
|
11
|
+
YELLOW = new(255, 255, 0)
|
|
12
|
+
CYAN = new(0, 255, 255)
|
|
13
|
+
MAGENTA = new(255, 0, 255)
|
|
14
|
+
ORANGE = new(255, 165, 0)
|
|
15
|
+
PURPLE = new(128, 0, 128)
|
|
16
|
+
BROWN = new(139, 69, 19)
|
|
17
|
+
PINK = new(255, 192, 203)
|
|
18
|
+
GRAY = new(128, 128, 128)
|
|
19
|
+
LIGHT_GRAY = new(192, 192, 192)
|
|
20
|
+
DARK_GRAY = new(64, 64, 64)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/rgame/util/controls.rb
CHANGED
|
@@ -10,8 +10,14 @@ module RGame
|
|
|
10
10
|
# layer and a game's own configuration name a physical input without
|
|
11
11
|
# touching RGame::Core, which they may not do:
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
# RGame::
|
|
13
|
+
# controls = RGame::Util::Controls
|
|
14
|
+
# map = RGame::Engine::InputMap.default.merge(
|
|
15
|
+
# fire: { buttons: [controls::KEY_SPACE, controls::PAD_A] }
|
|
16
|
+
# )
|
|
17
|
+
#
|
|
18
|
+
# This module is the **vocabulary** only. What an id *means* to a game — the
|
|
19
|
+
# binding table — is RGame::Engine::InputMap, one per player, because two
|
|
20
|
+
# players share a game's actions but not the buttons that trigger them.
|
|
15
21
|
#
|
|
16
22
|
# The same numbers exist as `#define`s in
|
|
17
23
|
# ext/rgame_core/include/rgame/core.h, because the C engine and the
|
|
@@ -20,17 +26,97 @@ module RGame
|
|
|
20
26
|
# by _Static_assert at compile time, and spec/rgame/util/controls_spec.rb
|
|
21
27
|
# parses that header and compares every value here against it.
|
|
22
28
|
module Controls
|
|
23
|
-
|
|
29
|
+
KEY_A = 4
|
|
30
|
+
KEY_B = 5
|
|
31
|
+
KEY_C = 6
|
|
32
|
+
KEY_D = 7
|
|
33
|
+
KEY_E = 8
|
|
34
|
+
KEY_F = 9
|
|
35
|
+
KEY_G = 10
|
|
36
|
+
KEY_H = 11
|
|
37
|
+
KEY_I = 12
|
|
38
|
+
KEY_J = 13
|
|
39
|
+
KEY_K = 14
|
|
40
|
+
KEY_L = 15
|
|
41
|
+
KEY_M = 16
|
|
42
|
+
KEY_N = 17
|
|
43
|
+
KEY_O = 18
|
|
44
|
+
KEY_P = 19
|
|
45
|
+
KEY_Q = 20
|
|
46
|
+
KEY_R = 21
|
|
47
|
+
KEY_S = 22
|
|
48
|
+
KEY_T = 23
|
|
49
|
+
KEY_U = 24
|
|
50
|
+
KEY_V = 25
|
|
51
|
+
KEY_W = 26
|
|
52
|
+
KEY_X = 27
|
|
53
|
+
KEY_Y = 28
|
|
54
|
+
KEY_Z = 29
|
|
55
|
+
|
|
56
|
+
KEY_1 = 30
|
|
57
|
+
KEY_2 = 31
|
|
58
|
+
KEY_3 = 32
|
|
59
|
+
KEY_4 = 33
|
|
60
|
+
KEY_5 = 34
|
|
61
|
+
KEY_6 = 35
|
|
62
|
+
KEY_7 = 36
|
|
63
|
+
KEY_8 = 37
|
|
64
|
+
KEY_9 = 38
|
|
65
|
+
KEY_0 = 39
|
|
66
|
+
|
|
24
67
|
KEY_RETURN = 40
|
|
25
68
|
KEY_ESCAPE = 41
|
|
69
|
+
KEY_BACKSPACE = 42
|
|
70
|
+
KEY_TAB = 43
|
|
26
71
|
KEY_SPACE = 44
|
|
72
|
+
|
|
73
|
+
KEY_MINUS = 45
|
|
74
|
+
KEY_EQUALS = 46
|
|
75
|
+
KEY_LEFTBRACKET = 47
|
|
76
|
+
KEY_RIGHTBRACKET = 48
|
|
77
|
+
KEY_BACKSLASH = 49
|
|
78
|
+
KEY_SEMICOLON = 51
|
|
79
|
+
KEY_APOSTROPHE = 52
|
|
80
|
+
KEY_GRAVE = 53
|
|
81
|
+
KEY_COMMA = 54
|
|
82
|
+
KEY_PERIOD = 55
|
|
83
|
+
KEY_SLASH = 56
|
|
84
|
+
|
|
85
|
+
KEY_CAPSLOCK = 57
|
|
27
86
|
KEY_F1 = 58
|
|
87
|
+
KEY_F2 = 59
|
|
88
|
+
KEY_F3 = 60
|
|
89
|
+
KEY_F4 = 61
|
|
90
|
+
KEY_F5 = 62
|
|
91
|
+
KEY_F6 = 63
|
|
92
|
+
KEY_F7 = 64
|
|
93
|
+
KEY_F8 = 65
|
|
94
|
+
KEY_F9 = 66
|
|
95
|
+
KEY_F10 = 67
|
|
96
|
+
KEY_F11 = 68
|
|
97
|
+
KEY_F12 = 69
|
|
98
|
+
|
|
99
|
+
KEY_INSERT = 73
|
|
100
|
+
KEY_HOME = 74
|
|
101
|
+
KEY_PAGEUP = 75
|
|
102
|
+
KEY_DELETE = 76
|
|
103
|
+
KEY_END = 77
|
|
104
|
+
KEY_PAGEDOWN = 78
|
|
105
|
+
|
|
28
106
|
KEY_RIGHT = 79
|
|
29
107
|
KEY_LEFT = 80
|
|
30
108
|
KEY_DOWN = 81
|
|
31
109
|
KEY_UP = 82
|
|
32
110
|
|
|
33
|
-
|
|
111
|
+
KEY_LCTRL = 224
|
|
112
|
+
KEY_LSHIFT = 225
|
|
113
|
+
KEY_LALT = 226
|
|
114
|
+
KEY_RCTRL = 228
|
|
115
|
+
KEY_RSHIFT = 229
|
|
116
|
+
KEY_RALT = 230
|
|
117
|
+
|
|
118
|
+
BUTTON_GAMEPAD_FIRST = 0x1000
|
|
119
|
+
|
|
34
120
|
PAD_A = 4096
|
|
35
121
|
PAD_B = 4097
|
|
36
122
|
PAD_X = 4098
|
|
@@ -46,10 +132,13 @@ module RGame
|
|
|
46
132
|
PAD_DPAD_DOWN = 4108
|
|
47
133
|
PAD_DPAD_LEFT = 4109
|
|
48
134
|
PAD_DPAD_RIGHT = 4110
|
|
135
|
+
PAD_MISC1 = 4111
|
|
136
|
+
PAD_PADDLE1 = 4112
|
|
137
|
+
PAD_PADDLE2 = 4113
|
|
138
|
+
PAD_PADDLE3 = 4114
|
|
139
|
+
PAD_PADDLE4 = 4115
|
|
140
|
+
PAD_TOUCHPAD = 4116
|
|
49
141
|
|
|
50
|
-
# --- Analog axes. Their own small space: they are float-valued and read
|
|
51
|
-
# through a different call, so folding them into the button space would
|
|
52
|
-
# only invite asking for an axis as if it were a button. ---
|
|
53
142
|
AXIS_LEFT_X = 0
|
|
54
143
|
AXIS_LEFT_Y = 1
|
|
55
144
|
AXIS_RIGHT_X = 2
|
|
@@ -57,8 +146,6 @@ module RGame
|
|
|
57
146
|
AXIS_TRIGGER_LEFT = 4
|
|
58
147
|
AXIS_TRIGGER_RIGHT = 5
|
|
59
148
|
|
|
60
|
-
# --- Devices. The keyboard is 0 so single-player code can leave it out;
|
|
61
|
-
# gamepads follow, one per player slot. ---
|
|
62
149
|
KEYBOARD = 0
|
|
63
150
|
GAMEPAD_FIRST = 1
|
|
64
151
|
MAX_GAMEPADS = 4
|
|
@@ -66,42 +153,17 @@ module RGame
|
|
|
66
153
|
# The device id for a player slot: gamepad(0) is the first controller.
|
|
67
154
|
def self.gamepad(slot) = GAMEPAD_FIRST + slot
|
|
68
155
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
# constants above and hand it to the input layer.
|
|
74
|
-
#
|
|
75
|
-
# Two button tables rather than one, because the same action is a
|
|
76
|
-
# different physical input per device class: :fire is the space bar on a
|
|
77
|
-
# keyboard and the A button on a pad.
|
|
78
|
-
DEFAULT_KEYBOARD = {
|
|
79
|
-
left: KEY_LEFT,
|
|
80
|
-
right: KEY_RIGHT,
|
|
81
|
-
up: KEY_UP,
|
|
82
|
-
down: KEY_DOWN,
|
|
83
|
-
confirm: KEY_RETURN,
|
|
84
|
-
fire: KEY_SPACE
|
|
85
|
-
}.freeze
|
|
86
|
-
|
|
87
|
-
DEFAULT_PAD = {
|
|
88
|
-
left: PAD_DPAD_LEFT,
|
|
89
|
-
right: PAD_DPAD_RIGHT,
|
|
90
|
-
up: PAD_DPAD_UP,
|
|
91
|
-
down: PAD_DPAD_DOWN,
|
|
92
|
-
confirm: PAD_A,
|
|
93
|
-
fire: PAD_A
|
|
94
|
-
}.freeze
|
|
156
|
+
# Is this *device* a controller rather than the keyboard? What a game asks
|
|
157
|
+
# when the answer changes what it shows the player — the prompt for an
|
|
158
|
+
# action is a key cap or a face button depending on what they last used.
|
|
159
|
+
def self.gamepad?(device) = device >= GAMEPAD_FIRST
|
|
95
160
|
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
trigger_left: AXIS_TRIGGER_LEFT,
|
|
103
|
-
trigger_right: AXIS_TRIGGER_RIGHT
|
|
104
|
-
}.freeze
|
|
161
|
+
# Is this *button* one a controller has rather than one a keyboard has?
|
|
162
|
+
# The mirror of the question above, on the other id space, and the pair is
|
|
163
|
+
# what lets a caller keep the two apart: an action bound to both lists its
|
|
164
|
+
# keys and its pad buttons together, and only a device can say which half
|
|
165
|
+
# of that list applies right now.
|
|
166
|
+
def self.pad_button?(id) = id >= BUTTON_GAMEPAD_FIRST
|
|
105
167
|
end
|
|
106
168
|
end
|
|
107
169
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rgame/util_ext'
|
|
4
|
+
|
|
5
|
+
module RGame
|
|
6
|
+
module Util
|
|
7
|
+
# Connected regions and A* routes over a SolidGrid, in C. The search reads its grid and
|
|
8
|
+
# never writes it, and keeps it alive; any number of searches may read one grid.
|
|
9
|
+
#
|
|
10
|
+
# The grid is 8-connected with octile costs (1 straight, √2 diagonal), and a diagonal
|
|
11
|
+
# step is allowed only when both orthogonal cells beside it are open, so a route never
|
|
12
|
+
# cuts the corner of a solid cell. Routes are cells, `[[col, row], ...]`, start and goal
|
|
13
|
+
# included — or nil when either end is solid, outside the grid, or in another region.
|
|
14
|
+
#
|
|
15
|
+
# Regions are labelled on the first ask after the grid's #revision moves, so a goal in
|
|
16
|
+
# another region is a lookup rather than a search that exhausts the map, and it stays
|
|
17
|
+
# right when the grid changes. Everything else a search keeps between queries is reused,
|
|
18
|
+
# so a repeated query allocates nothing but its result. One search is therefore not safe
|
|
19
|
+
# to use from two threads at once; two searches over one grid are.
|
|
20
|
+
#
|
|
21
|
+
# grid = RGame::Util::SolidGrid.build(8, 3) { |col, row| col == 3 && row < 2 }
|
|
22
|
+
# search = RGame::Util::RouteSearch.new(grid)
|
|
23
|
+
# search.find(0, 0, 7, 0) # => [[0, 0], [1, 1], [2, 2], [3, 2], [4, 2], [5, 1], [6, 0], [7, 0]]
|
|
24
|
+
class RouteSearch
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module RGame
|
|
7
|
+
module Util
|
|
8
|
+
# A game's saved state, as one JSON file.
|
|
9
|
+
#
|
|
10
|
+
# save = SaveFile.new('slot1.json', game: 'sheepdog')
|
|
11
|
+
# save.write(dog: [120, 80], sheep: [[40, 40], [90, 30]])
|
|
12
|
+
# save.read # => { dog: [120, 80], sheep: [...] }
|
|
13
|
+
# save.read.fetch(:dog, nil) # => nil when there is no save yet
|
|
14
|
+
#
|
|
15
|
+
# It holds no open handle — it opens, reads or writes, and closes — which is
|
|
16
|
+
# why this is `Util` rather than `Core` (see CLAUDE.md, "Value objects go in
|
|
17
|
+
# Util"). JSON comes from the standard library, so nothing here adds a
|
|
18
|
+
# runtime dependency.
|
|
19
|
+
#
|
|
20
|
+
# ## Reading never raises
|
|
21
|
+
#
|
|
22
|
+
# `read` answers with the default for a file that is missing, empty,
|
|
23
|
+
# truncated, not JSON, or JSON that is not an object. That is deliberate and
|
|
24
|
+
# it is the whole reason this class exists rather than three lines of
|
|
25
|
+
# `JSON.parse(File.read(...))` at a call site.
|
|
26
|
+
#
|
|
27
|
+
# A save file is the one input a game has that it did not produce this run.
|
|
28
|
+
# It survives crashes, disk-full, a killed process mid-write, an editor, and
|
|
29
|
+
# a copy from someone else's machine. A game that raises on any of those is
|
|
30
|
+
# a game that cannot be started again, and the player's only fix is to find
|
|
31
|
+
# and delete a file they were never told about. Losing a save is bad;
|
|
32
|
+
# refusing to launch is worse.
|
|
33
|
+
#
|
|
34
|
+
# **Writing still raises.** A read failure has a sensible answer — "there is
|
|
35
|
+
# no save" — and a write failure does not: silently discarding the player's
|
|
36
|
+
# progress is the failure that gets noticed hours later. A caller that wants
|
|
37
|
+
# to survive a full disk can rescue; it cannot un-swallow an exception this
|
|
38
|
+
# class never raised.
|
|
39
|
+
#
|
|
40
|
+
# ## Writing is atomic
|
|
41
|
+
#
|
|
42
|
+
# `write` writes a temporary file beside the target and renames it over the
|
|
43
|
+
# top. `File.rename` within one directory is atomic on every platform this
|
|
44
|
+
# engine supports, so a save is either the old one or the new one and never
|
|
45
|
+
# half of each.
|
|
46
|
+
#
|
|
47
|
+
# This matters more than it sounds: the moment a game most wants to save is
|
|
48
|
+
# on the way out, which is also the moment it is most likely to be killed.
|
|
49
|
+
# A plain `File.write` truncates first and fills in after, so a crash in
|
|
50
|
+
# between leaves a zero-byte file where the player's afternoon used to be.
|
|
51
|
+
class SaveFile
|
|
52
|
+
# Where saves go, per platform, following each one's own convention rather
|
|
53
|
+
# than dropping a dotfile in the home directory:
|
|
54
|
+
#
|
|
55
|
+
# Linux $XDG_DATA_HOME/<game>, or ~/.local/share/<game>
|
|
56
|
+
# macOS ~/Library/Application Support/<game>
|
|
57
|
+
# Windows %APPDATA%\<game>
|
|
58
|
+
#
|
|
59
|
+
# `game` names the directory, so two games do not share a save.
|
|
60
|
+
def self.directory(game)
|
|
61
|
+
case RbConfig::CONFIG['host_os']
|
|
62
|
+
when /darwin/ then File.join(Dir.home, 'Library', 'Application Support', game)
|
|
63
|
+
when /mswin|mingw|cygwin/ then File.join(ENV.fetch('APPDATA', Dir.home), game)
|
|
64
|
+
else File.join(ENV.fetch('XDG_DATA_HOME', File.join(Dir.home, '.local', 'share')), game)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
attr_reader :path
|
|
69
|
+
|
|
70
|
+
# `name` is a file name inside the game's save directory; pass `dir:` to
|
|
71
|
+
# put it somewhere else, which is what a spec does.
|
|
72
|
+
def initialize(name, game: 'rgame', dir: nil)
|
|
73
|
+
@path = File.join(dir || SaveFile.directory(game), name)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def exist? = File.exist?(@path)
|
|
77
|
+
|
|
78
|
+
# The saved state, or `default` if there is nothing usable to read. Keys
|
|
79
|
+
# come back as Symbols, so a game writes and reads the same shape.
|
|
80
|
+
def read(default = {})
|
|
81
|
+
parsed = JSON.parse(File.read(@path), symbolize_names: true)
|
|
82
|
+
parsed.is_a?(Hash) ? parsed : default
|
|
83
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR, JSON::ParserError
|
|
84
|
+
default
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Writes `state` as JSON, atomically. Creates the directory if it is not
|
|
88
|
+
# there — a first save on a fresh machine is the normal case, not an error.
|
|
89
|
+
def write(state)
|
|
90
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
91
|
+
temporary = "#{@path}.#{Process.pid}.tmp"
|
|
92
|
+
File.write(temporary, JSON.pretty_generate(state))
|
|
93
|
+
File.rename(temporary, @path)
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Removes the save, and says nothing if there was not one. "Delete my
|
|
98
|
+
# save" and "there was no save" leave the player in the same place.
|
|
99
|
+
def delete
|
|
100
|
+
File.unlink(@path)
|
|
101
|
+
self
|
|
102
|
+
rescue Errno::ENOENT
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rgame/util_ext'
|
|
4
|
+
|
|
5
|
+
module RGame
|
|
6
|
+
module Util
|
|
7
|
+
# Which cells of a tile grid are solid: one byte a cell, in C. A value with no handle
|
|
8
|
+
# behind it, which is why it lives in Util and why the engine layer may own one.
|
|
9
|
+
#
|
|
10
|
+
# It is the one store a tile world's solidity lives in. The blockers that stop a walker
|
|
11
|
+
# and the RouteSearch that plans one both read it, so they cannot disagree about a wall,
|
|
12
|
+
# and a cell changed with #set_solid is changed for both at once.
|
|
13
|
+
#
|
|
14
|
+
# #revision counts the changes: it moves on a #set_solid that changes a cell and on no
|
|
15
|
+
# other, so anything derived from the cells — a search's region labels — can tell
|
|
16
|
+
# whether it is stale without being told.
|
|
17
|
+
#
|
|
18
|
+
# Coordinates are Integers, and anything else is a TypeError. Outside the grid is open,
|
|
19
|
+
# including an Integer too large to be inside any grid.
|
|
20
|
+
#
|
|
21
|
+
# grid = RGame::Util::SolidGrid.build(8, 3) { |col, _row| col == 3 }
|
|
22
|
+
# grid.solid?(3, 1) # => true
|
|
23
|
+
# grid.solid?(-1, 0) # => false
|
|
24
|
+
# grid.set_solid(3, 1, false)
|
|
25
|
+
# grid.revision # => 1
|
|
26
|
+
class SolidGrid
|
|
27
|
+
# A grid with each cell's solidity read from the block, once per cell, row by row.
|
|
28
|
+
def self.build(width, height)
|
|
29
|
+
grid = new(width, height)
|
|
30
|
+
height.times do |row|
|
|
31
|
+
width.times { |col| grid.set_solid(col, row, true) if yield(col, row) }
|
|
32
|
+
end
|
|
33
|
+
grid
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/rgame/util/tensor.rb
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# RGame::Util::Tensor is implemented in C — see ext/rgame_util/tensor.c. It used
|
|
4
|
-
# to be a pure-Ruby class here; the public API (RGame::Util::Tensor.new(width,
|
|
5
|
-
# height, depth, initial: nil), #[], #[]=, #width/#height/#depth) is unchanged,
|
|
6
|
-
# only the backing implementation moved to C for speed and a compact flat-array
|
|
7
|
-
# layout.
|
|
8
|
-
#
|
|
9
|
-
# The compiled extension is a separate .so from the SDL/GL engine, so requiring
|
|
10
|
-
# Tensor pulls in no graphics libraries. It loads from lib/rgame/util_ext.so,
|
|
11
|
-
# which the build (`make ext-util`) copies out of ext/rgame_util/.
|
|
12
3
|
require 'rgame/util_ext'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rgame/util_ext'
|
|
4
|
+
|
|
5
|
+
module RGame
|
|
6
|
+
module Util
|
|
7
|
+
# An axis-aligned box against the solid tiles of a SolidGrid, in C. It reads its grid and
|
|
8
|
+
# never writes it, and keeps it alive; any number of sweeps may read one grid, so a change
|
|
9
|
+
# made with SolidGrid#set_solid is seen by all of them at once.
|
|
10
|
+
#
|
|
11
|
+
# It answers two questions with one piece of arithmetic, so they cannot disagree:
|
|
12
|
+
#
|
|
13
|
+
# - **#resolve_x / #resolve_y** — where the box's left (top) edge lands moving dx (dy). A
|
|
14
|
+
# step that would enter a solid tile snaps the box flush against that tile's edge. Each
|
|
15
|
+
# axis is resolved on its own. The step is assumed smaller than a tile; a longer one can
|
|
16
|
+
# pass through a tile without seeing it.
|
|
17
|
+
# - **#travel?** — whether the box can move by (dx, dy) without any resolve along the way
|
|
18
|
+
# falling short of where it was heading. The box is swept in overlapping windows half a
|
|
19
|
+
# tile long, a quarter tile apart, each resolved X-then-Y and Y-then-X, which makes the
|
|
20
|
+
# answer sound for a walker that resolves its own steps and steps less than a quarter tile
|
|
21
|
+
# at a time.
|
|
22
|
+
#
|
|
23
|
+
# Boxes are given by their top-left corner and size, in pixels. Outside the grid is open.
|
|
24
|
+
# Results are Floats. A coordinate that is not a number is a TypeError; a non-finite one is
|
|
25
|
+
# a FloatDomainError, except in a resolve with no movement, which returns the box where it
|
|
26
|
+
# is.
|
|
27
|
+
#
|
|
28
|
+
# grid = RGame::Util::SolidGrid.build(20, 15) { |col, _row| col == 5 } # a wall at x 80..96
|
|
29
|
+
# sweep = RGame::Util::TileSweep.new(grid, 16, 16)
|
|
30
|
+
# sweep.resolve_x(58.0, 32.0, 12, 6, 14.0) # => 68.0, flush against the wall
|
|
31
|
+
# sweep.travel?(10.0, 32.0, 12, 6, 50.0, 0) # => true
|
|
32
|
+
# sweep.travel?(10.0, 32.0, 12, 6, 90.0, 0) # => false
|
|
33
|
+
class TileSweep
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/rgame/util/z.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RGame
|
|
4
|
+
module Util
|
|
5
|
+
# The vocabulary of draw order: which band a thing is drawn in, and how the
|
|
6
|
+
# single number the renderer sorts on is built out of one.
|
|
7
|
+
#
|
|
8
|
+
# RGame::Util::Z::BANDS # => [:world, :hud, :overlay, :debug]
|
|
9
|
+
# RGame::Util::Z.base(:hud, 3) # the fourth slot handed out in the HUD band
|
|
10
|
+
#
|
|
11
|
+
# These are values — plain Symbols and Integers with no window, GPU or OS
|
|
12
|
+
# handle behind them — so they live here rather than in Core, and both sides
|
|
13
|
+
# of the line may name them. `RGame::Engine` decides which band a node is
|
|
14
|
+
# in; `RGame::Core::Renderer` turns that into a z. Neither could reach the
|
|
15
|
+
# other's spelling of it, and the vocabulary is exactly what they have to
|
|
16
|
+
# agree on. Same reasoning as RGame::Util::Controls.
|
|
17
|
+
#
|
|
18
|
+
# ## Draw order is tree order, not a number a caller picks
|
|
19
|
+
#
|
|
20
|
+
# A frame's order is decided in three steps, coarsest first:
|
|
21
|
+
#
|
|
22
|
+
# 1. **The band.** Structural and inherited: everything in `:world` is under
|
|
23
|
+
# everything in `:hud`, whatever either drew. Bands are 2**40 apart, so
|
|
24
|
+
# no arithmetic below can carry one into the next.
|
|
25
|
+
# 2. **The slot.** The scene graph is walked depth-first with siblings in
|
|
26
|
+
# `z` order, and each node takes the next slot in its band as it is
|
|
27
|
+
# reached. Because the walk descends fully before moving on, a node's
|
|
28
|
+
# subtree occupies one contiguous run of slots — which is what makes a
|
|
29
|
+
# subtree atomic, and what stops some of a node's children landing in
|
|
30
|
+
# front of a sibling the node itself is behind.
|
|
31
|
+
# 3. **The offset.** `Z_MIN..Z_MAX` inside the node's own slot, and the only
|
|
32
|
+
# part a `z:` argument on a drawing call touches. It orders a node's
|
|
33
|
+
# components against each other and its own draw calls against each
|
|
34
|
+
# other, and it cannot reach the next node, let alone the next band.
|
|
35
|
+
#
|
|
36
|
+
# So a node's `z` is **only ever compared to its siblings'**. Its magnitude
|
|
37
|
+
# means nothing: `z: 1` and `z: 1_000_000` behave identically if they are
|
|
38
|
+
# the only two children, and negatives are ordinary.
|
|
39
|
+
#
|
|
40
|
+
# ## The arithmetic is exact
|
|
41
|
+
#
|
|
42
|
+
# Every value here is an integer below 2**42, and the `double` the draw
|
|
43
|
+
# queue sorts on is exact below 2**53. Two different slots can therefore
|
|
44
|
+
# never compare equal by rounding — which would show up as two sprites
|
|
45
|
+
# swapping places between frames, and would be very hard to see as a
|
|
46
|
+
# precision problem.
|
|
47
|
+
module Z
|
|
48
|
+
SLOT = 1024
|
|
49
|
+
HALF = SLOT / 2
|
|
50
|
+
|
|
51
|
+
Z_MIN = -HALF
|
|
52
|
+
Z_MAX = HALF - 1
|
|
53
|
+
|
|
54
|
+
STRIDE = 1 << 40
|
|
55
|
+
SLOTS_PER_BAND = STRIDE / SLOT
|
|
56
|
+
|
|
57
|
+
BANDS = %i[world hud overlay debug].freeze
|
|
58
|
+
DEFAULT = :world
|
|
59
|
+
|
|
60
|
+
INDICES = BANDS.each_with_index.to_h.freeze
|
|
61
|
+
|
|
62
|
+
def self.band?(band) = INDICES.key?(band)
|
|
63
|
+
|
|
64
|
+
# Raises unless `band` names one. Called where a band is *set* rather than
|
|
65
|
+
# where it is used, so a typo surfaces at assignment with the list in the
|
|
66
|
+
# message instead of as a KeyError from inside a draw.
|
|
67
|
+
#
|
|
68
|
+
# @api private
|
|
69
|
+
def self.band!(band)
|
|
70
|
+
return band if band?(band)
|
|
71
|
+
|
|
72
|
+
raise ArgumentError, "unknown z band #{band.inspect}; expected one of #{BANDS.inspect}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Which band this is, 0-based and back to front. This is what a renderer's
|
|
76
|
+
# slot counters are keyed on, so it is also where a bad band is caught.
|
|
77
|
+
def self.index(band)
|
|
78
|
+
INDICES[band] || band!(band)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The z base of the `slot_index`-th slot in the band with that index.
|
|
82
|
+
# Offsets are measured from the middle of the slot, so Z_MIN..Z_MAX is
|
|
83
|
+
# symmetric — a node can draw behind its own sprite as easily as in front.
|
|
84
|
+
#
|
|
85
|
+
# Takes the band's *index* rather than its name because a renderer already
|
|
86
|
+
# has the index in hand (it counts slots per index), and looking the same
|
|
87
|
+
# Symbol up twice per node per frame is work with nothing to show for it.
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
# hot-path
|
|
91
|
+
def self.slot_base(band_index, slot_index)
|
|
92
|
+
if slot_index >= SLOTS_PER_BAND
|
|
93
|
+
raise RangeError,
|
|
94
|
+
"z band #{BANDS[band_index].inspect} is full at " \
|
|
95
|
+
"#{SLOTS_PER_BAND} slots per frame"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
(band_index * STRIDE) + (slot_index * SLOT) + HALF
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The same thing by name, for a caller that has one rather than an index.
|
|
102
|
+
def self.base(band, slot_index) = slot_base(index(band), slot_index)
|
|
103
|
+
|
|
104
|
+
# Checks a `z:` argument and returns it. Drawing methods run this instead
|
|
105
|
+
# of trusting the caller, because the whole guarantee — that a node cannot
|
|
106
|
+
# draw outside its band — rests on the offset staying inside one slot.
|
|
107
|
+
# A stale global z (the bands used to be Integers a caller passed) lands
|
|
108
|
+
# here and says so rather than sorting somewhere surprising.
|
|
109
|
+
# hot-path
|
|
110
|
+
def self.offset(z)
|
|
111
|
+
raise TypeError, "no implicit conversion of #{z.class} into Float" unless z.is_a?(Numeric)
|
|
112
|
+
|
|
113
|
+
unless z.between?(Z_MIN, Z_MAX)
|
|
114
|
+
raise ArgumentError,
|
|
115
|
+
"z: #{z} is outside a node's slot (#{Z_MIN}..#{Z_MAX}); a `z:` orders one " \
|
|
116
|
+
'node\'s own drawing, and the band comes from the tree'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
z
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/rgame/util.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# RGame::Util — everything that does NOT depend on SDL, OpenGL, or anything
|
|
4
|
-
# built on them. Backed by the graphics-free extension in ext/rgame_util/.
|
|
5
|
-
# Its counterpart is RGame::Core (see lib/rgame/core.rb).
|
|
6
3
|
require_relative 'util/color'
|
|
7
4
|
require_relative 'util/controls'
|
|
5
|
+
require_relative 'util/route_search'
|
|
6
|
+
require_relative 'util/save_file'
|
|
7
|
+
require_relative 'util/solid_grid'
|
|
8
8
|
require_relative 'util/tensor'
|
|
9
|
+
require_relative 'util/tile_sweep'
|
|
10
|
+
require_relative 'util/z'
|
data/lib/rgame/version.rb
CHANGED
data/lib/rgame.rb
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Entry point for `require "rgame"`: everything that runs without a window.
|
|
4
|
-
#
|
|
5
|
-
# require 'rgame' # RGame::Util + RGame::Engine — no graphics libraries
|
|
6
|
-
# require 'rgame/core' # adds the window, the GPU and the sound device
|
|
7
|
-
# require 'rgame/game' # all of it, wired together
|
|
8
|
-
#
|
|
9
|
-
# Each line is a strict superset of the one above, and the split is the point.
|
|
10
|
-
# **This one loads no SDL and no OpenGL** — `RGame::Util` is a graphics-free
|
|
11
|
-
# extension and `RGame::Engine` is pure Ruby — so game logic and the specs that
|
|
12
|
-
# cover it run with no display present. `spec/rgame/no_graphics_spec.rb` holds
|
|
13
|
-
# that up rather than leaving it to good intentions.
|
|
14
|
-
#
|
|
15
|
-
# Nothing is forced through this file. `rgame/util`, `rgame/engine` and
|
|
16
|
-
# `rgame/core` are separately requirable, which is how the Core spec suite loads
|
|
17
|
-
# exactly one layer and gets a `NameError` if it reaches for another.
|
|
18
3
|
require_relative 'rgame/version'
|
|
19
4
|
require_relative 'rgame/util'
|
|
20
5
|
require_relative 'rgame/engine'
|