metro-ld26 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +14 -0
  6. data/Guardfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +177 -0
  9. data/Rakefile +18 -0
  10. data/bin/metro +16 -0
  11. data/changelog.md +153 -0
  12. data/lib/assets/menu-movement.wav +0 -0
  13. data/lib/assets/menu-selection.wav +0 -0
  14. data/lib/assets/missing.ogg +0 -0
  15. data/lib/assets/missing.png +0 -0
  16. data/lib/assets/missing.wav +0 -0
  17. data/lib/assets/missing_animation.png +0 -0
  18. data/lib/commands/generate_game.rb +13 -0
  19. data/lib/commands/generate_model.rb +25 -0
  20. data/lib/commands/generate_scene.rb +36 -0
  21. data/lib/commands/generate_view.rb +21 -0
  22. data/lib/commands/thor.rb +83 -0
  23. data/lib/core_ext/class.rb +14 -0
  24. data/lib/core_ext/numeric.rb +59 -0
  25. data/lib/gosu_ext/color.rb +62 -0
  26. data/lib/gosu_ext/gosu_constants.rb +53 -0
  27. data/lib/locale/en.yml +35 -0
  28. data/lib/locale/locale.rb +1 -0
  29. data/lib/metro.rb +144 -0
  30. data/lib/metro/animation.rb +135 -0
  31. data/lib/metro/animation/after_interval_factory.rb +12 -0
  32. data/lib/metro/animation/animation_factory.rb +15 -0
  33. data/lib/metro/animation/easing/ease_in.rb +15 -0
  34. data/lib/metro/animation/easing/easing.rb +51 -0
  35. data/lib/metro/animation/easing/linear.rb +15 -0
  36. data/lib/metro/animation/has_animations.rb +70 -0
  37. data/lib/metro/animation/implicit_animation.rb +100 -0
  38. data/lib/metro/animation/on_update_operation.rb +96 -0
  39. data/lib/metro/animation/scene_animation.rb +16 -0
  40. data/lib/metro/asset_path.rb +97 -0
  41. data/lib/metro/events/control_definition.rb +11 -0
  42. data/lib/metro/events/controls.rb +42 -0
  43. data/lib/metro/events/event_data.rb +60 -0
  44. data/lib/metro/events/event_dictionary.rb +52 -0
  45. data/lib/metro/events/event_factory.rb +17 -0
  46. data/lib/metro/events/event_relay.rb +342 -0
  47. data/lib/metro/events/event_state_manager.rb +70 -0
  48. data/lib/metro/events/events.rb +3 -0
  49. data/lib/metro/events/has_events.rb +108 -0
  50. data/lib/metro/events/hit_list.rb +75 -0
  51. data/lib/metro/events/unknown_sender.rb +5 -0
  52. data/lib/metro/font.rb +69 -0
  53. data/lib/metro/game.rb +102 -0
  54. data/lib/metro/game/dsl.rb +68 -0
  55. data/lib/metro/image.rb +75 -0
  56. data/lib/metro/logging.rb +33 -0
  57. data/lib/metro/missing_scene.rb +21 -0
  58. data/lib/metro/models/audio/song.rb +33 -0
  59. data/lib/metro/models/draws.rb +86 -0
  60. data/lib/metro/models/key_value_coding.rb +38 -0
  61. data/lib/metro/models/model.rb +246 -0
  62. data/lib/metro/models/model_factory.rb +32 -0
  63. data/lib/metro/models/models.rb +62 -0
  64. data/lib/metro/models/properties/animation_property.rb +115 -0
  65. data/lib/metro/models/properties/array_property.rb +24 -0
  66. data/lib/metro/models/properties/boolean_property.rb +27 -0
  67. data/lib/metro/models/properties/color_property.rb +116 -0
  68. data/lib/metro/models/properties/dimensions_property.rb +84 -0
  69. data/lib/metro/models/properties/font_property.rb +130 -0
  70. data/lib/metro/models/properties/image_property.rb +96 -0
  71. data/lib/metro/models/properties/model_property.rb +84 -0
  72. data/lib/metro/models/properties/numeric_property.rb +29 -0
  73. data/lib/metro/models/properties/options_property/no_option.rb +29 -0
  74. data/lib/metro/models/properties/options_property/options.rb +98 -0
  75. data/lib/metro/models/properties/options_property/options_property.rb +125 -0
  76. data/lib/metro/models/properties/position_property.rb +90 -0
  77. data/lib/metro/models/properties/property.rb +221 -0
  78. data/lib/metro/models/properties/property_owner.rb +137 -0
  79. data/lib/metro/models/properties/sample_property.rb +84 -0
  80. data/lib/metro/models/properties/scale_property.rb +80 -0
  81. data/lib/metro/models/properties/song_property.rb +89 -0
  82. data/lib/metro/models/properties/text_property.rb +75 -0
  83. data/lib/metro/models/ui/animated_sprite.rb +85 -0
  84. data/lib/metro/models/ui/border.rb +95 -0
  85. data/lib/metro/models/ui/fps.rb +54 -0
  86. data/lib/metro/models/ui/generic.rb +66 -0
  87. data/lib/metro/models/ui/grid_drawer.rb +74 -0
  88. data/lib/metro/models/ui/image.rb +87 -0
  89. data/lib/metro/models/ui/label.rb +175 -0
  90. data/lib/metro/models/ui/menu.rb +214 -0
  91. data/lib/metro/models/ui/model_label.rb +65 -0
  92. data/lib/metro/models/ui/model_labeler.rb +79 -0
  93. data/lib/metro/models/ui/rectangle.rb +59 -0
  94. data/lib/metro/models/ui/sprite.rb +79 -0
  95. data/lib/metro/models/ui/tile_map.rb +132 -0
  96. data/lib/metro/models/ui/tmx/isometric_position.rb +43 -0
  97. data/lib/metro/models/ui/tmx/orthogonal_position.rb +15 -0
  98. data/lib/metro/models/ui/tmx/tile_layer.rb +78 -0
  99. data/lib/metro/models/ui/ui.rb +13 -0
  100. data/lib/metro/parameters/command_line_args_parser.rb +68 -0
  101. data/lib/metro/parameters/options.rb +25 -0
  102. data/lib/metro/parameters/parameters.rb +2 -0
  103. data/lib/metro/sample.rb +40 -0
  104. data/lib/metro/scene.rb +478 -0
  105. data/lib/metro/scenes.rb +154 -0
  106. data/lib/metro/song.rb +56 -0
  107. data/lib/metro/template_message.rb +60 -0
  108. data/lib/metro/transitions/edit_transition_scene.rb +100 -0
  109. data/lib/metro/transitions/fade_transition_scene.rb +66 -0
  110. data/lib/metro/transitions/scene_transitions.rb +44 -0
  111. data/lib/metro/transitions/transition_scene.rb +19 -0
  112. data/lib/metro/units/bounds.rb +8 -0
  113. data/lib/metro/units/calculation_validations.rb +74 -0
  114. data/lib/metro/units/dimensions.rb +60 -0
  115. data/lib/metro/units/point.rb +51 -0
  116. data/lib/metro/units/rectangle_bounds.rb +148 -0
  117. data/lib/metro/units/scale.rb +46 -0
  118. data/lib/metro/units/units.rb +6 -0
  119. data/lib/metro/version.rb +32 -0
  120. data/lib/metro/views/json_view.rb +60 -0
  121. data/lib/metro/views/no_view.rb +34 -0
  122. data/lib/metro/views/parsers.rb +42 -0
  123. data/lib/metro/views/scene_view.rb +107 -0
  124. data/lib/metro/views/view.rb +133 -0
  125. data/lib/metro/views/writers.rb +43 -0
  126. data/lib/metro/views/yaml_view.rb +94 -0
  127. data/lib/metro/window.rb +95 -0
  128. data/lib/setup_handlers/exit_if_dry_run.rb +26 -0
  129. data/lib/setup_handlers/game_execution.rb +65 -0
  130. data/lib/setup_handlers/load_game_configuration.rb +65 -0
  131. data/lib/setup_handlers/load_game_files.rb +101 -0
  132. data/lib/setup_handlers/move_to_game_directory.rb +25 -0
  133. data/lib/setup_handlers/reload_game_on_game_file_changes.rb +79 -0
  134. data/lib/templates/game/README.md.tt +43 -0
  135. data/lib/templates/game/assets/brand.jpg +0 -0
  136. data/lib/templates/game/assets/hero.png +0 -0
  137. data/lib/templates/game/lib/custom_easing.rb +32 -0
  138. data/lib/templates/game/metro.tt +63 -0
  139. data/lib/templates/game/models/hero.rb +62 -0
  140. data/lib/templates/game/scenes/brand_scene.rb +19 -0
  141. data/lib/templates/game/scenes/brand_to_title_scene.rb +13 -0
  142. data/lib/templates/game/scenes/first_scene.rb +28 -0
  143. data/lib/templates/game/scenes/game_scene.rb +43 -0
  144. data/lib/templates/game/scenes/title_scene.rb +15 -0
  145. data/lib/templates/game/views/brand.yaml +4 -0
  146. data/lib/templates/game/views/brand_to_title.yaml +8 -0
  147. data/lib/templates/game/views/first.yaml +26 -0
  148. data/lib/templates/game/views/title.yaml +11 -0
  149. data/lib/templates/message.erb +23 -0
  150. data/lib/templates/model.rb.tt +111 -0
  151. data/lib/templates/scene.rb.tt +140 -0
  152. data/lib/templates/view.yaml.tt +11 -0
  153. data/lib/tmx_ext/object.rb +26 -0
  154. data/lib/tmx_ext/tile_set.rb +41 -0
  155. data/metro.gemspec +57 -0
  156. data/metro.png +0 -0
  157. data/spec/core_ext/numeric_spec.rb +78 -0
  158. data/spec/core_ext/string_spec.rb +33 -0
  159. data/spec/gosu_ext/color_spec.rb +80 -0
  160. data/spec/metro/image_spec.rb +33 -0
  161. data/spec/metro/models/key_value_coding_spec.rb +61 -0
  162. data/spec/metro/models/properties/array_property_spec.rb +60 -0
  163. data/spec/metro/models/properties/color_property_spec.rb +85 -0
  164. data/spec/metro/models/properties/dimensions_spec.rb +29 -0
  165. data/spec/metro/models/properties/font_property_spec.rb +127 -0
  166. data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
  167. data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
  168. data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
  169. data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
  170. data/spec/metro/models/properties/position_property_spec.rb +90 -0
  171. data/spec/metro/models/ui/label_spec.rb +259 -0
  172. data/spec/metro/parameters/command_line_args_parser_spec.rb +42 -0
  173. data/spec/metro/scene_spec.rb +15 -0
  174. data/spec/metro/scene_views/json_view_spec.rb +27 -0
  175. data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
  176. data/spec/metro/scenes_spec.rb +77 -0
  177. data/spec/metro/units/point_spec.rb +132 -0
  178. data/spec/metro/units/rectangle_bounds_spec.rb +56 -0
  179. data/spec/metro/views/view_spec.rb +53 -0
  180. data/spec/setup_handlers/exit_if_dry_run_spec.rb +27 -0
  181. data/spec/setup_handlers/reload_game_on_game_file_changes_spec.rb +68 -0
  182. data/spec/spec_helper.rb +20 -0
  183. data/spec/tmx_ext/object_spec.rb +96 -0
  184. data/spec/tmx_ext/tile_set_spec.rb +24 -0
  185. metadata +379 -0
@@ -0,0 +1,16 @@
1
+ require_relative 'on_update_operation'
2
+
3
+ module Metro
4
+
5
+ module SceneAnimation
6
+ extend self
7
+
8
+ def build(options,&block)
9
+ animation = Metro::ImplicitAnimation.new options
10
+ animation.on_complete(&block) if block
11
+ animation
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,97 @@
1
+
2
+ #
3
+ # The asset_path is a helper which will generate a filepath based on the current
4
+ # working directory of the game. This allows for game author's to specify a path
5
+ # relative within the assets directory of their game.
6
+ #
7
+ # @note Paths that are defined within views use this helper and are assumed to
8
+ # be paths relative within the assets directory of the game. For images,
9
+ # samples, and songs in a model consider using a property.
10
+ #
11
+ # @see Metro::Model::ImageProperty
12
+ # @see Metro::Model::AnimationProperty
13
+ # @see Metro::Model::SongProperty
14
+ # @see Metro::Model::SampleProperty
15
+ #
16
+ # @note Also consider the model classes Image, Animation, Song, and Sample for
17
+ # creating the assets found within the assets folder. Each of those will
18
+ # assist with using the asset_path internally to find the asset.
19
+ #
20
+ # @see Metro::Image
21
+ # @see Metro::Animation
22
+ # @see Metro::Song
23
+ # @see Metro::Sample
24
+ #
25
+ # @example Loading a raw Gosu::Image with an `asset_path`
26
+ #
27
+ # class Player < Metro::Model
28
+ # def image
29
+ # @image ||= Gosu::Image.new( window, asset_path("player.png"), false )
30
+ # end
31
+ # def draw
32
+ # image.draw_rot(x,y,2,angle)
33
+ # end
34
+ # end
35
+ #
36
+ def asset_path(name)
37
+ File.join Dir.pwd, "assets", name
38
+ end
39
+
40
+ #
41
+ # The metro_asset_path is a helper which will generate a filepath based on the
42
+ # directory of the metro library. This is used to retrieve internal assets which
43
+ # can be used to serve up missing images, animations, samples, or songs or
44
+ # defaults that may come with the metro game library.
45
+ #
46
+ #
47
+ def metro_asset_path(name)
48
+ File.join Metro.asset_dir, name
49
+ end
50
+
51
+ module Metro
52
+
53
+ #
54
+ # An AssetPath searches through the various paths based on the path provided.
55
+ #
56
+ # First it assumes the path is absolute, second it assuemts that path is within
57
+ # the game, and third it assumes it is within metro itself.
58
+ #
59
+ class AssetPath
60
+
61
+ def self.with(path)
62
+ path.is_a?(AssetPath) ? path : new(path.to_s)
63
+ end
64
+
65
+ def initialize(path)
66
+ @path = path
67
+ end
68
+
69
+ attr_reader :path
70
+
71
+ def filepath
72
+ @filepath ||= begin
73
+ absolute_asset?(path) or game_asset?(path) or metro_asset?(path)
74
+ end
75
+ end
76
+
77
+ def absolute_asset?(path)
78
+ asset_at_path? path
79
+ end
80
+
81
+ def game_asset?(path)
82
+ asset_at_path? asset_path(path)
83
+ end
84
+
85
+ def metro_asset?(path)
86
+ asset_at_path? metro_asset_path(path)
87
+ end
88
+
89
+ alias_method :to_s, :filepath
90
+
91
+ private
92
+
93
+ def asset_at_path?(asset_path)
94
+ asset_path if File.exists?(asset_path) and File.file?(asset_path)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,11 @@
1
+ module Metro
2
+ class ControlDefinition
3
+ attr_accessor :name, :event, :args
4
+
5
+ def initialize(name,event,args)
6
+ @name = name
7
+ @event = event
8
+ @args = args
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'control_definition'
2
+
3
+ module Metro
4
+ #
5
+ # Assists in creating and the storing of ControlDefinitions.
6
+ #
7
+ # @see DSL
8
+ #
9
+ class Controls
10
+
11
+ #
12
+ # Creation through controls is usually done with an instance_eval
13
+ # of a block and this allows for a flexible interface.
14
+ #
15
+ # @param [String,Symbol] name the name or the alias of the control
16
+ # as it will be used within the course of the game.
17
+ #
18
+ def method_missing(name,*params,&block)
19
+ options = params.find {|param| param.is_a? Hash }
20
+ define_control(name,options)
21
+ end
22
+
23
+ def define_control(name,options)
24
+ event = _event_type(options)
25
+ args = _event_args(options)
26
+
27
+ defined_controls.push ControlDefinition.new name, event, args
28
+ end
29
+
30
+ def _event_type(options)
31
+ options[:is]
32
+ end
33
+
34
+ def _event_args(options)
35
+ options[:with]
36
+ end
37
+
38
+ def defined_controls
39
+ @defined_controls ||= []
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,60 @@
1
+ module Metro
2
+ class EventData
3
+
4
+ attr_reader :mouse_point, :created_at
5
+
6
+ def initialize(window)
7
+ @created_at = Time.now
8
+ @mouse_point = Metro::Units::Point.at window.mouse_x, window.mouse_y
9
+
10
+ capture_modifier_keys(window)
11
+ end
12
+
13
+ def modifier_keys
14
+ @modifier_keys ||= {}
15
+ end
16
+
17
+ def capture_modifier_keys(window)
18
+ self.class.modifier_key_list.each do |key|
19
+ modifier_keys[key] = window.button_down?(key)
20
+ end
21
+ end
22
+
23
+ #
24
+ # TODO: This attempt to reduce duplication is brittle and will likely end in heartache.
25
+ #
26
+
27
+ def self.modifier_key_list_names
28
+ @modifier_key_list_names ||= %w[ KbLeftControl KbRightControl
29
+ KbLeftAlt KbRightAlt
30
+ KbLeftMeta KbRightMeta
31
+ KbLeftShift KbRightShift ]
32
+ end
33
+
34
+ def self.modifier_key_list
35
+ @modifier_key_list ||= modifier_key_list_names.map {|key| "Gosu::#{key}".constantize }
36
+ end
37
+
38
+ #
39
+ # Generate methods for each left and right modifier key
40
+ #
41
+
42
+ modifier_key_list_names.each_with_index do |key_name,index|
43
+ define_method "#{key_name.gsub(/^Kb/,'').underscore}?" do
44
+ modifier_keys[self.class.modifier_key_list[index]]
45
+ end
46
+ end
47
+
48
+ #
49
+ # Define generic modifier keys that is not concerned with whether it
50
+ # was the left key or the right key.
51
+ #
52
+
53
+ [ :control?, :alt?, :meta?, :shift? ].each do |generic|
54
+ define_method generic do
55
+ send("left_#{generic}") or send("right_#{generic}")
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'event_factory'
2
+
3
+ module Metro
4
+
5
+ module EventDictionary
6
+ extend self
7
+
8
+ #
9
+ # All defined events within this dictionary.
10
+ #
11
+ def events
12
+ @events ||= HashWithIndifferentAccess.new
13
+ end
14
+
15
+ #
16
+ # @example Adding a new SceneEvent to the Dictionary
17
+ #
18
+ # SceneEventDictionary.add target: scene_name, type: event_type, args: args, block: block
19
+ #
20
+ def add(params = {})
21
+ target = params[:target]
22
+ event = EventFactory.new params[:type], params[:args], &params[:block]
23
+ events[target] = events_for_target(target).push event
24
+ end
25
+
26
+ #
27
+ # Return all the events for all the specified targets.
28
+ #
29
+ def events_for_targets(*list)
30
+ found_events = Array(list).flatten.compact.map {|s| events_for_target(s) }.flatten.compact
31
+ found_events
32
+ end
33
+
34
+ #
35
+ # Return the events for the specified target
36
+ #
37
+ def events_for_target(scene_name)
38
+ events[scene_name] ||= []
39
+ end
40
+
41
+ #
42
+ # When the game is reset the event dictionary needs to flush out all of the events that it
43
+ # has loaded as the game files will be reloaded. All metro related components will not
44
+ # be removed as those files are not reloaded when the game is reloaded.
45
+ #
46
+ def reset!
47
+ events.delete_if { |name,events| ! name.start_with? "metro/" }
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,17 @@
1
+ module Metro
2
+
3
+ class EventFactory
4
+
5
+ attr_reader :event, :args, :block
6
+
7
+ alias_method :buttons, :args
8
+
9
+ def initialize(event,args=[],&block)
10
+ @event = event
11
+ @args = args
12
+ @block = block
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,342 @@
1
+ require_relative 'event_data'
2
+
3
+ module Metro
4
+
5
+ #
6
+ # An EventRelay represents a target's willingness to respond to events
7
+ # generate from the window. An event relay is generate for every scene
8
+ # but additional relays can be generated to also listen for events.
9
+ #
10
+ # An event relay can register a target to listen for the following window
11
+ # events: 'button_down'; 'button_up'; and 'button_held'.
12
+ #
13
+ # @note registering for 'button_held' events require that the window be
14
+ # speicfied. As that is the only way to ask if the button is currently
15
+ # pressed.
16
+ #
17
+ # @see #on_up
18
+ # @see #on_down
19
+ # @see #on_hold
20
+ #
21
+ class EventRelay
22
+
23
+ #
24
+ # Defines the provided controls for every EventRelay that is created.
25
+ #
26
+ # @see #define_control
27
+ #
28
+ # @param [Array<ControlDefinition>] controls the definitions of controls
29
+ # that should be added to all EventRelays.
30
+ #
31
+ def self.define_controls(controls)
32
+ controls.each { |control| define_control control }
33
+ end
34
+
35
+ #
36
+ # Defines a control from a ControlDefinition for all EventRelays. A
37
+ # control is a way of defining a shortcut for a common event. This
38
+ # could be the use of a common set of keys for confirmation or canceling.
39
+ #
40
+ def self.define_control(control)
41
+ check_for_already_defined_control!(control)
42
+
43
+ define_method control.name do |&block|
44
+ send(control.event,*control.args,&block)
45
+ end
46
+ end
47
+
48
+ def self.check_for_already_defined_control!(control)
49
+ if instance_methods.include? control.name
50
+ error! "error.reserved_control_name", name: control.name
51
+ end
52
+ end
53
+
54
+ #
55
+ # An event relay is created a with a target and optionally a window.
56
+ #
57
+ # @param [Object] target the object that will execute the code when
58
+ # the button events have fired have been triggered.
59
+ # @param [Window] window the window of the game. This parameter is
60
+ # optional and only required if the events are interested in buttons
61
+ # being held.
62
+ #
63
+ def initialize(target,window = nil)
64
+ @target = target
65
+ @window = window
66
+ @up_actions ||= {}
67
+ @down_actions ||= {}
68
+ @held_actions ||= {}
69
+ @mouse_movement_actions ||= []
70
+ @custom_notifications ||= HashWithIndifferentAccess.new([])
71
+ end
72
+
73
+ attr_reader :target, :window
74
+
75
+ #
76
+ # Register for a button_down event. These events are fired when
77
+ # the button is pressed down. This event only fires once when the
78
+ # button moves from the not pressed to the down state.
79
+ #
80
+ # @example Registering for a button down event to call a method named 'previous_option'
81
+ #
82
+ # class ExampleScene
83
+ # event :on_down, GpLeft, GpUp, do: :previous_option
84
+ #
85
+ # def previous_option
86
+ # @selected_index = @selected_index - 1
87
+ # @selected_index = options.length - 1 if @selected_index <= -1
88
+ # end
89
+ # end
90
+ #
91
+ # Here in this scene if the GpLeft or GpUp buttons are pressed down the method
92
+ # `previous_options` will be executed.
93
+ #
94
+ #
95
+ # @example Registering for a button down event with a block of code to execute
96
+ #
97
+ # class ExampleScene
98
+ # event :on_down, GpLeft, GpUp do
99
+ # @selected_index = @selected_index - 1
100
+ # @selected_index = options.length - 1 if @selected_index <= -1
101
+ # end
102
+ # end
103
+ #
104
+ # This example uses a block instead of a method name but it is absolultey the same
105
+ # as the last example.
106
+ #
107
+ def on_down(*args,&block)
108
+ _on(@down_actions,args,block)
109
+ end
110
+
111
+ alias_method :button_down, :on_down
112
+
113
+ #
114
+ # Register for a button_up event. These events are fired when
115
+ # the button is released (from being pressed down). This event only fires
116
+ # once when the button moves from the pressed state to the up state.
117
+ #
118
+ # @example Registering for a button down event to call a method named 'next_option'
119
+ #
120
+ # class ExampleScene
121
+ # event :on_up, KbEscape, do: :leave_scene
122
+ #
123
+ # def leave_scene
124
+ # transition_to :title
125
+ # end
126
+ # end
127
+ #
128
+ # Here in this scene if the Escape Key is pressed and released the example scene
129
+ # will transition to the title scene.
130
+ #
131
+ # @example Registering for a button up event with a block of code to execute
132
+ #
133
+ # class ExampleScene
134
+ # event :on_up, KbEscape do
135
+ # transition_to :title
136
+ # end
137
+ # end
138
+ #
139
+ # This example uses a block instead of a method name but it is absolultey the same
140
+ # as the last example.
141
+ #
142
+ def on_up(*args,&block)
143
+ _on(@up_actions,args,block)
144
+ end
145
+
146
+ alias_method :button_up, :on_up
147
+
148
+ #
149
+ # Register for a button_held event. These events are fired when
150
+ # the button is currently in the downstate. This event continues to fire at the
151
+ # beginning of every update of a scene until the button is released.
152
+ #
153
+ # @note button_held events require that the window be specified during initialization.
154
+ #
155
+ # @example Registering for button held events
156
+ #
157
+ # class ExampleScene
158
+ # event :on_hold KbLeft, GpLeft do
159
+ # player.turn_left
160
+ # end
161
+ #
162
+ # event :on_hold, KbRight, GpRight do
163
+ # player.turn_right
164
+ # end
165
+ #
166
+ # event :on_hold, KbUp, Gosu::GpButton0, do: :calculate_accleration
167
+ #
168
+ # def calculate_acceleration
169
+ # long_complicated_calculated_result = 0
170
+ # # ... multi-line calculations to determine the player acceleration ...
171
+ # player.accelerate = long_complicated_calculated_result
172
+ # end
173
+ # end
174
+ #
175
+ def on_hold(*args,&block)
176
+ log.warn "Registering for a on_hold event requires that a window be provided." unless window
177
+ _on(@held_actions,args,block)
178
+ end
179
+
180
+ alias_method :button_hold, :on_hold
181
+ alias_method :button_held, :on_hold
182
+
183
+
184
+ #
185
+ # Register for mouse movements events. These events are fired each update
186
+ # providing an event which contains the current position of the mouse.
187
+ #
188
+ # @note mouse movement events fire with each update so it is up to the
189
+ # receiving object of the event to determine if the new mouse movement
190
+ # is a delta.
191
+ #
192
+ # @note mouse movement events require that the window be specified during initialization.
193
+ #
194
+ # @example Registering for button held events
195
+ #
196
+ # class ExampleScene
197
+ #
198
+ # draws :player
199
+ #
200
+ # event :on_mouse_movement do |event|
201
+ # player.position = event.mouse_point
202
+ # end
203
+ # end
204
+ #
205
+ def on_mouse_movement(*args,&block)
206
+ options = (args.last.is_a?(Hash) ? args.pop : {})
207
+ @mouse_movement_actions << ( block || lambda { |instance| send(options[:do]) } )
208
+ end
209
+
210
+ #
211
+ # Register for a custom notification event. These events are fired when
212
+ # another object within the game posts a notification with matching criteria.
213
+ # If there has indeed been a match, then the stored action block will be fired.
214
+ #
215
+ # When the action block is specified is defined with no parameters it is assumed that
216
+ # that the code should be executed within the context of the object that defined
217
+ # the action, the 'target'.
218
+ #
219
+ # @example Registering for a save complete event that would re-enable a menu.
220
+ #
221
+ # class ExampleScene
222
+ # event :notification, :save_complete do
223
+ # menu.enabled!
224
+ # end
225
+ # end
226
+ #
227
+ # The action block can also be specified with two parameters. In this case the code is
228
+ # no longer executed within the context of the object and is instead provided the
229
+ # the action target and the action source.
230
+ #
231
+ # @example Registering for a win game event that explicitly states the target and source.
232
+ #
233
+ # class ExampleScene
234
+ #
235
+ # event :notification, :win_game do |target,winner|
236
+ # target.declare_winner winner
237
+ # end
238
+ #
239
+ # def declare_winner(winning_player)
240
+ # # ...
241
+ # end
242
+ # end
243
+ #
244
+ def notification(param,&block)
245
+ custom_notifications[param.to_sym] = custom_notifications[param.to_sym] + [ block ]
246
+ end
247
+
248
+ attr_reader :up_actions, :down_actions, :held_actions
249
+
250
+ attr_reader :mouse_movement_actions
251
+
252
+ attr_reader :custom_notifications
253
+
254
+ def _on(hash,args,block)
255
+ options = (args.last.is_a?(Hash) ? args.pop : {})
256
+
257
+ args.each do |keystroke|
258
+ hash[keystroke] = block || lambda { |instance| send(options[:do]) }
259
+ end
260
+ end
261
+
262
+ #
263
+ # This is called by external or parent source of events, usually a Scene, when a button up event
264
+ # has been triggered.
265
+ #
266
+ def fire_button_up(id)
267
+ execute_block_for_target( &up_action(id) )
268
+ end
269
+
270
+ #
271
+ # This is called by external or parent source of events, usually a Scene, when a button down
272
+ # event has been triggered.
273
+ #
274
+ def fire_button_down(id)
275
+ execute_block_for_target( &down_action(id) )
276
+ end
277
+
278
+ #
279
+ # Fire the events mapped to the held buttons within the context
280
+ # of the specified target. This method is differently formatted because held buttons are not
281
+ # events but polling to see if the button is still being held.
282
+ #
283
+ def fire_events_for_held_buttons
284
+ held_actions.each do |key,action|
285
+ execute_block_for_target(&action) if window and window.button_down?(key)
286
+ end
287
+ end
288
+
289
+ #
290
+ # Fire events for all the registered actions that are suppose to receive
291
+ # the mouse movement events.
292
+ #
293
+ def fire_events_for_mouse_movement
294
+ mouse_movement_actions.each do |action|
295
+ execute_block_for_target(&action)
296
+ end
297
+ end
298
+
299
+ def execute_block_for_target(&block)
300
+ event_data = EventData.new(window)
301
+ target.instance_exec(event_data,&block)
302
+ end
303
+
304
+ # @return a block of code that is mapped for the 'button_up' id
305
+ def up_action(id)
306
+ up_actions[id] || lambda {|no_op| }
307
+ end
308
+
309
+ # @return a block of code that is mapped for the 'button_down' id
310
+ def down_action(id)
311
+ down_actions[id] || lambda {|no_op| }
312
+ end
313
+
314
+ #
315
+ # Fire all events mapped to the matching notification.
316
+ #
317
+ def fire_events_for_notification(event,sender)
318
+ notification_actions = custom_notifications[event]
319
+ notification_actions.each do |action|
320
+ _fire_event_for_notification(event,sender,action)
321
+ end
322
+ end
323
+
324
+ #
325
+ # Fire a single event based on the matched notification.
326
+ #
327
+ # An action without any parameters is assumed to be executed within the contexxt
328
+ # of the target. If there are two parameters we will simply execute the action and
329
+ # pass it both the target and the sender.
330
+ #
331
+ def _fire_event_for_notification(event,sender,action)
332
+ if action.arity == 2
333
+ target.instance_exec(sender,event,&action)
334
+ elsif action.arity == 1
335
+ target.instance_exec(sender,&action)
336
+ else
337
+ target.instance_eval(&action)
338
+ end
339
+ end
340
+
341
+ end
342
+ end