metro-ld25 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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