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/engine/node2d.rb
CHANGED
|
@@ -8,21 +8,246 @@ module RGame
|
|
|
8
8
|
# DSL to allow for easy signal usage.
|
|
9
9
|
class Node2D
|
|
10
10
|
extend Engine::Signal::DSL
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
extend Engine::SealedPrivates
|
|
12
|
+
|
|
13
|
+
# This node's transform **in its parent's space** — where it sits inside
|
|
14
|
+
# whatever contains it, and the only position a node ever sets. `x`, `y`
|
|
15
|
+
# and `angle` are the short spelling of the same three, because that is
|
|
16
|
+
# what reads well where a node moves itself:
|
|
17
|
+
#
|
|
18
|
+
# def on_update(dt) = self.x += @speed * dt
|
|
19
|
+
#
|
|
20
|
+
# Not for drawing, though it is tempting: `on_draw` runs with the renderer
|
|
21
|
+
# already on this node (see #_in_local_space), so drawing at `x` offsets by
|
|
22
|
+
# this node's own position a second time. `Game/DrawInLocalSpace` says so.
|
|
23
|
+
#
|
|
24
|
+
# The ivar carries the longer name so that `@x` does not exist. Reaching
|
|
25
|
+
# for a parent-relative coordinate where a world one was meant is the
|
|
26
|
+
# mistake this whole design is arranged against, and the spelling that used
|
|
27
|
+
# to make it silent is simply not there any more.
|
|
28
|
+
attr_reader :rel_x, :rel_y, :rel_angle
|
|
29
|
+
alias x rel_x
|
|
30
|
+
alias y rel_y
|
|
31
|
+
alias angle rel_angle
|
|
32
|
+
|
|
33
|
+
attr_accessor :width, :height
|
|
34
|
+
|
|
35
|
+
# Height above the ground in pixels, for a top-down view: how far this
|
|
36
|
+
# node's picture is drawn above the spot it stands on. Positive is up.
|
|
37
|
+
#
|
|
38
|
+
# It is **not** part of the transform. `y`, `world_y`, colliders, cameras
|
|
39
|
+
# and children all ignore it, which is what lets a character leave the
|
|
40
|
+
# ground without its feet box leaving too. Components::Sprite and
|
|
41
|
+
# Components::AnimatedSprite draw lifted by it, in the node's local space;
|
|
42
|
+
# Components::Hop is one thing that writes it.
|
|
43
|
+
attr_accessor :elevation
|
|
13
44
|
attr_writer :scene, :context
|
|
14
|
-
attr_reader :children, :components, :abs_x, :abs_y, :abs_z, :abs_angle
|
|
15
45
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
46
|
+
# The other way a node moves, and the only one that does not go through a
|
|
47
|
+
# coordinate writer: its `x`/`y` do not change, but they are now an offset
|
|
48
|
+
# from somewhere else. Set by #add_node and #remove_node — a game does not
|
|
49
|
+
# call this — and it invalidates the subtree for the same reason a move
|
|
50
|
+
# does.
|
|
51
|
+
def parent=(value)
|
|
52
|
+
@parent = value
|
|
53
|
+
_soil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Moving a node invalidates the world transform of the node *and its whole
|
|
57
|
+
# subtree* — every one of them is now somewhere else — but computes none of
|
|
58
|
+
# them. Whoever reads one next pays for that one.
|
|
59
|
+
#
|
|
60
|
+
# `node.x += dx` from a component (Components::Velocity does exactly that)
|
|
61
|
+
# is two writes and so two invalidations, which is why #_soil returns
|
|
62
|
+
# immediately on a subtree that is already stale.
|
|
63
|
+
def rel_x=(value)
|
|
64
|
+
@rel_x = value
|
|
65
|
+
_soil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def rel_y=(value)
|
|
69
|
+
@rel_y = value
|
|
70
|
+
_soil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def rel_angle=(value)
|
|
74
|
+
@rel_angle = value
|
|
75
|
+
_soil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
alias x= rel_x=
|
|
79
|
+
alias y= rel_y=
|
|
80
|
+
alias angle= rel_angle=
|
|
81
|
+
|
|
82
|
+
# This node's transform **in world space**, accumulated from its whole
|
|
83
|
+
# ancestry. A node lives in its parent's space and is moved by setting
|
|
84
|
+
# `x`/`y`/`angle`; `world_x=`/`world_y=` below are that same move, worked
|
|
85
|
+
# out from a world position for the code that thinks in one.
|
|
86
|
+
#
|
|
87
|
+
# `world_` rather than `abs_`, because the name should say which space the
|
|
88
|
+
# value is in. `abs_band` and `abs_input_owner` keep theirs deliberately —
|
|
89
|
+
# those are *inherited* from the nearest ancestor that declares one rather
|
|
90
|
+
# than expressed in a space, and a band is not in world coordinates.
|
|
91
|
+
#
|
|
92
|
+
# **Computed on demand and cached**, which is how Godot and Unity do it and
|
|
93
|
+
# why no phase resolves this any more. Moving a node marks it and its whole
|
|
94
|
+
# subtree stale (see #_soil); the next read walks up to the nearest node
|
|
95
|
+
# still current, recomputing on the way back down. Two consequences worth
|
|
96
|
+
# knowing:
|
|
97
|
+
#
|
|
98
|
+
# - It is never stale. There is no "resolved at the top of the phase" value
|
|
99
|
+
# to go out of date, so a node that moved, a node whose *ancestor* moved,
|
|
100
|
+
# and a paused node under a moving ancestor all answer correctly, at any
|
|
101
|
+
# point in any phase.
|
|
102
|
+
# - Nothing is computed for a node nobody asks about. A still frame costs
|
|
103
|
+
# nothing at all, where resolving the tree eagerly cost a full pass per
|
|
104
|
+
# phase whether or not anything had moved.
|
|
105
|
+
# hot-path
|
|
106
|
+
def world_x
|
|
107
|
+
_resolve_transform unless @world_current
|
|
108
|
+
@world_x
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# hot-path
|
|
112
|
+
def world_y
|
|
113
|
+
_resolve_transform unless @world_current
|
|
114
|
+
@world_y
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Place the node at a world coordinate, leaving the other one where it is.
|
|
118
|
+
#
|
|
119
|
+
# For code that decides where a node goes in world space — the edge of the
|
|
120
|
+
# world, a resolved collision — and must still write the node's own, local
|
|
121
|
+
# position. The target is turned back into the parent's frame, so the
|
|
122
|
+
# answer is exact under a rotated ancestor too: there it moves the local
|
|
123
|
+
# position along *both* axes, which is what one world axis looks like from
|
|
124
|
+
# inside a turned frame.
|
|
125
|
+
#
|
|
126
|
+
# A node with no parent is pinned to the origin (see #_resolve_transform),
|
|
127
|
+
# so there is no local position that would put it anywhere else, and this
|
|
128
|
+
# changes nothing.
|
|
129
|
+
# hot-path
|
|
130
|
+
def world_x=(value)
|
|
131
|
+
return if @parent.nil?
|
|
132
|
+
|
|
133
|
+
pa = @parent.world_angle
|
|
134
|
+
if pa.zero?
|
|
135
|
+
self.rel_x = value - @parent.world_x
|
|
136
|
+
else
|
|
137
|
+
_place_in_rotated_parent(value - @parent.world_x, world_y - @parent.world_y, pa)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# hot-path
|
|
142
|
+
def world_y=(value)
|
|
143
|
+
return if @parent.nil?
|
|
144
|
+
|
|
145
|
+
pa = @parent.world_angle
|
|
146
|
+
if pa.zero?
|
|
147
|
+
self.rel_y = value - @parent.world_y
|
|
148
|
+
else
|
|
149
|
+
_place_in_rotated_parent(world_x - @parent.world_x, value - @parent.world_y, pa)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# hot-path
|
|
154
|
+
def world_angle
|
|
155
|
+
_resolve_transform unless @world_current
|
|
156
|
+
@world_angle
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
attr_reader :children, :components, :parent, :abs_input_owner, :z, :band, :abs_band
|
|
160
|
+
|
|
161
|
+
# Where this node sits among its **siblings**, and nowhere else.
|
|
162
|
+
#
|
|
163
|
+
# The tree is drawn depth-first with siblings in `z` order, so a node's
|
|
164
|
+
# whole subtree is drawn before or after a sibling's whole subtree —
|
|
165
|
+
# never interleaved with it. Clouds over birds over people is three
|
|
166
|
+
# children of one node at `z` 2, 1 and 0, and each of them may be built
|
|
167
|
+
# out of as many parts as it likes without any of those parts escaping.
|
|
168
|
+
#
|
|
169
|
+
# Only the *comparison* matters. `z` is never added to anything and never
|
|
170
|
+
# reaches the renderer, so its magnitude means nothing: 1 and 1_000_000
|
|
171
|
+
# behave identically if they are the only two children, and a negative is
|
|
172
|
+
# ordinary. Equal `z` keeps the order the nodes were added in.
|
|
173
|
+
#
|
|
174
|
+
# This is deliberately unlike the additive relative z it replaces
|
|
175
|
+
# (`abs_z = parent.abs_z + z`), where a node at z 2 with a child at z 5
|
|
176
|
+
# resolved to 7 and overtook a sibling at 4 — some of a node's parts in
|
|
177
|
+
# front of something the node itself was behind. See RGame::Util::Z.
|
|
178
|
+
def z=(value)
|
|
179
|
+
@z = value
|
|
180
|
+
@parent&._children_unsorted!
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Which band this node and everything under it draws in — `:world` (the
|
|
184
|
+
# default), `:hud`, `:overlay` or `:debug`. A band beats every `z` in the
|
|
185
|
+
# tree: nothing in `:world` can draw over anything in `:hud`.
|
|
186
|
+
#
|
|
187
|
+
# Inherited like `input_owner`, and normally set by a node that exists to
|
|
188
|
+
# mark one: WorldView is `:world`, PlayerLayer is `:hud`. Setting it
|
|
189
|
+
# directly is the escape hatch — a node inside the world that must draw
|
|
190
|
+
# over the HUD says `band: :overlay` and does, still clipped to whatever
|
|
191
|
+
# its ancestors allowed. That is explicit and named, which is the whole
|
|
192
|
+
# difference from the Integer bases this replaces.
|
|
193
|
+
def band=(value)
|
|
194
|
+
Util::Z.band!(value) unless value.nil?
|
|
195
|
+
@band = value
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Whose input drives this node: an RGame::Engine::Player, or nil.
|
|
199
|
+
#
|
|
200
|
+
# Inherited down the tree exactly like the transform. Set it on a node and
|
|
201
|
+
# its whole subtree reads that player, so `ship.input_owner = players[1]`
|
|
202
|
+
# is all it takes for everything under the ship to answer to player two. A
|
|
203
|
+
# node that sets none inherits its parent's, and a tree that sets none
|
|
204
|
+
# anywhere reads the primary player — which is why single player needs no
|
|
205
|
+
# mention of this at all.
|
|
206
|
+
#
|
|
207
|
+
# **Not `player`**, deliberately, and not `controller` either. `@player` is
|
|
208
|
+
# what a game's own code naturally calls its hero node, so an
|
|
209
|
+
# `attr_accessor :player` here would quietly claim that ivar out from
|
|
210
|
+
# under every scene that has one — which it did, and the symptom
|
|
211
|
+
# was the input system being handed a Node2D. `controller` is taken too:
|
|
212
|
+
# a controller is the component that produces movement intent — see
|
|
213
|
+
# Components::PlayerController — which is a different idea entirely. This
|
|
214
|
+
# name says exactly what it decides and collides with neither.
|
|
215
|
+
attr_accessor :input_owner
|
|
216
|
+
|
|
217
|
+
# A paused node skips `control` and `update` — and so does everything
|
|
218
|
+
# under it, because a subtree is only ever reached through its parent.
|
|
219
|
+
# It still **draws**: pausing is about time, not visibility, which is what
|
|
220
|
+
# lets a frozen world sit under a cutscene overlay that keeps animating.
|
|
221
|
+
#
|
|
222
|
+
# world_view.paused = true # the world stops; the overlay above it does not
|
|
223
|
+
#
|
|
224
|
+
# There is no `abs_paused` to go with `abs_input_owner`. Ownership has to
|
|
225
|
+
# be resolved because a node needs to know whose input it reads even when
|
|
226
|
+
# its parent claims nobody; pausing needs no resolution at all, because a
|
|
227
|
+
# paused node simply never descends.
|
|
228
|
+
attr_accessor :paused
|
|
229
|
+
|
|
230
|
+
def initialize(x: 0, y: 0, z: 0, angle: 0, width: 0, height: 0, input_owner: nil,
|
|
231
|
+
band: nil)
|
|
232
|
+
@input_owner = input_owner
|
|
233
|
+
@paused = false
|
|
234
|
+
@rel_x = x
|
|
235
|
+
@rel_y = y
|
|
19
236
|
@z = z
|
|
20
|
-
|
|
237
|
+
self.band = band
|
|
238
|
+
@rel_angle = angle
|
|
21
239
|
@width = width
|
|
22
240
|
@height = height
|
|
241
|
+
@elevation = 0
|
|
242
|
+
@world_current = false
|
|
243
|
+
@world_x = @world_y = @world_angle = 0
|
|
244
|
+
@abs_input_owner = @input_owner
|
|
245
|
+
@abs_band = @band || Util::Z::DEFAULT
|
|
23
246
|
@children = []
|
|
247
|
+
@child_seq = 0
|
|
248
|
+
@children_sorted = true
|
|
24
249
|
@components = []
|
|
25
|
-
@component_slots = {}
|
|
250
|
+
@component_slots = {}
|
|
26
251
|
@parent = nil
|
|
27
252
|
@scene = nil
|
|
28
253
|
@in_tree = false
|
|
@@ -32,9 +257,8 @@ module RGame
|
|
|
32
257
|
def add_node(node)
|
|
33
258
|
@children << node
|
|
34
259
|
node.parent = self
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# split — a node built inside another node's initialize is not yet in the tree.
|
|
260
|
+
node._sibling_order = (@child_seq += 1)
|
|
261
|
+
@children_sorted = false
|
|
38
262
|
node.enter_tree if @in_tree
|
|
39
263
|
node
|
|
40
264
|
end
|
|
@@ -90,11 +314,6 @@ module RGame
|
|
|
90
314
|
component
|
|
91
315
|
end
|
|
92
316
|
|
|
93
|
-
# Anchors, resolved by walking parents so they can never go stale (a cached
|
|
94
|
-
# back-link set at add-time breaks when children are built before the node is
|
|
95
|
-
# in the tree). Shared systems live as components on an anchor node and are
|
|
96
|
-
# reached through these, not threaded through constructors.
|
|
97
|
-
|
|
98
317
|
# The top-most node — a node with no parent is its own root. Global,
|
|
99
318
|
# program-lifetime systems live here as components.
|
|
100
319
|
def root
|
|
@@ -116,45 +335,53 @@ module RGame
|
|
|
116
335
|
scene&.get_component(klass) || root.get_component(klass)
|
|
117
336
|
end
|
|
118
337
|
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
338
|
+
# `input` is an input *source*, not one player's snapshot: an
|
|
339
|
+
# RGame::Engine::Players registry, or a bare Actions when there is only
|
|
340
|
+
# ever one answer (which is what a spec usually passes).
|
|
341
|
+
#
|
|
342
|
+
# Each node asks the source for the actions of whichever player owns it,
|
|
343
|
+
# and hands its components and its own hook that plain Actions. So a
|
|
344
|
+
# component never learns there is more than one player — `control(actions)`
|
|
345
|
+
# means the same thing it always did — while two subtrees under one tick
|
|
346
|
+
# can read two different controllers.
|
|
347
|
+
#
|
|
348
|
+
# The source is what descends, not the resolved snapshot, because
|
|
349
|
+
# ownership can change further down.
|
|
350
|
+
def control(input)
|
|
351
|
+
return if @paused
|
|
352
|
+
|
|
353
|
+
_resolve_inherited
|
|
354
|
+
actions = input.actions_for(@abs_input_owner)
|
|
128
355
|
@components.each { it.control(actions) }
|
|
129
356
|
on_control(actions)
|
|
130
|
-
|
|
357
|
+
_children_in_order.each { it.control(input) }
|
|
131
358
|
end
|
|
132
359
|
|
|
133
360
|
# update game logic and physics (might become two calls with
|
|
134
361
|
# time, but for now works in one step). This runs second in a
|
|
135
362
|
# game tick
|
|
136
363
|
def update(dt)
|
|
137
|
-
|
|
364
|
+
return if @paused
|
|
365
|
+
|
|
138
366
|
@components.each { it.update(dt) }
|
|
139
367
|
on_update(dt)
|
|
140
|
-
|
|
368
|
+
_children_in_order.each { it.update(dt) }
|
|
141
369
|
end
|
|
142
370
|
|
|
143
371
|
# update visual game state, drawing the node. This runs last in
|
|
144
372
|
# a game tick
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
renderer
|
|
373
|
+
# `view` is the viewport being drawn into: its rectangle, and the camera
|
|
374
|
+
# (if any) it is seen through. Every node gets it, because a node cannot
|
|
375
|
+
# otherwise know where the edges of its own region are — a HUD laying out
|
|
376
|
+
# against the whole window is wrong the moment the window is one player's
|
|
377
|
+
# half of it — and because culling needs it once the world is drawn more
|
|
378
|
+
# than once. Most nodes ignore it and simply draw.
|
|
379
|
+
def draw(renderer, view)
|
|
380
|
+
_resolve_inherited
|
|
381
|
+
_in_local_space(renderer) do
|
|
382
|
+
renderer.layered(@abs_band) { _draw_content(renderer, view) }
|
|
383
|
+
draw_children(renderer, view)
|
|
156
384
|
end
|
|
157
|
-
draw_children(renderer)
|
|
158
385
|
end
|
|
159
386
|
|
|
160
387
|
def in_tree? = @in_tree
|
|
@@ -176,7 +403,7 @@ module RGame
|
|
|
176
403
|
while i < @children.size
|
|
177
404
|
child = @children[i]
|
|
178
405
|
if child.freed?
|
|
179
|
-
remove_node(child)
|
|
406
|
+
remove_node(child)
|
|
180
407
|
else
|
|
181
408
|
child.sweep_freed
|
|
182
409
|
i += 1
|
|
@@ -192,10 +419,10 @@ module RGame
|
|
|
192
419
|
return if @in_tree
|
|
193
420
|
|
|
194
421
|
@in_tree = true
|
|
195
|
-
@freed = false
|
|
422
|
+
@freed = false
|
|
196
423
|
@components.each(&:on_attach)
|
|
197
424
|
on_add
|
|
198
|
-
|
|
425
|
+
_children_in_order.each(&:enter_tree)
|
|
199
426
|
end
|
|
200
427
|
|
|
201
428
|
# Leaving-tree cascade: mirror of #enter_tree (children first, then this
|
|
@@ -203,68 +430,125 @@ module RGame
|
|
|
203
430
|
def exit_tree
|
|
204
431
|
return unless @in_tree
|
|
205
432
|
|
|
206
|
-
|
|
433
|
+
_children_in_order.each(&:exit_tree)
|
|
207
434
|
on_remove
|
|
208
435
|
@components.each(&:on_detach)
|
|
209
436
|
@in_tree = false
|
|
210
437
|
end
|
|
211
438
|
|
|
212
|
-
# Lifecycle hooks: Subclasses should implement these instead of
|
|
213
|
-
# overwriting the public interface draw/update/add etc. on_add/on_remove
|
|
214
|
-
# fire when the node enters/leaves the live tree (see #enter_tree), not at
|
|
215
|
-
# construction — so anchors and systems are available inside them.
|
|
216
|
-
|
|
217
439
|
def on_control(actions); end
|
|
218
440
|
def on_update(dt); end
|
|
219
|
-
def on_draw(renderer); end
|
|
441
|
+
def on_draw(renderer, view); end
|
|
220
442
|
def on_add; end
|
|
221
443
|
def on_remove; end
|
|
222
444
|
|
|
223
445
|
private
|
|
224
446
|
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
447
|
+
# hot-path
|
|
448
|
+
# rubocop:disable Style/ExplicitBlockArgument -- an explicit &block would
|
|
449
|
+
# allocate a Proc for every node, every frame, per viewport. `yield` is
|
|
450
|
+
# what keeps this path allocation-free, which culling_spec asserts.
|
|
451
|
+
def _in_local_space(renderer)
|
|
452
|
+
return yield if @parent.nil?
|
|
453
|
+
return yield if @rel_x.zero? && @rel_y.zero? && @rel_angle.zero?
|
|
454
|
+
|
|
455
|
+
renderer.translated(@rel_x, @rel_y) do
|
|
456
|
+
if @rel_angle.zero?
|
|
457
|
+
yield
|
|
458
|
+
else
|
|
459
|
+
renderer.rotated(@rel_angle * 180.0 / Math::PI, 0, 0) { yield }
|
|
460
|
+
end
|
|
461
|
+
end
|
|
230
462
|
end
|
|
463
|
+
# rubocop:enable Style/ExplicitBlockArgument
|
|
231
464
|
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
@children.each { it.draw(renderer) }
|
|
465
|
+
# hot-path
|
|
466
|
+
def _draw_content(renderer, view)
|
|
467
|
+
@components.each { it.draw(renderer, view) }
|
|
468
|
+
on_draw(renderer, view)
|
|
237
469
|
end
|
|
238
470
|
|
|
239
|
-
#
|
|
240
|
-
|
|
471
|
+
# hot-path
|
|
472
|
+
def draw_children(renderer, view)
|
|
473
|
+
_children_in_order.each { it.draw(renderer, view) }
|
|
474
|
+
end
|
|
241
475
|
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
476
|
+
# hot-path
|
|
477
|
+
def _children_in_order
|
|
478
|
+
_sort_children unless @children_sorted
|
|
479
|
+
@children
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def _sort_children
|
|
483
|
+
@children_sorted = true
|
|
484
|
+
return if @children.size < 2
|
|
485
|
+
|
|
486
|
+
@children.sort! do |a, b|
|
|
487
|
+
order = a.z <=> b.z
|
|
488
|
+
order.zero? ? a._sibling_order <=> b._sibling_order : order
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# hot-path
|
|
493
|
+
def _resolve_transform
|
|
494
|
+
@world_current = true
|
|
250
495
|
if @parent.nil?
|
|
251
|
-
@
|
|
252
|
-
@
|
|
496
|
+
@world_x = @world_y = 0
|
|
497
|
+
@world_angle = 0
|
|
253
498
|
return
|
|
254
499
|
end
|
|
255
500
|
|
|
256
|
-
pa = @parent.
|
|
257
|
-
if pa.zero?
|
|
258
|
-
@
|
|
259
|
-
@
|
|
501
|
+
pa = @parent.world_angle
|
|
502
|
+
if pa.zero?
|
|
503
|
+
@world_x = @parent.world_x + @rel_x
|
|
504
|
+
@world_y = @parent.world_y + @rel_y
|
|
260
505
|
else
|
|
261
506
|
cos = Math.cos(pa)
|
|
262
507
|
sin = Math.sin(pa)
|
|
263
|
-
@
|
|
264
|
-
@
|
|
508
|
+
@world_x = @parent.world_x + (@rel_x * cos) - (@rel_y * sin)
|
|
509
|
+
@world_y = @parent.world_y + (@rel_x * sin) + (@rel_y * cos)
|
|
510
|
+
end
|
|
511
|
+
@world_angle = pa + @rel_angle
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def _place_in_rotated_parent(offset_x, offset_y, pa)
|
|
515
|
+
cos = Math.cos(pa)
|
|
516
|
+
sin = Math.sin(pa)
|
|
517
|
+
self.rel_x = (offset_x * cos) + (offset_y * sin)
|
|
518
|
+
self.rel_y = (offset_y * cos) - (offset_x * sin)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
protected
|
|
522
|
+
|
|
523
|
+
attr_accessor :_sibling_order
|
|
524
|
+
|
|
525
|
+
def _children_unsorted! = @children_sorted = false
|
|
526
|
+
|
|
527
|
+
# hot-path
|
|
528
|
+
def _soil
|
|
529
|
+
return unless @world_current
|
|
530
|
+
|
|
531
|
+
@world_current = false
|
|
532
|
+
# rubocop:disable Style/SymbolProc -- `&:_soil` would call through
|
|
533
|
+
# Symbol#to_proc, which dispatches publicly and so cannot reach a
|
|
534
|
+
# protected method. An explicit receiver is the only form that works
|
|
535
|
+
# here, and it allocates no more than the symbol would.
|
|
536
|
+
@children.each { it._soil }
|
|
537
|
+
# rubocop:enable Style/SymbolProc
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
private
|
|
541
|
+
|
|
542
|
+
# hot-path
|
|
543
|
+
def _resolve_inherited
|
|
544
|
+
if @parent.nil?
|
|
545
|
+
@abs_input_owner = @input_owner
|
|
546
|
+
@abs_band = @band || Util::Z::DEFAULT
|
|
547
|
+
return
|
|
265
548
|
end
|
|
266
|
-
|
|
267
|
-
@
|
|
549
|
+
|
|
550
|
+
@abs_input_owner = @input_owner || @parent.abs_input_owner
|
|
551
|
+
@abs_band = @band || @parent.abs_band
|
|
268
552
|
end
|
|
269
553
|
end
|
|
270
554
|
end
|
data/lib/rgame/engine/path.rb
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module RGame
|
|
4
4
|
module Engine
|
|
5
|
-
# An ordered polyline of waypoints an entity walks along —
|
|
6
|
-
#
|
|
5
|
+
# An ordered polyline of waypoints an entity walks along — a road, a patrol
|
|
6
|
+
# route, a track. Pure data: it holds the waypoints and the precomputed per-segment
|
|
7
7
|
# lengths, so a follower walking it at runtime allocates nothing.
|
|
8
8
|
#
|
|
9
9
|
# Waypoints are stored flat (x0, y0, x1, y1, …) in one contiguous array rather than a
|
|
@@ -34,7 +34,7 @@ module RGame
|
|
|
34
34
|
def segment_length(index) = @segment_lengths[index]
|
|
35
35
|
|
|
36
36
|
# Shortest distance from the point (x, y) to the polyline — e.g. how far a spot is
|
|
37
|
-
# from the road, so a
|
|
37
|
+
# from the road, so a level can keep things from being placed on or beside it.
|
|
38
38
|
# Pure scalar maths, allocation-free.
|
|
39
39
|
def distance_to(x, y)
|
|
40
40
|
min = Float::INFINITY
|
|
@@ -47,12 +47,10 @@ module RGame
|
|
|
47
47
|
|
|
48
48
|
private
|
|
49
49
|
|
|
50
|
-
# Distance from (px, py) to the segment (ax, ay)-(bx, by): project the point onto the
|
|
51
|
-
# segment, clamp the projection to the segment's ends, and measure to that foot.
|
|
52
50
|
def segment_distance(px, py, ax, ay, bx, by)
|
|
53
51
|
abx = bx - ax
|
|
54
52
|
aby = by - ay
|
|
55
|
-
len2 = (abx * abx) + (aby * aby)
|
|
53
|
+
len2 = ((abx * abx) + (aby * aby)).to_f
|
|
56
54
|
t = len2.zero? ? 0.0 : (((px - ax) * abx) + ((py - ay) * aby)) / len2
|
|
57
55
|
t = 0.0 if t < 0.0
|
|
58
56
|
t = 1.0 if t > 1.0
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RGame
|
|
4
|
+
module Engine
|
|
5
|
+
# One person playing: their device, their bindings, their camera, and their
|
|
6
|
+
# own corner of the screen.
|
|
7
|
+
#
|
|
8
|
+
# player = Player.new(id: 0, device: Controls.gamepad(0))
|
|
9
|
+
# player.actions.held?(:fire) # what they are doing this tick
|
|
10
|
+
# player.camera # where they are looking
|
|
11
|
+
# player.ui # their HUD and menus, in screen space
|
|
12
|
+
#
|
|
13
|
+
# ## Why the player owns these and the scene does not
|
|
14
|
+
#
|
|
15
|
+
# A world has one simulation and any number of viewers. Two players share
|
|
16
|
+
# every NPC and every tile, but not a camera, not a set of bindings, and not
|
|
17
|
+
# a menu — so those belong to the viewer. Putting the camera on the scene
|
|
18
|
+
# works exactly until there are two of them, and putting it on a node in the
|
|
19
|
+
# world is worse: it forces the world to know how many times it is drawn.
|
|
20
|
+
#
|
|
21
|
+
# This is the model Unreal calls a LocalPlayer and Unity spreads across
|
|
22
|
+
# PlayerInput plus a camera.
|
|
23
|
+
#
|
|
24
|
+
# ## The action *names* are shared, the bindings are not
|
|
25
|
+
#
|
|
26
|
+
# Every player reads `:fire`. What triggers it is per player: one on Space,
|
|
27
|
+
# one on a pad's X button, and the same InputMap can serve both because a
|
|
28
|
+
# device only answers for its own kind of input. Each player gets their own
|
|
29
|
+
# ActionMapper, so their edge queries are independent — one player's press
|
|
30
|
+
# cannot consume another's.
|
|
31
|
+
class Player
|
|
32
|
+
Controls = RGame::Util::Controls
|
|
33
|
+
|
|
34
|
+
attr_reader :id, :camera, :ui, :mapper
|
|
35
|
+
attr_accessor :name
|
|
36
|
+
|
|
37
|
+
# `device` may be nil, meaning "nobody is driving this player yet" — a
|
|
38
|
+
# seat waiting for a controller. Polling one reads as nothing held rather
|
|
39
|
+
# than raising, so a game can show "Player 2: press a button" without a
|
|
40
|
+
# special case.
|
|
41
|
+
def initialize(id: 0, device: Controls::KEYBOARD, input_map: nil, camera: nil)
|
|
42
|
+
@id = id
|
|
43
|
+
@camera = camera || Camera.new
|
|
44
|
+
@mapper = ActionMapper.new(input_map || InputMap.default, device: device)
|
|
45
|
+
@ui = Node2D.new
|
|
46
|
+
@name = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def device = @mapper.device
|
|
50
|
+
def active? = !@mapper.device.nil?
|
|
51
|
+
|
|
52
|
+
# Reassigning is how a hot-plug lands: the pad that just arrived in a slot
|
|
53
|
+
# becomes this player's, and their bindings and camera carry on unchanged.
|
|
54
|
+
def device=(value)
|
|
55
|
+
@mapper.device = value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# This player's input for the current tick. Set by #poll, and a reused
|
|
59
|
+
# object — hold the Player, never this.
|
|
60
|
+
def actions = @mapper.actions
|
|
61
|
+
|
|
62
|
+
def poll(backend) = @mapper.poll(backend)
|
|
63
|
+
|
|
64
|
+
# What this player's map can answer for. The vocabulary is the game's, so
|
|
65
|
+
# it is the same for every player; the bindings behind it are not.
|
|
66
|
+
def input_map = @mapper.map
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|