metro-ld25 0.3.3
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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +12 -0
- data/Guardfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +189 -0
- data/Rakefile +18 -0
- data/bin/metro +16 -0
- data/changelog.md +157 -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 +140 -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 +300 -0
- data/lib/metro/events/event_state_manager.rb +63 -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 +68 -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 +236 -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 +94 -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 +162 -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 +477 -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 +85 -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 +94 -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 +52 -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/tmxed_ext/tile_set.rb +34 -0
- data/metro.gemspec +56 -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/events/event_state_manager_spec.rb +5 -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/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
- metadata +374 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module Metro
|
|
2
|
+
module UI
|
|
3
|
+
|
|
4
|
+
class TileLayer < Model
|
|
5
|
+
property :rotation
|
|
6
|
+
|
|
7
|
+
attr_accessor :map
|
|
8
|
+
attr_accessor :layer
|
|
9
|
+
attr_accessor :tilesets
|
|
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 viewport=(port)
|
|
28
|
+
@viewport = port
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_reader :viewport
|
|
32
|
+
|
|
33
|
+
def row(position)
|
|
34
|
+
position / layer.width
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def column(position)
|
|
38
|
+
position % layer.width
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def tile_bounds
|
|
42
|
+
@tile_bounds ||= build_tiles_index
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_tiles_index
|
|
46
|
+
data.each_with_index.map do |image_index,position|
|
|
47
|
+
next if image_index == 0
|
|
48
|
+
image = tileset_image(image_index)
|
|
49
|
+
[ position_of_image(image,row(position),column(position)), image ]
|
|
50
|
+
end.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tiles_within_viewport
|
|
54
|
+
tile_bounds.find_all {|bounds,images| viewport.intersect?(bounds) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def draw
|
|
58
|
+
tiles_within_viewport.each { |bounds,image| image.draw_rot(bounds.left - x,bounds.top - y,z_order,rotation) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tileset_image(image_index)
|
|
62
|
+
unless cached_images[image_index]
|
|
63
|
+
tileset = map.tilesets.find {|t| image_index >= t.firstgid && image_index < t.firstgid + t.images.count }
|
|
64
|
+
tileset_image_index = image_index - tileset.firstgid
|
|
65
|
+
cached_images[image_index] = tileset.images[tileset_image_index]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
cached_images[image_index]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def cached_images
|
|
72
|
+
@cached_images ||= {}
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class TileMapOrthogonalLayer < TileLayer
|
|
77
|
+
def position_of_image(image,row,column)
|
|
78
|
+
pos_x = x + column * map.tilewidth + map.tilewidth / 2
|
|
79
|
+
pos_y = y + row * map.tileheight + map.tileheight / 2
|
|
80
|
+
Bounds.new left: pos_x, top: pos_y, right: pos_x + map.tilewidth, bottom: pos_y + map.tileheight/2
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class TileMapIsometricLayer < TileLayer
|
|
85
|
+
def half_tilewidth
|
|
86
|
+
@half_tilewidth ||= map.tilewidth/2
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def half_tileheight
|
|
90
|
+
@half_tileheight ||= map.tileheight/2
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def start_x
|
|
94
|
+
@start_x ||= x + map.tilewidth * map.width / 2
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def x_position(row,column)
|
|
98
|
+
row_start_x = start_x - half_tilewidth * row
|
|
99
|
+
row_start_x + half_tilewidth * column
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def start_y
|
|
103
|
+
y
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def y_position(row,column)
|
|
107
|
+
row_start_y = start_y + half_tileheight * row
|
|
108
|
+
row_start_y + half_tileheight * column
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def position_of_image(image,row,column)
|
|
112
|
+
pos_x = x_position(row,column) - (map.tilewidth - image.width)/2
|
|
113
|
+
pos_y = y_position(row,column) + (map.tileheight - image.height)/2
|
|
114
|
+
Bounds.new left: pos_x, top: pos_y, right: pos_x + map.tilewidth, bottom: pos_y + map.tileheight
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class TileMap < Model
|
|
120
|
+
|
|
121
|
+
property :position
|
|
122
|
+
property :file, type: :text
|
|
123
|
+
property :rotation
|
|
124
|
+
|
|
125
|
+
attr_accessor :layers
|
|
126
|
+
attr_accessor :viewport
|
|
127
|
+
|
|
128
|
+
def map
|
|
129
|
+
@map ||= begin
|
|
130
|
+
map = Tmxed.parse asset_path(file)
|
|
131
|
+
map.tilesets.each {|tileset| tileset.window = window }
|
|
132
|
+
map
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def layer_class
|
|
137
|
+
{ orthogonal: "Metro::UI::TileMapOrthogonalLayer",
|
|
138
|
+
isometric: "Metro::UI::TileMapIsometricLayer" }[map.orientation.to_sym].constantize
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def show
|
|
142
|
+
self.layers = map.layers.collect do |layer|
|
|
143
|
+
tml = layer_class.new
|
|
144
|
+
tml.rotation = rotation
|
|
145
|
+
tml.viewport = viewport
|
|
146
|
+
tml.map = map
|
|
147
|
+
tml.layer = layer
|
|
148
|
+
tml.tilesets = map.tilesets
|
|
149
|
+
tml
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def update
|
|
154
|
+
# update the layer with the current viewport position
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def draw
|
|
158
|
+
layers.each {|layer| layer.draw }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
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,477 @@
|
|
|
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.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
|
+
end
|
|
398
|
+
|
|
399
|
+
# This provides the functionality for view handling.
|
|
400
|
+
include SceneView
|
|
401
|
+
|
|
402
|
+
#
|
|
403
|
+
# `transition_to` performs the work of transitioning this scene
|
|
404
|
+
# to another scene.
|
|
405
|
+
#
|
|
406
|
+
# @param [String,Symbol,Object] scene_or_scene_name the name of the Scene which can
|
|
407
|
+
# be either the class or a string/symbol representation of the shortened scene name.
|
|
408
|
+
# This could also be an instance of scene.
|
|
409
|
+
#
|
|
410
|
+
def transition_to(scene_or_scene_name,options = {})
|
|
411
|
+
new_scene = Scenes.generate(scene_or_scene_name,options)
|
|
412
|
+
_prepare_transition(new_scene)
|
|
413
|
+
window.scene = new_scene
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
#
|
|
417
|
+
# Before a scene is transitioned away from to a new scene, this private method is
|
|
418
|
+
# here to allow for any housekeeping or other work that needs to be done before
|
|
419
|
+
# calling the subclasses implementation of `prepare_transition`.
|
|
420
|
+
#
|
|
421
|
+
# @param [Scene] new_scene this is the instance of the scene that is about to replace
|
|
422
|
+
# the current scene.
|
|
423
|
+
#
|
|
424
|
+
def _prepare_transition(new_scene)
|
|
425
|
+
log.debug "Preparing to transition from scene #{self} to #{new_scene}"
|
|
426
|
+
|
|
427
|
+
new_scene.class.actors.find_all {|actor_factory| actor_factory.load_from_previous_scene? }.each do |actor_factory|
|
|
428
|
+
new_actor = new_scene.actor(actor_factory.name)
|
|
429
|
+
current_actor = actor(actor_factory.name)
|
|
430
|
+
new_actor._load current_actor._save
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
prepare_transition_to(new_scene)
|
|
434
|
+
new_scene.prepare_transition_from(self)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
#
|
|
438
|
+
# Helper method that is used internally to setup the events for the specified target.
|
|
439
|
+
#
|
|
440
|
+
# @param [Object] target the intended target for the specified events. This object
|
|
441
|
+
# will have the appropriate methods and functionality to respond appropriately
|
|
442
|
+
# to the action blocks defined in the methods.
|
|
443
|
+
#
|
|
444
|
+
# @param [Array<EventFactory>] events an array of EventFactory objects that need to now
|
|
445
|
+
# be mapped to the specified target.
|
|
446
|
+
#
|
|
447
|
+
def register_events_for_target(target,events)
|
|
448
|
+
state.add_events_for_target(target,events)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
#
|
|
452
|
+
# The event state manager is configured through the {#events} method, which
|
|
453
|
+
# stores all the gamepad and keyboard events defined. By default a scene is
|
|
454
|
+
# placed in the default state and events that are added to this basic state.
|
|
455
|
+
#
|
|
456
|
+
# @see Events
|
|
457
|
+
#
|
|
458
|
+
def state
|
|
459
|
+
@event_state_manager ||= EventStateManager.new
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
#
|
|
463
|
+
# A Scene represented as a hash currently only contains the drawers
|
|
464
|
+
#
|
|
465
|
+
# @return a hash of all the drawers
|
|
466
|
+
#
|
|
467
|
+
def to_hash
|
|
468
|
+
drawn = drawers.find_all{|draw| draw.saveable_to_view }.inject({}) do |hash,drawer|
|
|
469
|
+
drawer_hash = drawer.to_hash
|
|
470
|
+
hash.merge drawer_hash
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
drawn
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
end
|
|
477
|
+
end
|