metro-ld26 0.3.4

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.
Files changed (185) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +14 -0
  6. data/Guardfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +177 -0
  9. data/Rakefile +18 -0
  10. data/bin/metro +16 -0
  11. data/changelog.md +153 -0
  12. data/lib/assets/menu-movement.wav +0 -0
  13. data/lib/assets/menu-selection.wav +0 -0
  14. data/lib/assets/missing.ogg +0 -0
  15. data/lib/assets/missing.png +0 -0
  16. data/lib/assets/missing.wav +0 -0
  17. data/lib/assets/missing_animation.png +0 -0
  18. data/lib/commands/generate_game.rb +13 -0
  19. data/lib/commands/generate_model.rb +25 -0
  20. data/lib/commands/generate_scene.rb +36 -0
  21. data/lib/commands/generate_view.rb +21 -0
  22. data/lib/commands/thor.rb +83 -0
  23. data/lib/core_ext/class.rb +14 -0
  24. data/lib/core_ext/numeric.rb +59 -0
  25. data/lib/gosu_ext/color.rb +62 -0
  26. data/lib/gosu_ext/gosu_constants.rb +53 -0
  27. data/lib/locale/en.yml +35 -0
  28. data/lib/locale/locale.rb +1 -0
  29. data/lib/metro.rb +144 -0
  30. data/lib/metro/animation.rb +135 -0
  31. data/lib/metro/animation/after_interval_factory.rb +12 -0
  32. data/lib/metro/animation/animation_factory.rb +15 -0
  33. data/lib/metro/animation/easing/ease_in.rb +15 -0
  34. data/lib/metro/animation/easing/easing.rb +51 -0
  35. data/lib/metro/animation/easing/linear.rb +15 -0
  36. data/lib/metro/animation/has_animations.rb +70 -0
  37. data/lib/metro/animation/implicit_animation.rb +100 -0
  38. data/lib/metro/animation/on_update_operation.rb +96 -0
  39. data/lib/metro/animation/scene_animation.rb +16 -0
  40. data/lib/metro/asset_path.rb +97 -0
  41. data/lib/metro/events/control_definition.rb +11 -0
  42. data/lib/metro/events/controls.rb +42 -0
  43. data/lib/metro/events/event_data.rb +60 -0
  44. data/lib/metro/events/event_dictionary.rb +52 -0
  45. data/lib/metro/events/event_factory.rb +17 -0
  46. data/lib/metro/events/event_relay.rb +342 -0
  47. data/lib/metro/events/event_state_manager.rb +70 -0
  48. data/lib/metro/events/events.rb +3 -0
  49. data/lib/metro/events/has_events.rb +108 -0
  50. data/lib/metro/events/hit_list.rb +75 -0
  51. data/lib/metro/events/unknown_sender.rb +5 -0
  52. data/lib/metro/font.rb +69 -0
  53. data/lib/metro/game.rb +102 -0
  54. data/lib/metro/game/dsl.rb +68 -0
  55. data/lib/metro/image.rb +75 -0
  56. data/lib/metro/logging.rb +33 -0
  57. data/lib/metro/missing_scene.rb +21 -0
  58. data/lib/metro/models/audio/song.rb +33 -0
  59. data/lib/metro/models/draws.rb +86 -0
  60. data/lib/metro/models/key_value_coding.rb +38 -0
  61. data/lib/metro/models/model.rb +246 -0
  62. data/lib/metro/models/model_factory.rb +32 -0
  63. data/lib/metro/models/models.rb +62 -0
  64. data/lib/metro/models/properties/animation_property.rb +115 -0
  65. data/lib/metro/models/properties/array_property.rb +24 -0
  66. data/lib/metro/models/properties/boolean_property.rb +27 -0
  67. data/lib/metro/models/properties/color_property.rb +116 -0
  68. data/lib/metro/models/properties/dimensions_property.rb +84 -0
  69. data/lib/metro/models/properties/font_property.rb +130 -0
  70. data/lib/metro/models/properties/image_property.rb +96 -0
  71. data/lib/metro/models/properties/model_property.rb +84 -0
  72. data/lib/metro/models/properties/numeric_property.rb +29 -0
  73. data/lib/metro/models/properties/options_property/no_option.rb +29 -0
  74. data/lib/metro/models/properties/options_property/options.rb +98 -0
  75. data/lib/metro/models/properties/options_property/options_property.rb +125 -0
  76. data/lib/metro/models/properties/position_property.rb +90 -0
  77. data/lib/metro/models/properties/property.rb +221 -0
  78. data/lib/metro/models/properties/property_owner.rb +137 -0
  79. data/lib/metro/models/properties/sample_property.rb +84 -0
  80. data/lib/metro/models/properties/scale_property.rb +80 -0
  81. data/lib/metro/models/properties/song_property.rb +89 -0
  82. data/lib/metro/models/properties/text_property.rb +75 -0
  83. data/lib/metro/models/ui/animated_sprite.rb +85 -0
  84. data/lib/metro/models/ui/border.rb +95 -0
  85. data/lib/metro/models/ui/fps.rb +54 -0
  86. data/lib/metro/models/ui/generic.rb +66 -0
  87. data/lib/metro/models/ui/grid_drawer.rb +74 -0
  88. data/lib/metro/models/ui/image.rb +87 -0
  89. data/lib/metro/models/ui/label.rb +175 -0
  90. data/lib/metro/models/ui/menu.rb +214 -0
  91. data/lib/metro/models/ui/model_label.rb +65 -0
  92. data/lib/metro/models/ui/model_labeler.rb +79 -0
  93. data/lib/metro/models/ui/rectangle.rb +59 -0
  94. data/lib/metro/models/ui/sprite.rb +79 -0
  95. data/lib/metro/models/ui/tile_map.rb +132 -0
  96. data/lib/metro/models/ui/tmx/isometric_position.rb +43 -0
  97. data/lib/metro/models/ui/tmx/orthogonal_position.rb +15 -0
  98. data/lib/metro/models/ui/tmx/tile_layer.rb +78 -0
  99. data/lib/metro/models/ui/ui.rb +13 -0
  100. data/lib/metro/parameters/command_line_args_parser.rb +68 -0
  101. data/lib/metro/parameters/options.rb +25 -0
  102. data/lib/metro/parameters/parameters.rb +2 -0
  103. data/lib/metro/sample.rb +40 -0
  104. data/lib/metro/scene.rb +478 -0
  105. data/lib/metro/scenes.rb +154 -0
  106. data/lib/metro/song.rb +56 -0
  107. data/lib/metro/template_message.rb +60 -0
  108. data/lib/metro/transitions/edit_transition_scene.rb +100 -0
  109. data/lib/metro/transitions/fade_transition_scene.rb +66 -0
  110. data/lib/metro/transitions/scene_transitions.rb +44 -0
  111. data/lib/metro/transitions/transition_scene.rb +19 -0
  112. data/lib/metro/units/bounds.rb +8 -0
  113. data/lib/metro/units/calculation_validations.rb +74 -0
  114. data/lib/metro/units/dimensions.rb +60 -0
  115. data/lib/metro/units/point.rb +51 -0
  116. data/lib/metro/units/rectangle_bounds.rb +148 -0
  117. data/lib/metro/units/scale.rb +46 -0
  118. data/lib/metro/units/units.rb +6 -0
  119. data/lib/metro/version.rb +32 -0
  120. data/lib/metro/views/json_view.rb +60 -0
  121. data/lib/metro/views/no_view.rb +34 -0
  122. data/lib/metro/views/parsers.rb +42 -0
  123. data/lib/metro/views/scene_view.rb +107 -0
  124. data/lib/metro/views/view.rb +133 -0
  125. data/lib/metro/views/writers.rb +43 -0
  126. data/lib/metro/views/yaml_view.rb +94 -0
  127. data/lib/metro/window.rb +95 -0
  128. data/lib/setup_handlers/exit_if_dry_run.rb +26 -0
  129. data/lib/setup_handlers/game_execution.rb +65 -0
  130. data/lib/setup_handlers/load_game_configuration.rb +65 -0
  131. data/lib/setup_handlers/load_game_files.rb +101 -0
  132. data/lib/setup_handlers/move_to_game_directory.rb +25 -0
  133. data/lib/setup_handlers/reload_game_on_game_file_changes.rb +79 -0
  134. data/lib/templates/game/README.md.tt +43 -0
  135. data/lib/templates/game/assets/brand.jpg +0 -0
  136. data/lib/templates/game/assets/hero.png +0 -0
  137. data/lib/templates/game/lib/custom_easing.rb +32 -0
  138. data/lib/templates/game/metro.tt +63 -0
  139. data/lib/templates/game/models/hero.rb +62 -0
  140. data/lib/templates/game/scenes/brand_scene.rb +19 -0
  141. data/lib/templates/game/scenes/brand_to_title_scene.rb +13 -0
  142. data/lib/templates/game/scenes/first_scene.rb +28 -0
  143. data/lib/templates/game/scenes/game_scene.rb +43 -0
  144. data/lib/templates/game/scenes/title_scene.rb +15 -0
  145. data/lib/templates/game/views/brand.yaml +4 -0
  146. data/lib/templates/game/views/brand_to_title.yaml +8 -0
  147. data/lib/templates/game/views/first.yaml +26 -0
  148. data/lib/templates/game/views/title.yaml +11 -0
  149. data/lib/templates/message.erb +23 -0
  150. data/lib/templates/model.rb.tt +111 -0
  151. data/lib/templates/scene.rb.tt +140 -0
  152. data/lib/templates/view.yaml.tt +11 -0
  153. data/lib/tmx_ext/object.rb +26 -0
  154. data/lib/tmx_ext/tile_set.rb +41 -0
  155. data/metro.gemspec +57 -0
  156. data/metro.png +0 -0
  157. data/spec/core_ext/numeric_spec.rb +78 -0
  158. data/spec/core_ext/string_spec.rb +33 -0
  159. data/spec/gosu_ext/color_spec.rb +80 -0
  160. data/spec/metro/image_spec.rb +33 -0
  161. data/spec/metro/models/key_value_coding_spec.rb +61 -0
  162. data/spec/metro/models/properties/array_property_spec.rb +60 -0
  163. data/spec/metro/models/properties/color_property_spec.rb +85 -0
  164. data/spec/metro/models/properties/dimensions_spec.rb +29 -0
  165. data/spec/metro/models/properties/font_property_spec.rb +127 -0
  166. data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
  167. data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
  168. data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
  169. data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
  170. data/spec/metro/models/properties/position_property_spec.rb +90 -0
  171. data/spec/metro/models/ui/label_spec.rb +259 -0
  172. data/spec/metro/parameters/command_line_args_parser_spec.rb +42 -0
  173. data/spec/metro/scene_spec.rb +15 -0
  174. data/spec/metro/scene_views/json_view_spec.rb +27 -0
  175. data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
  176. data/spec/metro/scenes_spec.rb +77 -0
  177. data/spec/metro/units/point_spec.rb +132 -0
  178. data/spec/metro/units/rectangle_bounds_spec.rb +56 -0
  179. data/spec/metro/views/view_spec.rb +53 -0
  180. data/spec/setup_handlers/exit_if_dry_run_spec.rb +27 -0
  181. data/spec/setup_handlers/reload_game_on_game_file_changes_spec.rb +68 -0
  182. data/spec/spec_helper.rb +20 -0
  183. data/spec/tmx_ext/object_spec.rb +96 -0
  184. data/spec/tmx_ext/tile_set_spec.rb +24 -0
  185. 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
@@ -0,0 +1,2 @@
1
+ require_relative 'command_line_args_parser'
2
+ require_relative 'options'
@@ -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
@@ -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