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.
Files changed (177) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +12 -0
  6. data/Guardfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +189 -0
  9. data/Rakefile +18 -0
  10. data/bin/metro +16 -0
  11. data/changelog.md +157 -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 +140 -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 +300 -0
  47. data/lib/metro/events/event_state_manager.rb +63 -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 +68 -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 +236 -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 +94 -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 +162 -0
  96. data/lib/metro/models/ui/ui.rb +13 -0
  97. data/lib/metro/parameters/command_line_args_parser.rb +68 -0
  98. data/lib/metro/parameters/options.rb +25 -0
  99. data/lib/metro/parameters/parameters.rb +2 -0
  100. data/lib/metro/sample.rb +40 -0
  101. data/lib/metro/scene.rb +477 -0
  102. data/lib/metro/scenes.rb +154 -0
  103. data/lib/metro/song.rb +56 -0
  104. data/lib/metro/template_message.rb +60 -0
  105. data/lib/metro/transitions/edit_transition_scene.rb +100 -0
  106. data/lib/metro/transitions/fade_transition_scene.rb +66 -0
  107. data/lib/metro/transitions/scene_transitions.rb +44 -0
  108. data/lib/metro/transitions/transition_scene.rb +19 -0
  109. data/lib/metro/units/bounds.rb +8 -0
  110. data/lib/metro/units/calculation_validations.rb +74 -0
  111. data/lib/metro/units/dimensions.rb +60 -0
  112. data/lib/metro/units/point.rb +51 -0
  113. data/lib/metro/units/rectangle_bounds.rb +85 -0
  114. data/lib/metro/units/scale.rb +46 -0
  115. data/lib/metro/units/units.rb +6 -0
  116. data/lib/metro/version.rb +32 -0
  117. data/lib/metro/views/json_view.rb +60 -0
  118. data/lib/metro/views/no_view.rb +34 -0
  119. data/lib/metro/views/parsers.rb +42 -0
  120. data/lib/metro/views/scene_view.rb +107 -0
  121. data/lib/metro/views/view.rb +133 -0
  122. data/lib/metro/views/writers.rb +43 -0
  123. data/lib/metro/views/yaml_view.rb +94 -0
  124. data/lib/metro/window.rb +94 -0
  125. data/lib/setup_handlers/exit_if_dry_run.rb +26 -0
  126. data/lib/setup_handlers/game_execution.rb +65 -0
  127. data/lib/setup_handlers/load_game_configuration.rb +65 -0
  128. data/lib/setup_handlers/load_game_files.rb +101 -0
  129. data/lib/setup_handlers/move_to_game_directory.rb +25 -0
  130. data/lib/setup_handlers/reload_game_on_game_file_changes.rb +79 -0
  131. data/lib/templates/game/README.md.tt +52 -0
  132. data/lib/templates/game/assets/brand.jpg +0 -0
  133. data/lib/templates/game/assets/hero.png +0 -0
  134. data/lib/templates/game/lib/custom_easing.rb +32 -0
  135. data/lib/templates/game/metro.tt +63 -0
  136. data/lib/templates/game/models/hero.rb +62 -0
  137. data/lib/templates/game/scenes/brand_scene.rb +19 -0
  138. data/lib/templates/game/scenes/brand_to_title_scene.rb +13 -0
  139. data/lib/templates/game/scenes/first_scene.rb +28 -0
  140. data/lib/templates/game/scenes/game_scene.rb +43 -0
  141. data/lib/templates/game/scenes/title_scene.rb +15 -0
  142. data/lib/templates/game/views/brand.yaml +4 -0
  143. data/lib/templates/game/views/brand_to_title.yaml +8 -0
  144. data/lib/templates/game/views/first.yaml +26 -0
  145. data/lib/templates/game/views/title.yaml +11 -0
  146. data/lib/templates/message.erb +23 -0
  147. data/lib/templates/model.rb.tt +111 -0
  148. data/lib/templates/scene.rb.tt +140 -0
  149. data/lib/templates/view.yaml.tt +11 -0
  150. data/lib/tmxed_ext/tile_set.rb +34 -0
  151. data/metro.gemspec +56 -0
  152. data/spec/core_ext/numeric_spec.rb +78 -0
  153. data/spec/core_ext/string_spec.rb +33 -0
  154. data/spec/gosu_ext/color_spec.rb +80 -0
  155. data/spec/metro/events/event_state_manager_spec.rb +5 -0
  156. data/spec/metro/models/key_value_coding_spec.rb +61 -0
  157. data/spec/metro/models/properties/array_property_spec.rb +60 -0
  158. data/spec/metro/models/properties/color_property_spec.rb +85 -0
  159. data/spec/metro/models/properties/dimensions_spec.rb +29 -0
  160. data/spec/metro/models/properties/font_property_spec.rb +127 -0
  161. data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
  162. data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
  163. data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
  164. data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
  165. data/spec/metro/models/properties/position_property_spec.rb +90 -0
  166. data/spec/metro/models/ui/label_spec.rb +259 -0
  167. data/spec/metro/parameters/command_line_args_parser_spec.rb +42 -0
  168. data/spec/metro/scene_spec.rb +15 -0
  169. data/spec/metro/scene_views/json_view_spec.rb +27 -0
  170. data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
  171. data/spec/metro/scenes_spec.rb +77 -0
  172. data/spec/metro/units/point_spec.rb +132 -0
  173. data/spec/metro/views/view_spec.rb +53 -0
  174. data/spec/setup_handlers/exit_if_dry_run_spec.rb +27 -0
  175. data/spec/setup_handlers/reload_game_on_game_file_changes_spec.rb +68 -0
  176. data/spec/spec_helper.rb +20 -0
  177. 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
@@ -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,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