metro-ld26 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +14 -0
- data/Guardfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +177 -0
- data/Rakefile +18 -0
- data/bin/metro +16 -0
- data/changelog.md +153 -0
- data/lib/assets/menu-movement.wav +0 -0
- data/lib/assets/menu-selection.wav +0 -0
- data/lib/assets/missing.ogg +0 -0
- data/lib/assets/missing.png +0 -0
- data/lib/assets/missing.wav +0 -0
- data/lib/assets/missing_animation.png +0 -0
- data/lib/commands/generate_game.rb +13 -0
- data/lib/commands/generate_model.rb +25 -0
- data/lib/commands/generate_scene.rb +36 -0
- data/lib/commands/generate_view.rb +21 -0
- data/lib/commands/thor.rb +83 -0
- data/lib/core_ext/class.rb +14 -0
- data/lib/core_ext/numeric.rb +59 -0
- data/lib/gosu_ext/color.rb +62 -0
- data/lib/gosu_ext/gosu_constants.rb +53 -0
- data/lib/locale/en.yml +35 -0
- data/lib/locale/locale.rb +1 -0
- data/lib/metro.rb +144 -0
- data/lib/metro/animation.rb +135 -0
- data/lib/metro/animation/after_interval_factory.rb +12 -0
- data/lib/metro/animation/animation_factory.rb +15 -0
- data/lib/metro/animation/easing/ease_in.rb +15 -0
- data/lib/metro/animation/easing/easing.rb +51 -0
- data/lib/metro/animation/easing/linear.rb +15 -0
- data/lib/metro/animation/has_animations.rb +70 -0
- data/lib/metro/animation/implicit_animation.rb +100 -0
- data/lib/metro/animation/on_update_operation.rb +96 -0
- data/lib/metro/animation/scene_animation.rb +16 -0
- data/lib/metro/asset_path.rb +97 -0
- data/lib/metro/events/control_definition.rb +11 -0
- data/lib/metro/events/controls.rb +42 -0
- data/lib/metro/events/event_data.rb +60 -0
- data/lib/metro/events/event_dictionary.rb +52 -0
- data/lib/metro/events/event_factory.rb +17 -0
- data/lib/metro/events/event_relay.rb +342 -0
- data/lib/metro/events/event_state_manager.rb +70 -0
- data/lib/metro/events/events.rb +3 -0
- data/lib/metro/events/has_events.rb +108 -0
- data/lib/metro/events/hit_list.rb +75 -0
- data/lib/metro/events/unknown_sender.rb +5 -0
- data/lib/metro/font.rb +69 -0
- data/lib/metro/game.rb +102 -0
- data/lib/metro/game/dsl.rb +68 -0
- data/lib/metro/image.rb +75 -0
- data/lib/metro/logging.rb +33 -0
- data/lib/metro/missing_scene.rb +21 -0
- data/lib/metro/models/audio/song.rb +33 -0
- data/lib/metro/models/draws.rb +86 -0
- data/lib/metro/models/key_value_coding.rb +38 -0
- data/lib/metro/models/model.rb +246 -0
- data/lib/metro/models/model_factory.rb +32 -0
- data/lib/metro/models/models.rb +62 -0
- data/lib/metro/models/properties/animation_property.rb +115 -0
- data/lib/metro/models/properties/array_property.rb +24 -0
- data/lib/metro/models/properties/boolean_property.rb +27 -0
- data/lib/metro/models/properties/color_property.rb +116 -0
- data/lib/metro/models/properties/dimensions_property.rb +84 -0
- data/lib/metro/models/properties/font_property.rb +130 -0
- data/lib/metro/models/properties/image_property.rb +96 -0
- data/lib/metro/models/properties/model_property.rb +84 -0
- data/lib/metro/models/properties/numeric_property.rb +29 -0
- data/lib/metro/models/properties/options_property/no_option.rb +29 -0
- data/lib/metro/models/properties/options_property/options.rb +98 -0
- data/lib/metro/models/properties/options_property/options_property.rb +125 -0
- data/lib/metro/models/properties/position_property.rb +90 -0
- data/lib/metro/models/properties/property.rb +221 -0
- data/lib/metro/models/properties/property_owner.rb +137 -0
- data/lib/metro/models/properties/sample_property.rb +84 -0
- data/lib/metro/models/properties/scale_property.rb +80 -0
- data/lib/metro/models/properties/song_property.rb +89 -0
- data/lib/metro/models/properties/text_property.rb +75 -0
- data/lib/metro/models/ui/animated_sprite.rb +85 -0
- data/lib/metro/models/ui/border.rb +95 -0
- data/lib/metro/models/ui/fps.rb +54 -0
- data/lib/metro/models/ui/generic.rb +66 -0
- data/lib/metro/models/ui/grid_drawer.rb +74 -0
- data/lib/metro/models/ui/image.rb +87 -0
- data/lib/metro/models/ui/label.rb +175 -0
- data/lib/metro/models/ui/menu.rb +214 -0
- data/lib/metro/models/ui/model_label.rb +65 -0
- data/lib/metro/models/ui/model_labeler.rb +79 -0
- data/lib/metro/models/ui/rectangle.rb +59 -0
- data/lib/metro/models/ui/sprite.rb +79 -0
- data/lib/metro/models/ui/tile_map.rb +132 -0
- data/lib/metro/models/ui/tmx/isometric_position.rb +43 -0
- data/lib/metro/models/ui/tmx/orthogonal_position.rb +15 -0
- data/lib/metro/models/ui/tmx/tile_layer.rb +78 -0
- data/lib/metro/models/ui/ui.rb +13 -0
- data/lib/metro/parameters/command_line_args_parser.rb +68 -0
- data/lib/metro/parameters/options.rb +25 -0
- data/lib/metro/parameters/parameters.rb +2 -0
- data/lib/metro/sample.rb +40 -0
- data/lib/metro/scene.rb +478 -0
- data/lib/metro/scenes.rb +154 -0
- data/lib/metro/song.rb +56 -0
- data/lib/metro/template_message.rb +60 -0
- data/lib/metro/transitions/edit_transition_scene.rb +100 -0
- data/lib/metro/transitions/fade_transition_scene.rb +66 -0
- data/lib/metro/transitions/scene_transitions.rb +44 -0
- data/lib/metro/transitions/transition_scene.rb +19 -0
- data/lib/metro/units/bounds.rb +8 -0
- data/lib/metro/units/calculation_validations.rb +74 -0
- data/lib/metro/units/dimensions.rb +60 -0
- data/lib/metro/units/point.rb +51 -0
- data/lib/metro/units/rectangle_bounds.rb +148 -0
- data/lib/metro/units/scale.rb +46 -0
- data/lib/metro/units/units.rb +6 -0
- data/lib/metro/version.rb +32 -0
- data/lib/metro/views/json_view.rb +60 -0
- data/lib/metro/views/no_view.rb +34 -0
- data/lib/metro/views/parsers.rb +42 -0
- data/lib/metro/views/scene_view.rb +107 -0
- data/lib/metro/views/view.rb +133 -0
- data/lib/metro/views/writers.rb +43 -0
- data/lib/metro/views/yaml_view.rb +94 -0
- data/lib/metro/window.rb +95 -0
- data/lib/setup_handlers/exit_if_dry_run.rb +26 -0
- data/lib/setup_handlers/game_execution.rb +65 -0
- data/lib/setup_handlers/load_game_configuration.rb +65 -0
- data/lib/setup_handlers/load_game_files.rb +101 -0
- data/lib/setup_handlers/move_to_game_directory.rb +25 -0
- data/lib/setup_handlers/reload_game_on_game_file_changes.rb +79 -0
- data/lib/templates/game/README.md.tt +43 -0
- data/lib/templates/game/assets/brand.jpg +0 -0
- data/lib/templates/game/assets/hero.png +0 -0
- data/lib/templates/game/lib/custom_easing.rb +32 -0
- data/lib/templates/game/metro.tt +63 -0
- data/lib/templates/game/models/hero.rb +62 -0
- data/lib/templates/game/scenes/brand_scene.rb +19 -0
- data/lib/templates/game/scenes/brand_to_title_scene.rb +13 -0
- data/lib/templates/game/scenes/first_scene.rb +28 -0
- data/lib/templates/game/scenes/game_scene.rb +43 -0
- data/lib/templates/game/scenes/title_scene.rb +15 -0
- data/lib/templates/game/views/brand.yaml +4 -0
- data/lib/templates/game/views/brand_to_title.yaml +8 -0
- data/lib/templates/game/views/first.yaml +26 -0
- data/lib/templates/game/views/title.yaml +11 -0
- data/lib/templates/message.erb +23 -0
- data/lib/templates/model.rb.tt +111 -0
- data/lib/templates/scene.rb.tt +140 -0
- data/lib/templates/view.yaml.tt +11 -0
- data/lib/tmx_ext/object.rb +26 -0
- data/lib/tmx_ext/tile_set.rb +41 -0
- data/metro.gemspec +57 -0
- data/metro.png +0 -0
- data/spec/core_ext/numeric_spec.rb +78 -0
- data/spec/core_ext/string_spec.rb +33 -0
- data/spec/gosu_ext/color_spec.rb +80 -0
- data/spec/metro/image_spec.rb +33 -0
- data/spec/metro/models/key_value_coding_spec.rb +61 -0
- data/spec/metro/models/properties/array_property_spec.rb +60 -0
- data/spec/metro/models/properties/color_property_spec.rb +85 -0
- data/spec/metro/models/properties/dimensions_spec.rb +29 -0
- data/spec/metro/models/properties/font_property_spec.rb +127 -0
- data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
- data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
- data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
- data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
- data/spec/metro/models/properties/position_property_spec.rb +90 -0
- data/spec/metro/models/ui/label_spec.rb +259 -0
- data/spec/metro/parameters/command_line_args_parser_spec.rb +42 -0
- data/spec/metro/scene_spec.rb +15 -0
- data/spec/metro/scene_views/json_view_spec.rb +27 -0
- data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
- data/spec/metro/scenes_spec.rb +77 -0
- data/spec/metro/units/point_spec.rb +132 -0
- data/spec/metro/units/rectangle_bounds_spec.rb +56 -0
- data/spec/metro/views/view_spec.rb +53 -0
- data/spec/setup_handlers/exit_if_dry_run_spec.rb +27 -0
- data/spec/setup_handlers/reload_game_on_game_file_changes_spec.rb +68 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/tmx_ext/object_spec.rb +96 -0
- data/spec/tmx_ext/tile_set_spec.rb +24 -0
- metadata +379 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
module Metro
|
2
|
+
module UI
|
3
|
+
#
|
4
|
+
# Draws a TileMap within the scene.
|
5
|
+
#
|
6
|
+
# @example Creating a tile map with a Tiled TMX file
|
7
|
+
#
|
8
|
+
# class MainScene < GameScene
|
9
|
+
# draw :tile_map, model: "metro::ui::tile_map",
|
10
|
+
# file: "first_level.tmx"
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# The viewport, or camera, of the TileMap is static but
|
14
|
+
# can be set to follow a particular actor within the scene.
|
15
|
+
# The viewport will continue to move according to the position
|
16
|
+
# of the specified actor.
|
17
|
+
#
|
18
|
+
# @example Creating a tile map that will follow the hero
|
19
|
+
#
|
20
|
+
# class MainScene < GameScene
|
21
|
+
# draw :hero
|
22
|
+
# draw :tile_map, model: "metro::ui::tile_map",
|
23
|
+
# file: "first_level.tmx", follow: :hero
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
#
|
27
|
+
class TileMap < ::Metro::Model
|
28
|
+
|
29
|
+
# @attribute
|
30
|
+
# The Tiled File (*.tmx) that will be parsed and loaded for the tile map
|
31
|
+
property :file, type: :text
|
32
|
+
|
33
|
+
# @attribute
|
34
|
+
# The actor that the viewport of the scene will follow.
|
35
|
+
property :follow, type: :text
|
36
|
+
|
37
|
+
# @attribute
|
38
|
+
# The rotation of each tile within the scene. This is by default
|
39
|
+
# 0 and should likely remain 0.
|
40
|
+
property :rotation
|
41
|
+
|
42
|
+
#
|
43
|
+
# Return the viewport for the map. The default viewport will be
|
44
|
+
# the dimensions of the current Game.
|
45
|
+
#
|
46
|
+
# @return [RectangleBounds] the bounds of the current viewport
|
47
|
+
def viewport
|
48
|
+
@viewport ||= Game.bounds
|
49
|
+
end
|
50
|
+
|
51
|
+
# @attribute
|
52
|
+
# Set the viewport of the map, by default ths viewport will be
|
53
|
+
# the dimensions of the current Game, so this may not be
|
54
|
+
# necessary to set.
|
55
|
+
attr_writer :viewport
|
56
|
+
|
57
|
+
#
|
58
|
+
# @return [TMX::Map] the map object found in the specified TMX file.
|
59
|
+
#
|
60
|
+
def map
|
61
|
+
@map ||= begin
|
62
|
+
map = ::Tmx.load asset_path(file)
|
63
|
+
map.tilesets.each {|tileset| tileset.window = window }
|
64
|
+
map
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Find objects that match the specified type or by the specified
|
70
|
+
# parameters.
|
71
|
+
#
|
72
|
+
# @example Finding all the objects with the type :floor
|
73
|
+
#
|
74
|
+
# objects(:floor)
|
75
|
+
#
|
76
|
+
# @example Finding all the objects with the name 'tree'
|
77
|
+
#
|
78
|
+
# objects(name: 'tree')
|
79
|
+
def objects(params)
|
80
|
+
map.objects.find(params)
|
81
|
+
end
|
82
|
+
|
83
|
+
def update
|
84
|
+
shift_viewport_to_center_who_we_are_following
|
85
|
+
end
|
86
|
+
|
87
|
+
def draw
|
88
|
+
layers.each {|layer| layer.draw }
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def layers
|
94
|
+
@layers ||= map.layers.collect do |layer|
|
95
|
+
tml = TileLayer.new
|
96
|
+
tml.extend layer_positioning
|
97
|
+
tml.rotation = rotation
|
98
|
+
tml.viewport = viewport
|
99
|
+
tml.map = map
|
100
|
+
tml.layer = layer
|
101
|
+
tml.tilesets = map.tilesets
|
102
|
+
tml
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def layer_positioning
|
107
|
+
{ orthogonal: "Metro::Tmx::TileLayer::OrthogonalPositioning",
|
108
|
+
isometric: "Metro::Tmx::TileLayer::IsometricPositioning" }[map.orientation.to_sym].constantize
|
109
|
+
end
|
110
|
+
|
111
|
+
def following
|
112
|
+
scene.send(follow) if follow
|
113
|
+
end
|
114
|
+
|
115
|
+
def shift_viewport_to_center_who_we_are_following
|
116
|
+
return unless following
|
117
|
+
|
118
|
+
diff_x = (following.x - Game.center.x).to_i
|
119
|
+
|
120
|
+
if diff_x >= 1 or diff_x <= -1
|
121
|
+
viewport.shift(Point.at(diff_x,0))
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
require_relative 'tmx/tile_layer'
|
131
|
+
require_relative 'tmx/isometric_position'
|
132
|
+
require_relative 'tmx/orthogonal_position'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Metro
|
2
|
+
module Tmx
|
3
|
+
class TileLayer
|
4
|
+
|
5
|
+
module IsometricPositioning
|
6
|
+
|
7
|
+
def position_of_image(image,row,column)
|
8
|
+
pos_x = x_position(row,column) - (map.tilewidth - image.width)/2
|
9
|
+
pos_y = y_position(row,column) + (map.tileheight - image.height)/2
|
10
|
+
::Metro::Units::Bounds.new left: pos_x, top: pos_y, right: pos_x + map.tilewidth, bottom: pos_y + map.tileheight
|
11
|
+
end
|
12
|
+
|
13
|
+
def half_tilewidth
|
14
|
+
@half_tilewidth ||= map.tilewidth/2
|
15
|
+
end
|
16
|
+
|
17
|
+
def half_tileheight
|
18
|
+
@half_tileheight ||= map.tileheight/2
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_x
|
22
|
+
@start_x ||= x + map.tilewidth * map.width / 2
|
23
|
+
end
|
24
|
+
|
25
|
+
def x_position(row,column)
|
26
|
+
row_start_x = start_x - half_tilewidth * row
|
27
|
+
row_start_x + half_tilewidth * column
|
28
|
+
end
|
29
|
+
|
30
|
+
def start_y
|
31
|
+
y
|
32
|
+
end
|
33
|
+
|
34
|
+
def y_position(row,column)
|
35
|
+
row_start_y = start_y + half_tileheight * row
|
36
|
+
row_start_y + half_tileheight * column
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Metro
|
2
|
+
module Tmx
|
3
|
+
class TileLayer
|
4
|
+
|
5
|
+
module OrthogonalPositioning
|
6
|
+
def position_of_image(image,row,column)
|
7
|
+
pos_x = x + column * map.tilewidth + map.tilewidth / 2
|
8
|
+
pos_y = y + row * map.tileheight + map.tileheight / 2
|
9
|
+
::Metro::Units::RectangleBounds.new left: pos_x, top: pos_y, right: pos_x + map.tilewidth, bottom: pos_y + map.tileheight/2
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Metro
|
2
|
+
module UI
|
3
|
+
class TileLayer < ::Metro::Model
|
4
|
+
property :rotation
|
5
|
+
|
6
|
+
attr_accessor :map
|
7
|
+
attr_accessor :layer
|
8
|
+
attr_accessor :tilesets
|
9
|
+
attr_accessor :viewport
|
10
|
+
|
11
|
+
def data
|
12
|
+
layer.data
|
13
|
+
end
|
14
|
+
|
15
|
+
def x
|
16
|
+
viewport.left
|
17
|
+
end
|
18
|
+
|
19
|
+
def y
|
20
|
+
viewport.top
|
21
|
+
end
|
22
|
+
|
23
|
+
def z_order
|
24
|
+
-1
|
25
|
+
end
|
26
|
+
|
27
|
+
def draw
|
28
|
+
tiles_within_viewport.each do |bounds,image|
|
29
|
+
image.draw_rot(bounds.left - x,bounds.top - y,z_order,rotation)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def tiles_within_viewport
|
36
|
+
enlarged_viewport = viewport.enlarge(left: map.tilewidth, right: map.tilewidth, top: map.tileheight, bottom: map.tileheight)
|
37
|
+
tile_bounds.find_all {|bounds,images| enlarged_viewport.intersect?(bounds) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def tile_bounds
|
41
|
+
@tile_bounds ||= build_tiles_index
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_tiles_index
|
45
|
+
data.each_with_index.map do |image_index,position|
|
46
|
+
next if image_index == 0
|
47
|
+
image = tileset_image(image_index)
|
48
|
+
[ position_of_image(image,row(position),column(position)), image ]
|
49
|
+
end.compact
|
50
|
+
end
|
51
|
+
|
52
|
+
def tileset_image(image_index)
|
53
|
+
unless cached_images[image_index]
|
54
|
+
tileset = map.tilesets.find do |t|
|
55
|
+
image_index >= t.firstgid && image_index < t.firstgid + t.images.count
|
56
|
+
end
|
57
|
+
tileset_image_index = image_index - tileset.firstgid
|
58
|
+
cached_images[image_index] = tileset.images[tileset_image_index]
|
59
|
+
end
|
60
|
+
|
61
|
+
cached_images[image_index]
|
62
|
+
end
|
63
|
+
|
64
|
+
def row(position)
|
65
|
+
position / layer.width
|
66
|
+
end
|
67
|
+
|
68
|
+
def column(position)
|
69
|
+
position % layer.width
|
70
|
+
end
|
71
|
+
|
72
|
+
def cached_images
|
73
|
+
@cached_images ||= {}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'generic'
|
2
|
+
require_relative 'label'
|
3
|
+
require_relative 'menu'
|
4
|
+
require_relative 'image'
|
5
|
+
require_relative 'rectangle'
|
6
|
+
require_relative 'grid_drawer'
|
7
|
+
require_relative 'border'
|
8
|
+
require_relative 'model_label'
|
9
|
+
require_relative 'model_labeler'
|
10
|
+
require_relative 'fps'
|
11
|
+
require_relative 'sprite'
|
12
|
+
require_relative 'animated_sprite'
|
13
|
+
require_relative 'tile_map'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Metro
|
2
|
+
module Parameters
|
3
|
+
|
4
|
+
#
|
5
|
+
# The CommandLineArgsParser converts the argument list passed in from the
|
6
|
+
# command-line and generates an Metro::Parameters::Options object.
|
7
|
+
#
|
8
|
+
module CommandLineArgsParser
|
9
|
+
extend self
|
10
|
+
|
11
|
+
#
|
12
|
+
# Given the array of parameters usually from the Command-Line ARGV and
|
13
|
+
# convert that information into various parameters
|
14
|
+
#
|
15
|
+
def parse(*parameters)
|
16
|
+
parameters = parameters.flatten.compact
|
17
|
+
options = { execution_parameters: parameters.dup }
|
18
|
+
|
19
|
+
command_flags = extract_command_flags!(parameters)
|
20
|
+
filename = extract_game_file!(parameters)
|
21
|
+
|
22
|
+
Options.new options.merge(filename: filename).merge(command_flags)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
#
|
28
|
+
# Find all the flags within the array of parameters, extract them and
|
29
|
+
# generate a hash. Their presence within the array means that they should
|
30
|
+
# have a true value.
|
31
|
+
#
|
32
|
+
# @return [Hash] a hash that contains all the flags as keys and true as
|
33
|
+
# their values. As the presence of the flag means the value is true.
|
34
|
+
#
|
35
|
+
def extract_command_flags!(parameters)
|
36
|
+
raw_command_flags = parameters.flatten.find_all { |arg| arg.start_with? "--" }
|
37
|
+
parameters.delete_if { |param| raw_command_flags.include? param }
|
38
|
+
|
39
|
+
flag_names = raw_command_flags.map { |flag| flag[/--(.+)$/,1].underscore.to_sym }
|
40
|
+
flag_values = [ true ] * flag_names.count
|
41
|
+
Hash[flag_names.zip(flag_values)]
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# From the current parameters array remove the first element which should
|
46
|
+
# be the default game file. When there is no value then use the default
|
47
|
+
# game filename
|
48
|
+
#
|
49
|
+
# @return [String] the game file name to use
|
50
|
+
#
|
51
|
+
def extract_game_file!(parameters)
|
52
|
+
parameters.delete_at(0) || default_game_filename
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# The default for games is to have a game file called 'metro'. So if they
|
57
|
+
# do not provide the file parameter we assume that it is this file.
|
58
|
+
#
|
59
|
+
# @return [String] the default filename that contains the game contents
|
60
|
+
#
|
61
|
+
def default_game_filename
|
62
|
+
'metro'
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Metro
|
2
|
+
module Parameters
|
3
|
+
|
4
|
+
#
|
5
|
+
# Options are the result of a parameters parser. The options class defines
|
6
|
+
# a read-only structure that provides getters for all the parameters
|
7
|
+
# specified within the has.
|
8
|
+
#
|
9
|
+
# @see CommandLineArgsParser
|
10
|
+
#
|
11
|
+
class Options
|
12
|
+
def initialize(params = {})
|
13
|
+
params.each do |key,value|
|
14
|
+
self.class.send(:define_method,key) { value }
|
15
|
+
self.class.send(:define_method,"#{key}?") { value }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(name,*args,&block)
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/metro/sample.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Metro
|
2
|
+
|
3
|
+
#
|
4
|
+
# Sample is a wrapper class for a Gosu Sample. This allows for additional data to be stored
|
5
|
+
# without relying on monkey-patching on functionality.
|
6
|
+
#
|
7
|
+
class Sample < SimpleDelegator
|
8
|
+
|
9
|
+
attr_accessor :sample, :path
|
10
|
+
|
11
|
+
def initialize(sample,path)
|
12
|
+
super(sample)
|
13
|
+
@sample = sample
|
14
|
+
@path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Create a sample given the window and path.
|
19
|
+
#
|
20
|
+
# @example Creating a Sample
|
21
|
+
#
|
22
|
+
# Metro::Sample.create window: model.window, path: "sample_path.wav"
|
23
|
+
#
|
24
|
+
def self.create(options)
|
25
|
+
window, asset_path = create_params(options)
|
26
|
+
gosu_sample = Gosu::Sample.new(window,asset_path.filepath)
|
27
|
+
new gosu_sample, asset_path.path
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.create_params(options)
|
33
|
+
options.symbolize_keys!
|
34
|
+
asset_path = AssetPath.with(options[:path])
|
35
|
+
window = options[:window]
|
36
|
+
[ window, asset_path ]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/metro/scene.rb
ADDED
@@ -0,0 +1,478 @@
|
|
1
|
+
require_relative 'views/scene_view'
|
2
|
+
require_relative 'events/events'
|
3
|
+
require_relative 'models/draws'
|
4
|
+
|
5
|
+
require_relative 'animation/has_animations'
|
6
|
+
require_relative 'animation/scene_animation'
|
7
|
+
require_relative 'animation/after_interval_factory'
|
8
|
+
|
9
|
+
module Metro
|
10
|
+
|
11
|
+
#
|
12
|
+
# A scene is a basic unit of a game. Within a scene you define a number of methods
|
13
|
+
# that handle the initial setup, event configuration, logic updating, and drawing.
|
14
|
+
#
|
15
|
+
# @see #show
|
16
|
+
# @see #update
|
17
|
+
# @see #draw
|
18
|
+
#
|
19
|
+
# A fair number of private methods within Scene are prefaced with an underscore.
|
20
|
+
# These methods often call non-underscored methods within those methods. This allows
|
21
|
+
# for scene to configure or perform some functionality, while providing an interface
|
22
|
+
# so that every subclass does not have to constantly call `super`.
|
23
|
+
#
|
24
|
+
class Scene
|
25
|
+
include Units
|
26
|
+
|
27
|
+
#
|
28
|
+
# As Scene does a lot of work for you with regarding to setting up content, it is
|
29
|
+
# best not to override #initialize and instead define an #after_initialize method
|
30
|
+
# within the subclasses of Scene.
|
31
|
+
#
|
32
|
+
# @note This method should be implemented in the Scene subclass.
|
33
|
+
#
|
34
|
+
def after_initialize ; end
|
35
|
+
|
36
|
+
#
|
37
|
+
# This method is called right after the scene has been adopted by the window
|
38
|
+
#
|
39
|
+
# @note This method should be implemented in the Scene subclass.
|
40
|
+
#
|
41
|
+
def show ; end
|
42
|
+
|
43
|
+
#
|
44
|
+
# This is called every update interval while the window is being shown.
|
45
|
+
#
|
46
|
+
# @note This method should be implemented in the Scene subclass.
|
47
|
+
#
|
48
|
+
def update ; end
|
49
|
+
|
50
|
+
#
|
51
|
+
# This is called after every {#update} and when the OS wants the window to
|
52
|
+
# repaint itself.
|
53
|
+
#
|
54
|
+
# @note This method should be implemented in the Scene subclass.
|
55
|
+
#
|
56
|
+
def draw ; end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Before a scene is transitioned away from to a new scene, this method is called
|
60
|
+
# to allow for the scene to complete any tasks, stop any actions, or pass any
|
61
|
+
# information from the existing scene to the scene that is about to replace it.
|
62
|
+
#
|
63
|
+
# @note This method should be implemented in the Scene subclass.
|
64
|
+
#
|
65
|
+
# @param [Scene] new_scene this is the instance of the scene that is about to replace
|
66
|
+
# the current scene.
|
67
|
+
#
|
68
|
+
def prepare_transition_to(new_scene) ; end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Before a scene is transitioned to it is called with the previous scene. This
|
72
|
+
# allows for the new scene to retrieve any data from the previous scene to assist
|
73
|
+
# with the layout of the current scene.
|
74
|
+
#
|
75
|
+
# @note This method should be implemented in the Scene subclass.
|
76
|
+
#
|
77
|
+
# @param [Scene] old_scene this is the instance of the scene that is being moved
|
78
|
+
# away from.
|
79
|
+
#
|
80
|
+
def prepare_transition_from(old_scene) ; end
|
81
|
+
|
82
|
+
include Draws
|
83
|
+
|
84
|
+
#
|
85
|
+
# When an actor is defined, through the class method `draw` a getter and setter method
|
86
|
+
# is defined. However, it is a better interface internally not to rely heavily on send
|
87
|
+
# and have this small amount of obfuscation in the event that this needs to change.
|
88
|
+
#
|
89
|
+
# @return the actor with the given name.
|
90
|
+
#
|
91
|
+
def actor(actor_or_actor_name)
|
92
|
+
if actor_or_actor_name.is_a? String or actor_or_actor_name.is_a? Symbol
|
93
|
+
send(actor_or_actor_name)
|
94
|
+
else
|
95
|
+
actor_or_actor_name
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Post a custom notification event. This will trigger an event for all the
|
101
|
+
# objects that are registered for notification with the current state.
|
102
|
+
#
|
103
|
+
def notification(event,sender=nil)
|
104
|
+
sender = sender || UnknownSender
|
105
|
+
state.fire_events_for_notification(event,sender)
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# A scene has events which it will register when the window is established.
|
110
|
+
#
|
111
|
+
include HasEvents
|
112
|
+
|
113
|
+
#
|
114
|
+
# A scene defines animations which it will execute when the scene starts
|
115
|
+
#
|
116
|
+
include HasAnimations
|
117
|
+
|
118
|
+
#
|
119
|
+
# Allow the definition of a updater that will be executed when the scene starts.
|
120
|
+
#
|
121
|
+
# @example Setting up an event to 2 seconds after the scene has started.
|
122
|
+
#
|
123
|
+
# class ExampleScene
|
124
|
+
#
|
125
|
+
# draws :title
|
126
|
+
#
|
127
|
+
# after 2.seconds do
|
128
|
+
# transition_to :next_scene
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
def self.after(ticks,&block)
|
133
|
+
after_intervals.push AfterIntervalFactory.new ticks, &block
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Perform an operation after the specified interval.
|
138
|
+
#
|
139
|
+
# class ExampleScene
|
140
|
+
#
|
141
|
+
# draws :player
|
142
|
+
#
|
143
|
+
# def update
|
144
|
+
# if player.is_dead?
|
145
|
+
# after 2.seconds do
|
146
|
+
# transition_to :game_over
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
def after(ticks,&block)
|
154
|
+
tick = OnUpdateOperation.new interval: ticks, context: self
|
155
|
+
tick.on_complete(&block)
|
156
|
+
enqueue tick
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Setups up the Actors for the Scene based on the ModelFactories that have been
|
161
|
+
# defined.
|
162
|
+
#
|
163
|
+
# @note this method should not be overriden, otherwise the actors will perish!
|
164
|
+
# @see #after_initialize
|
165
|
+
#
|
166
|
+
def initialize
|
167
|
+
add_actors_to_scene
|
168
|
+
after_initialize
|
169
|
+
end
|
170
|
+
|
171
|
+
def add_actors_to_scene
|
172
|
+
self.class.actors.each do |scene_actor|
|
173
|
+
actor_instance = scene_actor.create
|
174
|
+
actor_instance.scene = self
|
175
|
+
send "#{scene_actor.name}=", actor_instance
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# The window is the main instance of the game. Using window can access a lot of
|
181
|
+
# underlying Metro::Window, a subclass of Gosu::Window, that the Scene class is
|
182
|
+
# obfuscating.
|
183
|
+
#
|
184
|
+
# @see Metro::Window
|
185
|
+
# @see Gosu::Window
|
186
|
+
#
|
187
|
+
attr_reader :window
|
188
|
+
|
189
|
+
#
|
190
|
+
# Setting the window places the scene within in the specified window. Which
|
191
|
+
# will cause a number of variables and settings to be set up. The {#show}
|
192
|
+
# method is called after the window has been set.
|
193
|
+
#
|
194
|
+
def window=(window)
|
195
|
+
@window = window
|
196
|
+
|
197
|
+
state.window = window
|
198
|
+
state.clear
|
199
|
+
|
200
|
+
register_events!
|
201
|
+
register_actors!
|
202
|
+
register_animations!
|
203
|
+
register_after_intervals!
|
204
|
+
|
205
|
+
show
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# Register all the events that were defined for this scene.
|
210
|
+
#
|
211
|
+
def register_events!
|
212
|
+
register_events_for_target(self,self.class.events)
|
213
|
+
end
|
214
|
+
|
215
|
+
#
|
216
|
+
# Register all the actors that were defined for this scene.
|
217
|
+
#
|
218
|
+
def register_actors!
|
219
|
+
self.class.actors.each { |actor| register_actor(actor) }
|
220
|
+
end
|
221
|
+
|
222
|
+
#
|
223
|
+
# Register all the animations that were defined for this scene.
|
224
|
+
#
|
225
|
+
def register_animations!
|
226
|
+
self.class.animations.each do |animation|
|
227
|
+
animate animation.actor, animation.options, &animation.on_complete_block
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def register_after_intervals!
|
232
|
+
self.class.after_intervals.each do |after_interval|
|
233
|
+
after after_interval.ticks, &after_interval.block
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
#
|
238
|
+
# Registering an actor involves setting up the actor within
|
239
|
+
# the window, adding them to the list of things that need to be
|
240
|
+
# drawn and then registering any eventst that they might have.
|
241
|
+
#
|
242
|
+
def register_actor(actor_factory)
|
243
|
+
registering_actor = actor(actor_factory.name)
|
244
|
+
registering_actor.window = window
|
245
|
+
registering_actor.show
|
246
|
+
|
247
|
+
drawers.push(registering_actor)
|
248
|
+
updaters.push(registering_actor)
|
249
|
+
|
250
|
+
register_events_for_target(registering_actor,registering_actor.class.events)
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# Allows you to set or retrieve the scene name for the Scene.
|
255
|
+
#
|
256
|
+
# @example Retrieving the default scene name
|
257
|
+
#
|
258
|
+
# class ExampleScene < GameScene
|
259
|
+
# end
|
260
|
+
#
|
261
|
+
# ExampleScene.scene_name # => "example"
|
262
|
+
#
|
263
|
+
# @example Setting a custom name for the Scene
|
264
|
+
#
|
265
|
+
# class RollingCreditsScene < GameScene
|
266
|
+
# scene_name "credits"
|
267
|
+
# end
|
268
|
+
#
|
269
|
+
# RollingCreditsScene.scene_name # => "credits"
|
270
|
+
#
|
271
|
+
# @param [String] scene_name when specified it will set the scene name for the class
|
272
|
+
# to the value specified.
|
273
|
+
#
|
274
|
+
# @return the String name of the scene which it can be used as a reference for transitioning
|
275
|
+
# or for generating the appropriate view information.
|
276
|
+
#
|
277
|
+
def self.scene_name(scene_name=nil)
|
278
|
+
@scene_name ||= begin
|
279
|
+
if to_s == "Metro::Scene"
|
280
|
+
to_s.underscore
|
281
|
+
else
|
282
|
+
to_s.gsub(/_?Scene$/i,'').underscore
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
scene_name ? @scene_name = scene_name.to_s : @scene_name
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# @return a common name that can be used through the system as a common identifier.
|
291
|
+
#
|
292
|
+
def self.metro_name
|
293
|
+
scene_name
|
294
|
+
end
|
295
|
+
|
296
|
+
#
|
297
|
+
# @return an array of all the scene names of all the ancestor scenes
|
298
|
+
#
|
299
|
+
def self.hierarchy
|
300
|
+
ancestors.find_all {|a| a.respond_to? :metro_name }.map(&:metro_name)
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# Allows you to set or retrieve the scene name for the Scene.
|
305
|
+
#
|
306
|
+
# @example Retrieving the default scene name
|
307
|
+
#
|
308
|
+
# class ExampleScene
|
309
|
+
# def show
|
310
|
+
# puts "Showing Scene: #{self.scene_name}" # => Showing Scene: example
|
311
|
+
# end
|
312
|
+
# end
|
313
|
+
#
|
314
|
+
# @return the string name of the Scene.
|
315
|
+
#
|
316
|
+
def scene_name
|
317
|
+
self.class.scene_name
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
# @return the string representation of a scene, this is used for debugging.
|
322
|
+
#
|
323
|
+
def to_s
|
324
|
+
"[SCENE: #{self.class.scene_name}(#{self.class})]"
|
325
|
+
end
|
326
|
+
|
327
|
+
#
|
328
|
+
# Captures all classes that subclass Scene.
|
329
|
+
#
|
330
|
+
def self.inherited(base)
|
331
|
+
scenes << base.to_s
|
332
|
+
Scenes.add(base)
|
333
|
+
end
|
334
|
+
|
335
|
+
#
|
336
|
+
# All subclasses of Scene, this should be all the defined scenes within the game.
|
337
|
+
#
|
338
|
+
# @return an Array of Scene subclasses
|
339
|
+
#
|
340
|
+
def self.scenes
|
341
|
+
@scenes ||= []
|
342
|
+
end
|
343
|
+
|
344
|
+
#
|
345
|
+
# Enqueue will add an updater to the list of updaters that are run initially when
|
346
|
+
# update is called. An updater is any object that can respond to #update. This
|
347
|
+
# is used for animations.
|
348
|
+
#
|
349
|
+
def enqueue(updater)
|
350
|
+
updaters.push(updater)
|
351
|
+
end
|
352
|
+
|
353
|
+
#
|
354
|
+
# The class defined updaters which will be converted to instance updaters when the scene
|
355
|
+
# has started.
|
356
|
+
#
|
357
|
+
def self.after_intervals
|
358
|
+
@after_intervals ||= []
|
359
|
+
end
|
360
|
+
|
361
|
+
#
|
362
|
+
# The objects that need to be executed on every update. These objects are traditionally
|
363
|
+
# animations or window events for held pressed buttons. But can be any objects that responds
|
364
|
+
# to the method #update.
|
365
|
+
#
|
366
|
+
def updaters
|
367
|
+
@updaters ||= []
|
368
|
+
end
|
369
|
+
|
370
|
+
#
|
371
|
+
# The `base_update` method is called by the Game Window. This is to allow for any
|
372
|
+
# special update needs to be handled before calling the traditional `update` method
|
373
|
+
# defined in the subclassed Scene.
|
374
|
+
#
|
375
|
+
def base_update
|
376
|
+
updaters.each { |updater| updater.update }
|
377
|
+
update
|
378
|
+
updaters.reject! { |updater| updater.update_completed? }
|
379
|
+
end
|
380
|
+
|
381
|
+
#
|
382
|
+
# The objects that need to be drawn with every draw cycle. These objects are traditionally
|
383
|
+
# the model objects, like the actors defined within the scene.
|
384
|
+
#
|
385
|
+
def drawers
|
386
|
+
@drawers ||= []
|
387
|
+
end
|
388
|
+
|
389
|
+
#
|
390
|
+
# The `base_draw` method is called by the Game Window. This is to allow for any
|
391
|
+
# special drawing needs to be handled before calling the traditional `draw` method
|
392
|
+
# defined in the subclassed Scene.
|
393
|
+
#
|
394
|
+
def base_draw
|
395
|
+
drawers.each { |drawer| drawer.draw }
|
396
|
+
draw
|
397
|
+
drawers.reject! { |drawer| drawer.draw_completed? }
|
398
|
+
end
|
399
|
+
|
400
|
+
# This provides the functionality for view handling.
|
401
|
+
include SceneView
|
402
|
+
|
403
|
+
#
|
404
|
+
# `transition_to` performs the work of transitioning this scene
|
405
|
+
# to another scene.
|
406
|
+
#
|
407
|
+
# @param [String,Symbol,Object] scene_or_scene_name the name of the Scene which can
|
408
|
+
# be either the class or a string/symbol representation of the shortened scene name.
|
409
|
+
# This could also be an instance of scene.
|
410
|
+
#
|
411
|
+
def transition_to(scene_or_scene_name,options = {})
|
412
|
+
new_scene = Scenes.generate(scene_or_scene_name,options)
|
413
|
+
_prepare_transition(new_scene)
|
414
|
+
window.scene = new_scene
|
415
|
+
end
|
416
|
+
|
417
|
+
#
|
418
|
+
# Before a scene is transitioned away from to a new scene, this private method is
|
419
|
+
# here to allow for any housekeeping or other work that needs to be done before
|
420
|
+
# calling the subclasses implementation of `prepare_transition`.
|
421
|
+
#
|
422
|
+
# @param [Scene] new_scene this is the instance of the scene that is about to replace
|
423
|
+
# the current scene.
|
424
|
+
#
|
425
|
+
def _prepare_transition(new_scene)
|
426
|
+
log.debug "Preparing to transition from scene #{self} to #{new_scene}"
|
427
|
+
|
428
|
+
new_scene.class.actors.find_all {|actor_factory| actor_factory.load_from_previous_scene? }.each do |actor_factory|
|
429
|
+
new_actor = new_scene.actor(actor_factory.name)
|
430
|
+
current_actor = actor(actor_factory.name)
|
431
|
+
new_actor._load current_actor._save
|
432
|
+
end
|
433
|
+
|
434
|
+
prepare_transition_to(new_scene)
|
435
|
+
new_scene.prepare_transition_from(self)
|
436
|
+
end
|
437
|
+
|
438
|
+
#
|
439
|
+
# Helper method that is used internally to setup the events for the specified target.
|
440
|
+
#
|
441
|
+
# @param [Object] target the intended target for the specified events. This object
|
442
|
+
# will have the appropriate methods and functionality to respond appropriately
|
443
|
+
# to the action blocks defined in the methods.
|
444
|
+
#
|
445
|
+
# @param [Array<EventFactory>] events an array of EventFactory objects that need to now
|
446
|
+
# be mapped to the specified target.
|
447
|
+
#
|
448
|
+
def register_events_for_target(target,events)
|
449
|
+
state.add_events_for_target(target,events)
|
450
|
+
end
|
451
|
+
|
452
|
+
#
|
453
|
+
# The event state manager is configured through the {#events} method, which
|
454
|
+
# stores all the gamepad and keyboard events defined. By default a scene is
|
455
|
+
# placed in the default state and events that are added to this basic state.
|
456
|
+
#
|
457
|
+
# @see Events
|
458
|
+
#
|
459
|
+
def state
|
460
|
+
@event_state_manager ||= EventStateManager.new
|
461
|
+
end
|
462
|
+
|
463
|
+
#
|
464
|
+
# A Scene represented as a hash currently only contains the drawers
|
465
|
+
#
|
466
|
+
# @return a hash of all the drawers
|
467
|
+
#
|
468
|
+
def to_hash
|
469
|
+
drawn = drawers.find_all{|draw| draw.saveable_to_view }.inject({}) do |hash,drawer|
|
470
|
+
drawer_hash = drawer.to_hash
|
471
|
+
hash.merge drawer_hash
|
472
|
+
end
|
473
|
+
|
474
|
+
drawn
|
475
|
+
end
|
476
|
+
|
477
|
+
end
|
478
|
+
end
|