metro-ld26 0.3.4

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