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
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RGame
|
|
4
|
-
module Engine
|
|
5
|
-
# Holds a display string and rebuilds it only when its source value changes, so a
|
|
6
|
-
# per-frame draw can show the cached copy without interpolating (allocating) a String
|
|
7
|
-
# every frame. The format block runs only on an actual change.
|
|
8
|
-
#
|
|
9
|
-
# @score_label = Engine::CachedLabel.new { |score| "Score: #{score}" } # build the block once
|
|
10
|
-
# renderer.text(@score_label[@score], 12, 10) # in on_draw: cached, no alloc
|
|
11
|
-
#
|
|
12
|
-
# `@score_label[value]` returns the cached string when `value` is unchanged, and
|
|
13
|
-
# otherwise rebuilds it through the block. Construct it (and its block) outside the
|
|
14
|
-
# per-frame path — e.g. in `on_add` — so the interpolation isn't itself a per-frame cost.
|
|
15
|
-
class CachedLabel
|
|
16
|
-
def initialize(&format)
|
|
17
|
-
raise ArgumentError, 'CachedLabel needs a format block' unless format
|
|
18
|
-
|
|
19
|
-
@format = format
|
|
20
|
-
@value = nil
|
|
21
|
-
@text = nil
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# The cached string for `value`, rebuilt only when `value` differs from last time.
|
|
25
|
-
def [](value)
|
|
26
|
-
return @text if !@text.nil? && value == @value
|
|
27
|
-
|
|
28
|
-
@value = value
|
|
29
|
-
@text = @format.call(value)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RGame
|
|
4
|
-
module Engine
|
|
5
|
-
# Drives an Actor from player input: turns the move_x/move_y actions into a
|
|
6
|
-
# movement intent. NPC controllers implement the same `intent(dt, input)` seam
|
|
7
|
-
# and ignore `input`.
|
|
8
|
-
class PlayerController
|
|
9
|
-
def intent(_dt, input)
|
|
10
|
-
[input.axis(:move_x), input.axis(:move_y)]
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
data/lib/rgame/engine/matrix.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RGame
|
|
4
|
-
module Engine
|
|
5
|
-
# A fixed-size 2-D grid backed by a single flat (row-major) array, addressed as
|
|
6
|
-
# [x, y]. The flat backing keeps the whole grid in one contiguous allocation —
|
|
7
|
-
# cheaper than an array-of-arrays and a straightforward shape to later move into a
|
|
8
|
-
# C-level buffer. Pure; no bounds checking on the hot path (callers stay in range).
|
|
9
|
-
#
|
|
10
|
-
# This stays Ruby, unlike the engine's old `Tensor`, which was deleted in
|
|
11
|
-
# favour of the C `RGame::Util::Tensor`. The reason is only that there is no
|
|
12
|
-
# C `Util::Matrix` to swap to — `Util` holds `Tensor`, `Controls` and
|
|
13
|
-
# `Color`. If one is ever written, this is the caller to point at it.
|
|
14
|
-
class Matrix
|
|
15
|
-
attr_reader :width, :height
|
|
16
|
-
|
|
17
|
-
def initialize(width, height, initial: nil)
|
|
18
|
-
@width = width
|
|
19
|
-
@height = height
|
|
20
|
-
@data = Array.new(width * height, initial)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def [](x, y)
|
|
24
|
-
@data[(y * @width) + x]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def []=(x, y, value)
|
|
28
|
-
@data[(y * @width) + x] = value
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RGame
|
|
4
|
-
module Engine
|
|
5
|
-
# Builds value-object classes for pooling. Like `Data.define`, instances expose
|
|
6
|
-
# read-only accessors and carry their fields as a unit — but where a `Data` value
|
|
7
|
-
# is fully immutable (every change means a fresh allocation), these add exactly
|
|
8
|
-
# one mutation: `reset`, which overwrites all fields at once and returns self.
|
|
9
|
-
#
|
|
10
|
-
# That is the only mutability a pool needs: acquire a recycled instance and
|
|
11
|
-
# `reset` it, without exposing the per-field setters a `Struct` would. The methods
|
|
12
|
-
# are generated fixed-arity with direct ivar assignment (as Struct/Data do), so
|
|
13
|
-
# `reset` is allocation-free and recycling stays zero-allocation in steady state.
|
|
14
|
-
#
|
|
15
|
-
# Positional by default; pass `keyword_init: true` for keyword fields. Keywords
|
|
16
|
-
# are *also* allocation-free here because they are generated as named parameters
|
|
17
|
-
# (`reset(x:, y:)`), not a `**kwargs` splat — the splat is the one form that builds
|
|
18
|
-
# a Hash per call.
|
|
19
|
-
#
|
|
20
|
-
# Point = Engine::Resettable.define(:x, :y)
|
|
21
|
-
# p = Point.new(3, 4); p.x # => 3
|
|
22
|
-
# p.reset(5, 6) # same object, new values; returns p
|
|
23
|
-
#
|
|
24
|
-
# Event = Engine::Resettable.define(:type, :payload, keyword_init: true)
|
|
25
|
-
# e = Event.new(type: :hit, payload: 10)
|
|
26
|
-
# e.reset(type: :lose, payload: nil) # same object; returns e
|
|
27
|
-
# e.respond_to?(:type=) # => false (no setters)
|
|
28
|
-
#
|
|
29
|
-
# Field names are generated into real methods, so they must be plain identifiers.
|
|
30
|
-
module Resettable
|
|
31
|
-
IDENTIFIER = /\A[a-z_][a-zA-Z0-9_]*\z/
|
|
32
|
-
|
|
33
|
-
def self.define(*fields, keyword_init: false)
|
|
34
|
-
fields.each do |field|
|
|
35
|
-
raise ArgumentError, "invalid field name: #{field.inspect}" unless field.to_s.match?(IDENTIFIER)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
params = fields.map { |f| keyword_init ? "#{f}:" : f.to_s }.join(', ')
|
|
39
|
-
assign = fields.map { |f| "@#{f} = #{f}" }.join("\n")
|
|
40
|
-
|
|
41
|
-
Class.new do
|
|
42
|
-
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
43
|
-
# For fields [:x, :y] this generates (keyword_init makes `x, y` -> `x:, y:`):
|
|
44
|
-
# attr_reader :x, :y
|
|
45
|
-
# def initialize(x, y)
|
|
46
|
-
# @x = x; @y = y
|
|
47
|
-
# end
|
|
48
|
-
# def reset(x, y)
|
|
49
|
-
# @x = x; @y = y
|
|
50
|
-
# self
|
|
51
|
-
# end
|
|
52
|
-
attr_reader #{fields.map { |f| ":#{f}" }.join(', ')}
|
|
53
|
-
|
|
54
|
-
def initialize(#{params})
|
|
55
|
-
#{assign}
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def reset(#{params})
|
|
59
|
-
#{assign}
|
|
60
|
-
self
|
|
61
|
-
end
|
|
62
|
-
RUBY
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RGame
|
|
4
|
-
module Engine
|
|
5
|
-
# Axis-separated AABB-vs-tile collision resolution (pure). `solid` is a callable
|
|
6
|
-
# `solid.call(col, row) -> bool`. Resolve X then Y (with the X result) to get
|
|
7
|
-
# wall-sliding. Assumes per-step movement smaller than a tile (no tunneling),
|
|
8
|
-
# which holds for our speeds.
|
|
9
|
-
class TileCollision
|
|
10
|
-
EPS = 1e-9
|
|
11
|
-
|
|
12
|
-
def initialize(tile_width:, tile_height:, solid:)
|
|
13
|
-
@tile_width = tile_width
|
|
14
|
-
@tile_height = tile_height
|
|
15
|
-
@solid = solid
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Move an AABB (top-left x, y; size w, h) by dx, snapping flush against solids.
|
|
19
|
-
def resolve_x(x, y, w, h, dx)
|
|
20
|
-
nx = x + dx
|
|
21
|
-
return nx if dx.zero?
|
|
22
|
-
|
|
23
|
-
first_row = (y / @tile_height).floor
|
|
24
|
-
last_row = ((y + h - EPS) / @tile_height).floor
|
|
25
|
-
|
|
26
|
-
if dx.positive?
|
|
27
|
-
col = ((nx + w - EPS) / @tile_width).floor
|
|
28
|
-
return col * @tile_width - w if solid_in_rows?(col, first_row, last_row)
|
|
29
|
-
else
|
|
30
|
-
col = (nx / @tile_width).floor
|
|
31
|
-
return (col + 1) * @tile_width if solid_in_rows?(col, first_row, last_row)
|
|
32
|
-
end
|
|
33
|
-
nx
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def resolve_y(x, y, w, h, dy)
|
|
37
|
-
ny = y + dy
|
|
38
|
-
return ny if dy.zero?
|
|
39
|
-
|
|
40
|
-
first_col = (x / @tile_width).floor
|
|
41
|
-
last_col = ((x + w - EPS) / @tile_width).floor
|
|
42
|
-
|
|
43
|
-
if dy.positive?
|
|
44
|
-
row = ((ny + h - EPS) / @tile_height).floor
|
|
45
|
-
return row * @tile_height - h if solid_in_cols?(row, first_col, last_col)
|
|
46
|
-
else
|
|
47
|
-
row = (ny / @tile_height).floor
|
|
48
|
-
return (row + 1) * @tile_height if solid_in_cols?(row, first_col, last_col)
|
|
49
|
-
end
|
|
50
|
-
ny
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
private
|
|
54
|
-
|
|
55
|
-
# Plain integer loops rather than (a..b).any? { ... }: the Range literal would
|
|
56
|
-
# allocate on this per-frame collision path.
|
|
57
|
-
def solid_in_rows?(col, first_row, last_row)
|
|
58
|
-
row = first_row
|
|
59
|
-
while row <= last_row
|
|
60
|
-
return true if @solid.call(col, row)
|
|
61
|
-
|
|
62
|
-
row += 1
|
|
63
|
-
end
|
|
64
|
-
false
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def solid_in_cols?(row, first_col, last_col)
|
|
68
|
-
col = first_col
|
|
69
|
-
while col <= last_col
|
|
70
|
-
return true if @solid.call(col, row)
|
|
71
|
-
|
|
72
|
-
col += 1
|
|
73
|
-
end
|
|
74
|
-
false
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RGame
|
|
4
|
-
module Engine
|
|
5
|
-
module UI
|
|
6
|
-
# One entry in a Menu: a label on a nine-slice, and a signal for when it is
|
|
7
|
-
# chosen.
|
|
8
|
-
#
|
|
9
|
-
# resume = menu.add_item('Resume')
|
|
10
|
-
# resume.on_activated { cutscene.close }
|
|
11
|
-
#
|
|
12
|
-
# It draws itself from its **state** — focused, pressed, disabled or
|
|
13
|
-
# idle — which is why the shipped atlas has an element for each. There is
|
|
14
|
-
# no hover, because there is no pointer: what a mouse-driven control would
|
|
15
|
-
# get from the cursor being over it, this gets from the Menu telling it it
|
|
16
|
-
# is the focused one.
|
|
17
|
-
#
|
|
18
|
-
# Its position is its own, resolved through the tree like any node's, so a
|
|
19
|
-
# Menu inside a PlayerLayer puts its items inside that player's region
|
|
20
|
-
# without either of them arranging it.
|
|
21
|
-
class MenuItem < Node2D
|
|
22
|
-
signal :on_activated # emits no payload; the item is the handle
|
|
23
|
-
|
|
24
|
-
# Atlas element per state. Replaceable per menu, so a game with its own
|
|
25
|
-
# art is not obliged to name it the way the shipped atlas does.
|
|
26
|
-
STYLE = {
|
|
27
|
-
idle: :button_idle,
|
|
28
|
-
focus: :button_focus,
|
|
29
|
-
pressed: :button_pressed,
|
|
30
|
-
disabled: :button_disabled
|
|
31
|
-
}.freeze
|
|
32
|
-
|
|
33
|
-
LABEL_COLOR = [46, 34, 24].freeze
|
|
34
|
-
DISABLED_LABEL_COLOR = [120, 110, 100].freeze
|
|
35
|
-
|
|
36
|
-
attr_accessor :label, :enabled
|
|
37
|
-
attr_writer :focused, :pressed
|
|
38
|
-
|
|
39
|
-
def initialize(label:, style: STYLE, enabled: true, **)
|
|
40
|
-
super(**)
|
|
41
|
-
@label = label
|
|
42
|
-
@style = style
|
|
43
|
-
@enabled = enabled
|
|
44
|
-
@focused = false
|
|
45
|
-
@pressed = false
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def enabled? = @enabled
|
|
49
|
-
def focused? = @focused
|
|
50
|
-
|
|
51
|
-
# Fires the signal and returns the item, or nil if it is disabled — so a
|
|
52
|
-
# caller never has to check first, and a disabled item cannot be
|
|
53
|
-
# activated by any route.
|
|
54
|
-
def activate
|
|
55
|
-
return nil unless @enabled
|
|
56
|
-
|
|
57
|
-
on_activated_signal.emit
|
|
58
|
-
self
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# The panel and its label share this node's slot, so the only ordering
|
|
62
|
-
# question is which of the two goes on top — and `z: 1` says exactly
|
|
63
|
-
# that, about this item and nothing else in the frame.
|
|
64
|
-
def on_draw(renderer, _view)
|
|
65
|
-
renderer.nine_slice(@style.fetch(state), abs_x, abs_y, width, height)
|
|
66
|
-
renderer.text(@label, label_x(renderer), label_y(renderer),
|
|
67
|
-
z: 1, color: @enabled ? LABEL_COLOR : DISABLED_LABEL_COLOR)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def state
|
|
73
|
-
return :disabled unless @enabled
|
|
74
|
-
return :idle unless @focused
|
|
75
|
-
|
|
76
|
-
@pressed ? :pressed : :focus
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def label_x(renderer) = abs_x + ((width - renderer.text_width(@label)) / 2)
|
|
80
|
-
def label_y(renderer) = abs_y + ((height - renderer.text_height) / 2)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|