rgame 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +135 -1
- data/README.md +67 -65
- data/docs/api/README.md +86 -82
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -165
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1032 -265
- data/docs/api/drawing.md +160 -160
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +149 -50
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +226 -148
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +397 -244
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +186 -73
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +395 -146
- data/docs/api/ui.md +883 -54
- data/docs/api/values.md +216 -33
- data/examples/assets/README.md +322 -0
- data/examples/assets/blip.ogg +0 -0
- data/examples/assets/glyphs.json +5 -0
- data/examples/assets/glyphs.png +0 -0
- data/examples/assets/hero.json +12 -0
- data/examples/assets/hero.png +0 -0
- data/examples/assets/icons.json +13 -0
- data/examples/assets/icons.png +0 -0
- data/examples/assets/music.ogg +0 -0
- data/examples/assets/skills.json +10 -0
- data/examples/assets/skills.png +0 -0
- data/examples/assets/tileset.png +0 -0
- data/examples/assets/tileset.tsx +65 -0
- data/examples/assets/town.tmx +26 -0
- data/examples/assets/ui.json +11 -0
- data/examples/assets/ui.png +0 -0
- data/examples/collision/locales/en.yml +8 -0
- data/examples/collision/main.rb +316 -0
- data/examples/collision_tiles/locales/en.yml +9 -0
- data/examples/collision_tiles/main.rb +274 -0
- data/examples/fullscreen/locales/en.yml +10 -0
- data/examples/fullscreen/main.rb +216 -0
- data/examples/game_menu/locales/en.yml +8 -0
- data/examples/game_menu/main.rb +170 -0
- data/examples/input_glyphs/locales/en.yml +14 -0
- data/examples/input_glyphs/main.rb +213 -0
- data/examples/jump_topdown/locales/en.yml +9 -0
- data/examples/jump_topdown/main.rb +178 -0
- data/examples/localization/locales/de.yml +12 -0
- data/examples/localization/locales/en.yml +13 -0
- data/examples/localization/main.rb +158 -0
- data/examples/menu_navigation/locales/en.yml +23 -0
- data/examples/menu_navigation/main.rb +365 -0
- data/examples/music/locales/en.yml +7 -0
- data/examples/music/main.rb +134 -0
- data/examples/pathfinding/locales/en.yml +17 -0
- data/examples/pathfinding/main.rb +298 -0
- data/examples/pooling/locales/en.yml +7 -0
- data/examples/pooling/main.rb +259 -0
- data/examples/quick_wheel/locales/en.yml +16 -0
- data/examples/quick_wheel/main.rb +184 -0
- data/examples/radial_menu/locales/en.yml +16 -0
- data/examples/radial_menu/main.rb +184 -0
- data/examples/save_load/locales/en.yml +11 -0
- data/examples/save_load/main.rb +207 -0
- data/examples/save_load_ids/locales/en.yml +11 -0
- data/examples/save_load_ids/main.rb +322 -0
- data/examples/scroll_map/locales/en.yml +4 -0
- data/examples/scroll_map/main.rb +140 -0
- data/examples/signals/locales/en.yml +6 -0
- data/examples/signals/main.rb +278 -0
- data/examples/skill_bar/locales/en.yml +14 -0
- data/examples/skill_bar/main.rb +159 -0
- data/examples/sound/locales/en.yml +6 -0
- data/examples/sound/main.rb +122 -0
- data/examples/split_screen/locales/en.yml +9 -0
- data/examples/split_screen/main.rb +304 -0
- data/examples/sprite/locales/en.yml +8 -0
- data/examples/sprite/main.rb +180 -0
- data/examples/timer/locales/en.yml +12 -0
- data/examples/timer/main.rb +273 -0
- data/examples/velocity/locales/en.yml +6 -0
- data/examples/velocity/main.rb +196 -0
- data/examples/walk/locales/en.yml +4 -0
- data/examples/walk/main.rb +99 -0
- data/exe/rgame +9 -0
- data/ext/rgame_core/app/app.c +33 -3
- data/ext/rgame_core/app/locale.c +67 -0
- data/ext/rgame_core/app/locale.h +28 -0
- data/ext/rgame_core/audio/audio.c +39 -2
- data/ext/rgame_core/example.rb +0 -49
- data/ext/rgame_core/extconf.rb +0 -125
- data/ext/rgame_core/include/rgame/core.h +38 -2
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +30 -7
- data/ext/rgame_core/ruby/core_ext.h +3 -0
- data/ext/rgame_core/ruby/locale_ext.c +44 -0
- data/ext/rgame_core/ruby/recording_ext.c +1 -1
- data/ext/rgame_core/ruby/renderer_ext.c +20 -20
- data/ext/rgame_util/extconf.rb +2 -20
- data/ext/rgame_util/route_search.c +305 -0
- data/ext/rgame_util/route_search.h +86 -0
- data/ext/rgame_util/route_search_ext.c +150 -0
- data/ext/rgame_util/solid_grid.c +58 -0
- data/ext/rgame_util/solid_grid.h +49 -0
- data/ext/rgame_util/solid_grid_ext.c +161 -0
- data/ext/rgame_util/tile_sweep.c +164 -0
- data/ext/rgame_util/tile_sweep.h +62 -0
- data/ext/rgame_util/tile_sweep_ext.c +155 -0
- data/ext/rgame_util/util_ext.c +3 -0
- data/ext/rgame_util/util_ext.h +15 -0
- data/lib/rgame/boot.rb +0 -10
- data/lib/rgame/cli/new_project.rb +139 -0
- data/lib/rgame/cli/templates/Gemfile.tt +23 -0
- data/lib/rgame/cli/templates/README.md.tt +93 -0
- data/lib/rgame/cli/templates/Rakefile.tt +9 -0
- data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
- data/lib/rgame/cli/templates/game.rb.tt +23 -0
- data/lib/rgame/cli/templates/gitignore.tt +12 -0
- data/lib/rgame/cli/templates/main.rb.tt +11 -0
- data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
- data/lib/rgame/cli/templates/rspec.tt +2 -0
- data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
- data/lib/rgame/cli/templates/ruby-version.tt +1 -0
- data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
- data/lib/rgame/cli.rb +66 -0
- data/lib/rgame/core/app.rb +6 -44
- data/lib/rgame/core/asset_manager.rb +13 -31
- data/lib/rgame/core/audio.rb +37 -16
- data/lib/rgame/core/font.rb +0 -3
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/renderer.rb +6 -63
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +2 -19
- data/lib/rgame/core/ui_atlas.rb +28 -13
- data/lib/rgame/core.rb +1 -8
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +1 -0
- data/lib/rgame/engine/audio_director.rb +36 -6
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +3 -3
- data/lib/rgame/engine/circle_collider.rb +4 -2
- data/lib/rgame/engine/collision_box.rb +26 -1
- data/lib/rgame/engine/collision_system.rb +110 -22
- data/lib/rgame/engine/component.rb +34 -10
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +27 -25
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +6 -5
- data/lib/rgame/engine/components/character_body.rb +20 -61
- data/lib/rgame/engine/components/circle_collider.rb +47 -11
- data/lib/rgame/engine/components/collision_world.rb +159 -31
- data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
- data/lib/rgame/engine/components/feet_collider.rb +61 -0
- data/lib/rgame/engine/components/hop.rb +76 -0
- data/lib/rgame/engine/components/identity.rb +73 -0
- data/lib/rgame/engine/components/mover.rb +285 -0
- data/lib/rgame/engine/components/navigator.rb +145 -0
- data/lib/rgame/engine/components/path_follow.rb +123 -31
- data/lib/rgame/engine/components/player_controller.rb +5 -2
- data/lib/rgame/engine/components/pool.rb +1 -1
- data/lib/rgame/engine/components/screen_wrap.rb +33 -11
- data/lib/rgame/engine/components/sprite.rb +12 -6
- data/lib/rgame/engine/components/targeting.rb +9 -11
- data/lib/rgame/engine/components/thrust_controller.rb +1 -1
- data/lib/rgame/engine/components/tile_world.rb +41 -13
- data/lib/rgame/engine/components/timer.rb +1 -1
- data/lib/rgame/engine/components/velocity.rb +23 -7
- data/lib/rgame/engine/components/wander_controller.rb +6 -2
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +4 -6
- data/lib/rgame/engine/debug_overlay.rb +5 -11
- data/lib/rgame/engine/i18n/plural.rb +45 -0
- data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
- data/lib/rgame/engine/i18n/template.rb +59 -0
- data/lib/rgame/engine/i18n.rb +276 -51
- data/lib/rgame/engine/input/action_mapper.rb +0 -26
- data/lib/rgame/engine/input/actions.rb +2 -8
- data/lib/rgame/engine/input/input_map.rb +39 -21
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +248 -133
- data/lib/rgame/engine/path.rb +4 -6
- data/lib/rgame/engine/players.rb +6 -13
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +4 -4
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +51 -19
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +2 -3
- data/lib/rgame/engine/tile_map_layer.rb +0 -2
- data/lib/rgame/engine/tileset.rb +2 -4
- data/lib/rgame/engine/timer.rb +2 -2
- data/lib/rgame/engine/ui/button.rb +248 -0
- data/lib/rgame/engine/ui/column.rb +20 -0
- data/lib/rgame/engine/ui/icon_button.rb +93 -0
- data/lib/rgame/engine/ui/menu.rb +246 -71
- data/lib/rgame/engine/ui/navigation.rb +57 -0
- data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
- data/lib/rgame/engine/ui/option_button.rb +163 -0
- data/lib/rgame/engine/ui/panel_button.rb +32 -0
- data/lib/rgame/engine/ui/panel_menu.rb +36 -0
- data/lib/rgame/engine/ui/pointing.rb +146 -0
- data/lib/rgame/engine/ui/radial_menu.rb +85 -0
- data/lib/rgame/engine/ui/ring.rb +55 -0
- data/lib/rgame/engine/ui/row.rb +21 -0
- data/lib/rgame/engine/ui/shape_style.rb +102 -0
- data/lib/rgame/engine/ui/stack.rb +58 -0
- data/lib/rgame/engine/ui/stepping.rb +93 -0
- data/lib/rgame/engine/ui/text_button.rb +59 -0
- data/lib/rgame/engine/viewports.rb +2 -5
- data/lib/rgame/engine/world_view.rb +5 -4
- data/lib/rgame/engine.rb +31 -25
- data/lib/rgame/game.rb +99 -27
- data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
- data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
- data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
- data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
- data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
- data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
- data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
- data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
- data/lib/rgame/rubocop/default.yml +39 -0
- data/lib/rgame/rubocop/plugin.rb +45 -0
- data/lib/rgame/rubocop.rb +11 -0
- data/lib/rgame/util/color.rb +20 -24
- data/lib/rgame/util/controls.rb +14 -28
- data/lib/rgame/util/route_search.rb +27 -0
- data/lib/rgame/util/save_file.rb +107 -0
- data/lib/rgame/util/solid_grid.rb +37 -0
- data/lib/rgame/util/tensor.rb +0 -9
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +4 -14
- data/lib/rgame/util.rb +4 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +152 -11
- data/lib/rgame/engine/actor.rb +0 -53
- data/lib/rgame/engine/body.rb +0 -49
- data/lib/rgame/engine/cached_label.rb +0 -33
- data/lib/rgame/engine/input/player_controller.rb +0 -14
- data/lib/rgame/engine/matrix.rb +0 -32
- data/lib/rgame/engine/resettable.rb +0 -67
- data/lib/rgame/engine/tile_collision.rb +0 -78
- data/lib/rgame/engine/ui/menu_item.rb +0 -84
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Quick wheel — a radial menu held open by a button, and chosen by letting go.
|
|
4
|
+
#
|
|
5
|
+
# Run it:
|
|
6
|
+
#
|
|
7
|
+
# ruby examples/quick_wheel/main.rb
|
|
8
|
+
#
|
|
9
|
+
# Hold Tab (or the left shoulder button), point with the arrow keys (or the left
|
|
10
|
+
# stick), and let go of Tab to choose. Let go with nothing pointed at to choose
|
|
11
|
+
# nothing. It exercises:
|
|
12
|
+
# - UI::RadialMenu with `trigger:` — the wheel is closed until the trigger goes
|
|
13
|
+
# down and chooses what is focused when it comes up;
|
|
14
|
+
# - UI::Pointing's grace window — a stick that springs back a moment before
|
|
15
|
+
# the trigger still chooses what it pointed at;
|
|
16
|
+
# - UI::Menu#on_opened / #on_closed — what the game does while the wheel is
|
|
17
|
+
# open, here slowing the world to a quarter of its speed;
|
|
18
|
+
# - InputMap.default.merge — declaring the trigger on Tab and the left shoulder.
|
|
19
|
+
#
|
|
20
|
+
# ## Letting go is the choice
|
|
21
|
+
#
|
|
22
|
+
# There is no confirm button here, and pressing one does nothing: the player's
|
|
23
|
+
# thumb is on the stick and a finger is on the shoulder, and releasing the
|
|
24
|
+
# shoulder is the only thing left to do. `examples/radial_menu` is the other
|
|
25
|
+
# style of the same wheel — always open, point, then press A — and is the same
|
|
26
|
+
# classes without `trigger:`.
|
|
27
|
+
#
|
|
28
|
+
# ## The stick springs back first
|
|
29
|
+
#
|
|
30
|
+
# Letting go of a stick and a shoulder button at once is not at once: the stick
|
|
31
|
+
# is back in the middle a frame or two before the button comes up. With nothing
|
|
32
|
+
# to account for that, the release would find the stick at rest and choose
|
|
33
|
+
# nothing almost every time. So focus survives the dead zone for
|
|
34
|
+
# `Pointing::GRACE`, 0.15 s, which a wheel with a trigger gets without asking.
|
|
35
|
+
# On a keyboard the same thing happens between letting go of an arrow key and
|
|
36
|
+
# letting go of Tab. Hold the arrow at rest for longer than that, and the
|
|
37
|
+
# release chooses nothing — which is how a player changes their mind.
|
|
38
|
+
#
|
|
39
|
+
# ## The world is the game's business
|
|
40
|
+
#
|
|
41
|
+
# The wheel says when it opens and closes. Whether the world pauses, slows or
|
|
42
|
+
# carries on is a decision about the game, not about menus, so it is made here:
|
|
43
|
+
# the dots drift at a quarter speed while the wheel is open.
|
|
44
|
+
#
|
|
45
|
+
# ## What this example does not solve
|
|
46
|
+
#
|
|
47
|
+
# Captions on the wheel, and a wheel centred on the view — see
|
|
48
|
+
# `examples/radial_menu`. A stick that overshoots the middle as it springs back
|
|
49
|
+
# can briefly point at the opposite icon, which the grace window does not
|
|
50
|
+
# cover.
|
|
51
|
+
|
|
52
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
|
|
53
|
+
require 'rgame/game'
|
|
54
|
+
|
|
55
|
+
WIDTH = 640
|
|
56
|
+
HEIGHT = 480
|
|
57
|
+
ASSETS = File.expand_path('../assets', __dir__)
|
|
58
|
+
LOCALES = File.expand_path('locales', __dir__) # the text on screen: locales/en.yml
|
|
59
|
+
Controls = RGame::Util::Controls
|
|
60
|
+
|
|
61
|
+
# The part of the scene the wheel slows down. A time scale applied to `dt` on
|
|
62
|
+
# the way in is the whole of slow motion, because time only enters through
|
|
63
|
+
# `update`.
|
|
64
|
+
class World < RGame::Engine::Node2D
|
|
65
|
+
SLOWED = 0.25
|
|
66
|
+
|
|
67
|
+
attr_accessor :time_scale
|
|
68
|
+
|
|
69
|
+
def initialize(**)
|
|
70
|
+
super
|
|
71
|
+
@time_scale = 1.0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def update(dt) = super(dt * @time_scale)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# A square that drifts and bounces off the window's edges.
|
|
78
|
+
class Dot < RGame::Engine::Node2D
|
|
79
|
+
SIZE = 12
|
|
80
|
+
COLOR = RGame::Util::Color.new(120, 180, 150)
|
|
81
|
+
|
|
82
|
+
def initialize(speed_x:, speed_y:, **)
|
|
83
|
+
super(**)
|
|
84
|
+
@speed_x = speed_x
|
|
85
|
+
@speed_y = speed_y
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def on_update(dt)
|
|
89
|
+
self.x += @speed_x * dt
|
|
90
|
+
self.y += @speed_y * dt
|
|
91
|
+
@speed_x = -@speed_x unless x.between?(0, WIDTH - SIZE)
|
|
92
|
+
@speed_y = -@speed_y unless y.between?(0, HEIGHT - SIZE)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def on_draw(renderer, _view) = renderer.rect(0, 0, SIZE, SIZE, color: COLOR)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The wheel, held open by `:quick_menu`, and what it last chose.
|
|
99
|
+
class QuickWheel < RGame::Engine::Node2D
|
|
100
|
+
UI = RGame::Engine::UI
|
|
101
|
+
|
|
102
|
+
RADIUS = 150
|
|
103
|
+
SLOT = 64
|
|
104
|
+
|
|
105
|
+
# Clockwise from the top. Each image names an entry of icons.json's `images`.
|
|
106
|
+
ICONS = [
|
|
107
|
+
%i[home home],
|
|
108
|
+
%i[settings gear],
|
|
109
|
+
%i[save save],
|
|
110
|
+
%i[favourite star],
|
|
111
|
+
%i[trophies trophy],
|
|
112
|
+
%i[sound audio_on],
|
|
113
|
+
%i[music music_on],
|
|
114
|
+
%i[locked locked]
|
|
115
|
+
].freeze
|
|
116
|
+
|
|
117
|
+
DISC = UI::ShapeStyle.new(shape: :disc)
|
|
118
|
+
|
|
119
|
+
attr_reader :chosen
|
|
120
|
+
|
|
121
|
+
def initialize(world:, **)
|
|
122
|
+
super(**)
|
|
123
|
+
@chosen = nil
|
|
124
|
+
@menu = add_node(UI::RadialMenu.new(radius: RADIUS, button_width: SLOT, trigger: :quick_menu))
|
|
125
|
+
ICONS.each { |key, image| add_icon(key, image) }
|
|
126
|
+
@menu.on_opened { world.time_scale = World::SLOWED }
|
|
127
|
+
@menu.on_closed { world.time_scale = 1.0 }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def add_icon(key, image)
|
|
133
|
+
button = @menu.add(UI::IconButton.new(image: image, style: DISC, enabled: image != :locked))
|
|
134
|
+
button.on_activated { @chosen = key }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The captions. Added last, so its final line is the last text of every frame —
|
|
139
|
+
# which is what the drive script reads.
|
|
140
|
+
class Caption < RGame::Engine::Node2D
|
|
141
|
+
NAMES = QuickWheel::ICONS.to_h { |key, _| [key, RGame::Engine::Text.new(key, scope: 'items')] }.freeze
|
|
142
|
+
|
|
143
|
+
def initialize(wheel:, **)
|
|
144
|
+
super(**)
|
|
145
|
+
@wheel = wheel
|
|
146
|
+
@help = RGame::Engine::Text.new('help.wheel')
|
|
147
|
+
@nothing = RGame::Engine::Text.new('status.nothing')
|
|
148
|
+
@chosen = RGame::Engine::Text.new('status.chosen', :item)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def on_draw(renderer, _view)
|
|
152
|
+
renderer.text(@help, 12, 12)
|
|
153
|
+
chosen = @wheel.chosen
|
|
154
|
+
renderer.text(chosen ? @chosen.with(item: NAMES.fetch(chosen).to_s) : @nothing, 12, HEIGHT - 30)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class Scene < RGame::Engine::Node2D
|
|
159
|
+
DOTS = [[60, 80, 90, 40], [400, 120, -70, 60], [200, 360, 50, -80], [520, 300, -100, -30]].freeze
|
|
160
|
+
|
|
161
|
+
def on_add
|
|
162
|
+
world = add_node(World.new)
|
|
163
|
+
DOTS.each { |x, y, speed_x, speed_y| world.add_node(Dot.new(x: x, y: y, speed_x: speed_x, speed_y: speed_y)) }
|
|
164
|
+
wheel = add_node(QuickWheel.new(world: world, x: WIDTH / 2, y: (HEIGHT / 2) + 6))
|
|
165
|
+
add_node(Caption.new(wheel: wheel))
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
game = RGame::Game.new(
|
|
170
|
+
root: Scene.new,
|
|
171
|
+
caption: 'Quick wheel',
|
|
172
|
+
width: WIDTH,
|
|
173
|
+
height: HEIGHT,
|
|
174
|
+
media_root: ASSETS,
|
|
175
|
+
locales: LOCALES,
|
|
176
|
+
input_map: RGame::Engine::InputMap.default.merge(
|
|
177
|
+
quick_menu: { buttons: [Controls::KEY_TAB, Controls::PAD_LEFT_SHOULDER] }
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# The icons are registered by name, once: the atlas cuts each from the strip.
|
|
182
|
+
game.renderer.register_ui_atlas(game.assets.ui_atlas('icons.json'))
|
|
183
|
+
|
|
184
|
+
game.start
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# The English table: every string this example draws.
|
|
2
|
+
en:
|
|
3
|
+
help:
|
|
4
|
+
point: Point the stick (or arrow keys) at an icon, then press A (or Enter)
|
|
5
|
+
status:
|
|
6
|
+
nothing: "Chosen: nothing yet"
|
|
7
|
+
chosen: "Chosen: %{item}"
|
|
8
|
+
items:
|
|
9
|
+
home: Home
|
|
10
|
+
settings: Settings
|
|
11
|
+
save: Save
|
|
12
|
+
favourite: Favourite
|
|
13
|
+
trophies: Trophies
|
|
14
|
+
sound: Sound
|
|
15
|
+
music: Music
|
|
16
|
+
locked: Locked
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Radial menu — a quick menu of icons, chosen by pointing rather than by stepping
|
|
4
|
+
# through a list.
|
|
5
|
+
#
|
|
6
|
+
# Run it:
|
|
7
|
+
#
|
|
8
|
+
# ruby examples/radial_menu/main.rb
|
|
9
|
+
#
|
|
10
|
+
# Point the left stick at an icon and press A. On a keyboard, hold the arrow
|
|
11
|
+
# keys — two at once for a diagonal — and press Enter or Space. The chosen icon
|
|
12
|
+
# appears, doubled, in the middle of the wheel. It exercises:
|
|
13
|
+
# - UI::RadialMenu — a UI::Menu that builds its own UI::Ring and
|
|
14
|
+
# UI::Pointing, and draws the backdrop, the dead zone and the pointer;
|
|
15
|
+
# - UI::IconButton — each entry, a picture tinted by its state;
|
|
16
|
+
# - UI::ShapeStyle — the disc behind every icon, outlined while focused;
|
|
17
|
+
# - `ui_radial_x` / `ui_radial_y` — the two axes Pointing reads, from the
|
|
18
|
+
# universal set every InputMap carries, so nothing here declares an action;
|
|
19
|
+
# - a UI atlas's `images` — the eight icons, cut from one strip and registered
|
|
20
|
+
# by name in one call.
|
|
21
|
+
#
|
|
22
|
+
# ## The direction is the selection
|
|
23
|
+
#
|
|
24
|
+
# A list menu moves focus *relative* to where it already is: down means "the
|
|
25
|
+
# next one". A stick is bad at that and good at pointing, so here nothing is
|
|
26
|
+
# stepped through at all. Whichever button lies closest to the direction the
|
|
27
|
+
# stick points in is focused, which on a ring cuts the circle into one sector per
|
|
28
|
+
# button, centred on it. Eight buttons is the number that makes a keyboard a
|
|
29
|
+
# first-class input too: the arrow keys, alone and in pairs, produce exactly
|
|
30
|
+
# eight directions, one per icon.
|
|
31
|
+
#
|
|
32
|
+
# That is the only thing that makes this a wheel rather than a list. The menu,
|
|
33
|
+
# its buttons and what confirming does are the same classes `examples/game_menu`
|
|
34
|
+
# uses; a RadialMenu is a UI::Menu handed a UI::Ring where that one has a
|
|
35
|
+
# UI::Column, and UI::Pointing where that one keeps the default UI::Stepping.
|
|
36
|
+
#
|
|
37
|
+
# `examples/quick_wheel` is the other common style of the same wheel: closed until
|
|
38
|
+
# a button is held, and chosen by letting that button go.
|
|
39
|
+
#
|
|
40
|
+
# ## Letting go selects nothing
|
|
41
|
+
#
|
|
42
|
+
# The lighter disc around the middle is the **dead zone**, drawn to scale: the
|
|
43
|
+
# pointer's tip inside it means the stick is not deflected far enough to mean
|
|
44
|
+
# anything, and no button is focused. That is what makes the obvious mistake
|
|
45
|
+
# impossible — a player who lets the stick spring back and then presses A has
|
|
46
|
+
# not asked for whatever the stick passed over on its way home, and gets nothing.
|
|
47
|
+
#
|
|
48
|
+
# It is a length on the combined vector, and much bigger than the per-axis dead
|
|
49
|
+
# zone every action already has. That one only stops a worn stick drifting; it
|
|
50
|
+
# would let a stick barely off centre choose.
|
|
51
|
+
#
|
|
52
|
+
# **Locked** is disabled, and pointing at it focuses nothing — the same rule a
|
|
53
|
+
# list follows by skipping it.
|
|
54
|
+
#
|
|
55
|
+
# ## White art, coloured by a tint
|
|
56
|
+
#
|
|
57
|
+
# The icons are white, and IconButton draws each one multiplied by a colour for
|
|
58
|
+
# its state — grey at rest, white while focused, dim while disabled — so one
|
|
59
|
+
# picture serves all four looks. Dark art would take none of them. While pressed
|
|
60
|
+
# the disc is gold, and the disc's ShapeStyle says the icon goes dark on it: what
|
|
61
|
+
# reads on a fill is the style's to decide, since it chose the fill.
|
|
62
|
+
#
|
|
63
|
+
# The icons are 50 pixels in 64-pixel slots and the chosen one is drawn at scale
|
|
64
|
+
# 2: images sample nearest-neighbour, so only a whole-number scale stays sharp.
|
|
65
|
+
#
|
|
66
|
+
# ## What this example does not solve
|
|
67
|
+
#
|
|
68
|
+
# Captions. The icons carry no text, so the wheel says what was chosen only in
|
|
69
|
+
# the line along the bottom. The wheel is also at a fixed position rather than
|
|
70
|
+
# centred on the view, because its buttons are placed when they are added and the
|
|
71
|
+
# view's size only exists at draw time — the same limit `examples/game_menu`
|
|
72
|
+
# names.
|
|
73
|
+
|
|
74
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
|
|
75
|
+
require 'rgame/game'
|
|
76
|
+
|
|
77
|
+
WIDTH = 640
|
|
78
|
+
HEIGHT = 480
|
|
79
|
+
ASSETS = File.expand_path('../assets', __dir__)
|
|
80
|
+
LOCALES = File.expand_path('locales', __dir__) # the text on screen: locales/en.yml
|
|
81
|
+
|
|
82
|
+
# The wheel and what it chooses. The wheel itself — backdrop, dead zone, pointer
|
|
83
|
+
# and buttons — is a UI::RadialMenu; the chosen icon is a node added after it,
|
|
84
|
+
# because between nodes the tree decides what lands on top, and anything this
|
|
85
|
+
# node drew itself would sit under the menu's backdrop.
|
|
86
|
+
class QuickMenu < RGame::Engine::Node2D
|
|
87
|
+
UI = RGame::Engine::UI
|
|
88
|
+
|
|
89
|
+
RADIUS = 150
|
|
90
|
+
SLOT = 64
|
|
91
|
+
|
|
92
|
+
# Clockwise from the top. Each image names an entry of icons.json's `images`.
|
|
93
|
+
ICONS = [
|
|
94
|
+
%i[home home],
|
|
95
|
+
%i[settings gear],
|
|
96
|
+
%i[save save],
|
|
97
|
+
%i[favourite star],
|
|
98
|
+
%i[trophies trophy],
|
|
99
|
+
%i[sound audio_on],
|
|
100
|
+
%i[music music_on],
|
|
101
|
+
%i[locked locked]
|
|
102
|
+
].freeze
|
|
103
|
+
|
|
104
|
+
DISC = UI::ShapeStyle.new(shape: :disc)
|
|
105
|
+
|
|
106
|
+
attr_reader :chosen, :chosen_image
|
|
107
|
+
|
|
108
|
+
def initialize(**)
|
|
109
|
+
super
|
|
110
|
+
@chosen = nil
|
|
111
|
+
@chosen_image = nil
|
|
112
|
+
@menu = add_node(UI::RadialMenu.new(radius: RADIUS, button_width: SLOT))
|
|
113
|
+
ICONS.each { |key, image| add_icon(key, image) }
|
|
114
|
+
add_node(ChosenIcon.new(menu: self))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def add_icon(key, image)
|
|
120
|
+
button = @menu.add(UI::IconButton.new(image: image, style: DISC, enabled: image != :locked))
|
|
121
|
+
button.on_activated do
|
|
122
|
+
@chosen = key
|
|
123
|
+
@chosen_image = image
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The chosen icon, doubled, in the middle of the wheel.
|
|
129
|
+
class ChosenIcon < RGame::Engine::Node2D
|
|
130
|
+
SCALE = 2
|
|
131
|
+
|
|
132
|
+
def initialize(menu:, **)
|
|
133
|
+
super(**)
|
|
134
|
+
@menu = menu
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def on_draw(renderer, _view)
|
|
138
|
+
image = @menu.chosen_image
|
|
139
|
+
renderer.image(image, 0, 0, scale: SCALE) if image
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The captions. Added after the wheel, so it draws last and its final line is
|
|
144
|
+
# the last text of every frame — which is what the drive script reads.
|
|
145
|
+
class Caption < RGame::Engine::Node2D
|
|
146
|
+
NAMES = QuickMenu::ICONS.to_h { |key, _| [key, RGame::Engine::Text.new(key, scope: 'items')] }.freeze
|
|
147
|
+
|
|
148
|
+
def initialize(menu:, **)
|
|
149
|
+
super(**)
|
|
150
|
+
@menu = menu
|
|
151
|
+
@help = RGame::Engine::Text.new('help.point')
|
|
152
|
+
@nothing = RGame::Engine::Text.new('status.nothing')
|
|
153
|
+
@chosen = RGame::Engine::Text.new('status.chosen', :item)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def on_draw(renderer, _view)
|
|
157
|
+
renderer.text(@help, 12, 12)
|
|
158
|
+
chosen = @menu.chosen
|
|
159
|
+
renderer.text(chosen ? @chosen.with(item: NAMES.fetch(chosen).to_s) : @nothing, 12, HEIGHT - 30)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
class Scene < RGame::Engine::Node2D
|
|
164
|
+
def on_add
|
|
165
|
+
menu = add_node(QuickMenu.new(x: WIDTH / 2, y: (HEIGHT / 2) + 6))
|
|
166
|
+
add_node(Caption.new(menu: menu))
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
game = RGame::Game.new(
|
|
171
|
+
root: Scene.new,
|
|
172
|
+
caption: 'Radial menu',
|
|
173
|
+
width: WIDTH,
|
|
174
|
+
height: HEIGHT,
|
|
175
|
+
media_root: ASSETS,
|
|
176
|
+
locales: LOCALES
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# An image id that is a Symbol is a name rather than a path, so the icons are
|
|
180
|
+
# registered once, by hand: the atlas cuts each from the strip and registers it
|
|
181
|
+
# under its name.
|
|
182
|
+
game.renderer.register_ui_atlas(game.assets.ui_atlas('icons.json'))
|
|
183
|
+
|
|
184
|
+
game.start
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# The English table: every string this example draws.
|
|
2
|
+
en:
|
|
3
|
+
help:
|
|
4
|
+
walk: Arrows walk the dog
|
|
5
|
+
keys: F5 saves F9 loads Delete discards the save
|
|
6
|
+
status:
|
|
7
|
+
saved: saved — quit and run again, or press F9
|
|
8
|
+
loaded: loaded
|
|
9
|
+
deleted: save deleted
|
|
10
|
+
fresh: no save yet — walk the dog, then press F5
|
|
11
|
+
restored: loaded the save from last time
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Save and load — writing game state to disk, and putting it back.
|
|
4
|
+
#
|
|
5
|
+
# Run it:
|
|
6
|
+
#
|
|
7
|
+
# ruby examples/save_load/main.rb
|
|
8
|
+
#
|
|
9
|
+
# Arrow keys walk the dog. **F5** saves, **F9** loads, **Delete** throws the save
|
|
10
|
+
# away. Quit and run it again: the dog and the sheep are where you left them,
|
|
11
|
+
# because a save found at startup is loaded before the first frame. It
|
|
12
|
+
# exercises:
|
|
13
|
+
# - Util::SaveFile — one JSON file, written atomically, read forgivingly;
|
|
14
|
+
# - the same Node2D + CharacterBody + controller composition as
|
|
15
|
+
# `examples/walk`, with the flock on a WanderController instead;
|
|
16
|
+
# - `RGAME_SEED`, so a run without a save is reproducible.
|
|
17
|
+
#
|
|
18
|
+
# ## The tree is not saved, and nothing here tries
|
|
19
|
+
#
|
|
20
|
+
# This is the shape every comparable engine settles on: **a scene is a recipe
|
|
21
|
+
# and a save file is state.** The scene builds itself the same way every time,
|
|
22
|
+
# and the save supplies the handful of facts that differ between runs. Godot's
|
|
23
|
+
# own guidance is this, and it is why an rgame node has no name, no path and no
|
|
24
|
+
# id — nothing needs one.
|
|
25
|
+
#
|
|
26
|
+
# Two kinds of thing are being restored here, and they need different answers.
|
|
27
|
+
#
|
|
28
|
+
# **The dog is singular, so a variable is its identity.** `@dog` is set where the
|
|
29
|
+
# dog is built, and `load_state` writes straight to it. There is no lookup, no
|
|
30
|
+
# name, nothing to match up — the code that made the dog is the code that puts
|
|
31
|
+
# it back, and it never lost track of which one it was.
|
|
32
|
+
#
|
|
33
|
+
# **The sheep are interchangeable, so their order is their identity.** The save
|
|
34
|
+
# holds an array of positions; loading walks the flock and the array together.
|
|
35
|
+
# If sheep three and seven swapped places nothing observable would change, which
|
|
36
|
+
# is exactly what "interchangeable" means, and it is the property that makes an
|
|
37
|
+
# array enough.
|
|
38
|
+
#
|
|
39
|
+
# ## What would break this, and what to do about it
|
|
40
|
+
#
|
|
41
|
+
# The flock is a fixed size and no sheep can die. That is what lets an array
|
|
42
|
+
# index stand in for identity — and it is the assumption to check before copying
|
|
43
|
+
# this shape. The moment sheep can be killed, and the survivors must keep their
|
|
44
|
+
# own hunger and name across a save, the array stops meaning anything and each
|
|
45
|
+
# sheep needs an id of its own.
|
|
46
|
+
#
|
|
47
|
+
# `Components::Identity` is that id, and `examples/save_load_ids` is where it is
|
|
48
|
+
# shown: sheep that can be lost, and a dog chasing one particular sheep — a
|
|
49
|
+
# reference between two saved things, which is what genuinely forces a name.
|
|
50
|
+
# Unreal's save libraries bolt a GUID onto every actor for the same reason, and
|
|
51
|
+
# Unity's community does the same.
|
|
52
|
+
#
|
|
53
|
+
# ## Position is not the whole of where something is
|
|
54
|
+
#
|
|
55
|
+
# The sheep resume standing still, because a WanderController's timer is not
|
|
56
|
+
# saved. That is a deliberate simplification and worth naming, because the real
|
|
57
|
+
# version of this bug is not funny: a game that saves a falling player's position
|
|
58
|
+
# and not their velocity restores them hanging in the air.
|
|
59
|
+
|
|
60
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
|
|
61
|
+
require 'rgame/game'
|
|
62
|
+
|
|
63
|
+
Controls = RGame::Util::Controls
|
|
64
|
+
|
|
65
|
+
WIDTH = 640
|
|
66
|
+
HEIGHT = 480
|
|
67
|
+
ASSETS = File.expand_path('../assets', __dir__)
|
|
68
|
+
LOCALES = File.expand_path('locales', __dir__) # the text on screen: locales/en.yml
|
|
69
|
+
|
|
70
|
+
DOG_SPEED = 150.0
|
|
71
|
+
SHEEP_SPEED = 40.0
|
|
72
|
+
SHEEP_COUNT = 8
|
|
73
|
+
|
|
74
|
+
# Seeded, so a fresh run with no save always lays the flock out the same way and
|
|
75
|
+
# two driven runs can be compared.
|
|
76
|
+
DEFAULT_SEED = 0x5EED
|
|
77
|
+
|
|
78
|
+
class Pasture < RGame::Engine::Node2D
|
|
79
|
+
GRASS = RGame::Util::Color.new(96, 140, 84)
|
|
80
|
+
DOG = RGame::Util::Color.new(70, 50, 40)
|
|
81
|
+
SHEEP = RGame::Util::Color.new(240, 240, 235)
|
|
82
|
+
DOG_R = 11
|
|
83
|
+
SHEEP_R = 9
|
|
84
|
+
|
|
85
|
+
STATUS = { saved: RGame::Engine::Text.new('status.saved'),
|
|
86
|
+
loaded: RGame::Engine::Text.new('status.loaded'),
|
|
87
|
+
deleted: RGame::Engine::Text.new('status.deleted'),
|
|
88
|
+
fresh: RGame::Engine::Text.new('status.fresh'),
|
|
89
|
+
restored: RGame::Engine::Text.new('status.restored') }.freeze
|
|
90
|
+
|
|
91
|
+
def initialize(save:, **)
|
|
92
|
+
super(**)
|
|
93
|
+
@save = save
|
|
94
|
+
@help = RGame::Engine::Text.new('help.walk')
|
|
95
|
+
@keys = RGame::Engine::Text.new('help.keys')
|
|
96
|
+
@rng = Random.new(ENV.fetch('RGAME_SEED', DEFAULT_SEED).to_i)
|
|
97
|
+
@flock = []
|
|
98
|
+
@status = :fresh
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def on_add
|
|
102
|
+
# Built first, always, and identically. The save does not decide what exists
|
|
103
|
+
# — only where it is.
|
|
104
|
+
@dog = add_node(build_walker(RGame::Engine::Components::PlayerController.new,
|
|
105
|
+
DOG_SPEED, WIDTH / 2, HEIGHT / 2))
|
|
106
|
+
SHEEP_COUNT.times do
|
|
107
|
+
@flock << add_node(build_walker(
|
|
108
|
+
RGame::Engine::Components::WanderController.new(rng: @rng),
|
|
109
|
+
SHEEP_SPEED, @rng.rand(WIDTH - 40) + 20, @rng.rand(HEIGHT - 80) + 60
|
|
110
|
+
))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
return unless @save.exist?
|
|
114
|
+
|
|
115
|
+
load_state
|
|
116
|
+
@status = :restored
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def on_control(actions)
|
|
120
|
+
save_state if actions.pressed?(:save)
|
|
121
|
+
load_state if actions.pressed?(:load)
|
|
122
|
+
drop_save if actions.pressed?(:drop)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def on_draw(renderer, view)
|
|
126
|
+
renderer.rect(0, 0, view.width, view.height, color: GRASS)
|
|
127
|
+
@flock.each { |sheep| renderer.circle(sheep.x, sheep.y, SHEEP_R, color: SHEEP) }
|
|
128
|
+
renderer.circle(@dog.x, @dog.y, DOG_R, color: DOG)
|
|
129
|
+
|
|
130
|
+
renderer.text(@help, 12, 12)
|
|
131
|
+
renderer.text(@keys, 12, 34)
|
|
132
|
+
renderer.text(STATUS.fetch(@status), 12, 56)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# The whole save: two facts, written as plain numbers.
|
|
138
|
+
#
|
|
139
|
+
# Reading `sheep.x` from outside the node is fine here and is not the thing
|
|
140
|
+
# Game/DrawInLocalSpace forbids — that rule is about a node reading its *own*
|
|
141
|
+
# position while drawing, when its transform has already been applied.
|
|
142
|
+
def save_state
|
|
143
|
+
@save.write(dog: [@dog.x, @dog.y], sheep: @flock.map { |sheep| [sheep.x, sheep.y] })
|
|
144
|
+
@status = :saved
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def load_state
|
|
148
|
+
state = @save.read
|
|
149
|
+
place(@dog, state[:dog])
|
|
150
|
+
# `zip` stops at the shorter of the two, so a save written when the flock
|
|
151
|
+
# was a different size restores what it can instead of raising. That is the
|
|
152
|
+
# cheap half of the versioning problem; the expensive half is a save whose
|
|
153
|
+
# *shape* changed, which needs a version number in the file.
|
|
154
|
+
@flock.zip(state.fetch(:sheep, [])) { |sheep, position| place(sheep, position) }
|
|
155
|
+
@status = :loaded
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def drop_save
|
|
159
|
+
@save.delete
|
|
160
|
+
@status = :deleted
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def place(node, position)
|
|
164
|
+
return if position.nil?
|
|
165
|
+
|
|
166
|
+
node.x, node.y = position
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def build_walker(controller, speed, x, y)
|
|
170
|
+
node = RGame::Engine::Node2D.new(x: x, y: y)
|
|
171
|
+
node.add_component(RGame::Engine::Components::CharacterBody.new(speed: speed))
|
|
172
|
+
node.add_component(controller)
|
|
173
|
+
node
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# `dir:` is normally left out, and the file lands in the platform's own data
|
|
178
|
+
# directory — ~/.local/share, Application Support or %APPDATA%. It is overridable
|
|
179
|
+
# here so a driven run can write somewhere disposable instead of into the home
|
|
180
|
+
# directory of whoever happens to be running it.
|
|
181
|
+
save = RGame::Util::SaveFile.new('pasture.json', game: 'rgame-examples',
|
|
182
|
+
dir: ENV.fetch('RGAME_SAVE_DIR', nil))
|
|
183
|
+
|
|
184
|
+
game = RGame::Game.new(
|
|
185
|
+
root: Pasture.new(save: save),
|
|
186
|
+
caption: 'Save and load',
|
|
187
|
+
width: WIDTH,
|
|
188
|
+
height: HEIGHT,
|
|
189
|
+
media_root: ASSETS,
|
|
190
|
+
locales: LOCALES,
|
|
191
|
+
# F5 and F9 rather than S and L, and the reason is worth knowing: **a key
|
|
192
|
+
# already in the default map keeps doing its default job too.** `move_y` is
|
|
193
|
+
# bound to W and S, so an action added on S saves *and* walks the dog
|
|
194
|
+
# downwards — both fire, because two actions may read one key. Nothing is
|
|
195
|
+
# broken and nothing warns; the game just moves when you asked it to save.
|
|
196
|
+
#
|
|
197
|
+
# F5 and F9 are free, and are what a player already expects quicksave and
|
|
198
|
+
# quickload to be. F1 and F2 are not free: RGame::Game keeps those for the
|
|
199
|
+
# debug overlay and quit.
|
|
200
|
+
input_map: RGame::Engine::InputMap.default.merge(
|
|
201
|
+
save: { buttons: [Controls::KEY_F5] },
|
|
202
|
+
load: { buttons: [Controls::KEY_F9] },
|
|
203
|
+
drop: { buttons: [Controls::KEY_DELETE] }
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
game.start
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# The English table: every string this example draws.
|
|
2
|
+
en:
|
|
3
|
+
help:
|
|
4
|
+
keys: Arrows walk Tab targets Space shears N adds a sheep
|
|
5
|
+
save: F5 saves F9 loads Delete discards the save
|
|
6
|
+
status:
|
|
7
|
+
fresh: no save yet — shear a few, add one, then press F5
|
|
8
|
+
restored: loaded the save from last time
|
|
9
|
+
saved: saved — quit, run again, then press N for a fresh id
|
|
10
|
+
loaded: loaded
|
|
11
|
+
deleted: save deleted
|