metro-ld25 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +12 -0
  6. data/Guardfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +189 -0
  9. data/Rakefile +18 -0
  10. data/bin/metro +16 -0
  11. data/changelog.md +157 -0
  12. data/lib/assets/menu-movement.wav +0 -0
  13. data/lib/assets/menu-selection.wav +0 -0
  14. data/lib/assets/missing.ogg +0 -0
  15. data/lib/assets/missing.png +0 -0
  16. data/lib/assets/missing.wav +0 -0
  17. data/lib/assets/missing_animation.png +0 -0
  18. data/lib/commands/generate_game.rb +13 -0
  19. data/lib/commands/generate_model.rb +25 -0
  20. data/lib/commands/generate_scene.rb +36 -0
  21. data/lib/commands/generate_view.rb +21 -0
  22. data/lib/commands/thor.rb +83 -0
  23. data/lib/core_ext/class.rb +14 -0
  24. data/lib/core_ext/numeric.rb +59 -0
  25. data/lib/gosu_ext/color.rb +62 -0
  26. data/lib/gosu_ext/gosu_constants.rb +53 -0
  27. data/lib/locale/en.yml +35 -0
  28. data/lib/locale/locale.rb +1 -0
  29. data/lib/metro.rb +140 -0
  30. data/lib/metro/animation.rb +135 -0
  31. data/lib/metro/animation/after_interval_factory.rb +12 -0
  32. data/lib/metro/animation/animation_factory.rb +15 -0
  33. data/lib/metro/animation/easing/ease_in.rb +15 -0
  34. data/lib/metro/animation/easing/easing.rb +51 -0
  35. data/lib/metro/animation/easing/linear.rb +15 -0
  36. data/lib/metro/animation/has_animations.rb +70 -0
  37. data/lib/metro/animation/implicit_animation.rb +100 -0
  38. data/lib/metro/animation/on_update_operation.rb +96 -0
  39. data/lib/metro/animation/scene_animation.rb +16 -0
  40. data/lib/metro/asset_path.rb +97 -0
  41. data/lib/metro/events/control_definition.rb +11 -0
  42. data/lib/metro/events/controls.rb +42 -0
  43. data/lib/metro/events/event_data.rb +60 -0
  44. data/lib/metro/events/event_dictionary.rb +52 -0
  45. data/lib/metro/events/event_factory.rb +17 -0
  46. data/lib/metro/events/event_relay.rb +300 -0
  47. data/lib/metro/events/event_state_manager.rb +63 -0
  48. data/lib/metro/events/events.rb +3 -0
  49. data/lib/metro/events/has_events.rb +108 -0
  50. data/lib/metro/events/hit_list.rb +75 -0
  51. data/lib/metro/events/unknown_sender.rb +5 -0
  52. data/lib/metro/font.rb +69 -0
  53. data/lib/metro/game.rb +102 -0
  54. data/lib/metro/game/dsl.rb +68 -0
  55. data/lib/metro/image.rb +68 -0
  56. data/lib/metro/logging.rb +33 -0
  57. data/lib/metro/missing_scene.rb +21 -0
  58. data/lib/metro/models/audio/song.rb +33 -0
  59. data/lib/metro/models/draws.rb +86 -0
  60. data/lib/metro/models/key_value_coding.rb +38 -0
  61. data/lib/metro/models/model.rb +236 -0
  62. data/lib/metro/models/model_factory.rb +32 -0
  63. data/lib/metro/models/models.rb +62 -0
  64. data/lib/metro/models/properties/animation_property.rb +115 -0
  65. data/lib/metro/models/properties/array_property.rb +24 -0
  66. data/lib/metro/models/properties/boolean_property.rb +27 -0
  67. data/lib/metro/models/properties/color_property.rb +116 -0
  68. data/lib/metro/models/properties/dimensions_property.rb +84 -0
  69. data/lib/metro/models/properties/font_property.rb +130 -0
  70. data/lib/metro/models/properties/image_property.rb +96 -0
  71. data/lib/metro/models/properties/model_property.rb +84 -0
  72. data/lib/metro/models/properties/numeric_property.rb +29 -0
  73. data/lib/metro/models/properties/options_property/no_option.rb +29 -0
  74. data/lib/metro/models/properties/options_property/options.rb +94 -0
  75. data/lib/metro/models/properties/options_property/options_property.rb +125 -0
  76. data/lib/metro/models/properties/position_property.rb +90 -0
  77. data/lib/metro/models/properties/property.rb +221 -0
  78. data/lib/metro/models/properties/property_owner.rb +137 -0
  79. data/lib/metro/models/properties/sample_property.rb +84 -0
  80. data/lib/metro/models/properties/scale_property.rb +80 -0
  81. data/lib/metro/models/properties/song_property.rb +89 -0
  82. data/lib/metro/models/properties/text_property.rb +75 -0
  83. data/lib/metro/models/ui/animated_sprite.rb +85 -0
  84. data/lib/metro/models/ui/border.rb +95 -0
  85. data/lib/metro/models/ui/fps.rb +54 -0
  86. data/lib/metro/models/ui/generic.rb +66 -0
  87. data/lib/metro/models/ui/grid_drawer.rb +74 -0
  88. data/lib/metro/models/ui/image.rb +87 -0
  89. data/lib/metro/models/ui/label.rb +175 -0
  90. data/lib/metro/models/ui/menu.rb +214 -0
  91. data/lib/metro/models/ui/model_label.rb +65 -0
  92. data/lib/metro/models/ui/model_labeler.rb +79 -0
  93. data/lib/metro/models/ui/rectangle.rb +59 -0
  94. data/lib/metro/models/ui/sprite.rb +79 -0
  95. data/lib/metro/models/ui/tile_map.rb +162 -0
  96. data/lib/metro/models/ui/ui.rb +13 -0
  97. data/lib/metro/parameters/command_line_args_parser.rb +68 -0
  98. data/lib/metro/parameters/options.rb +25 -0
  99. data/lib/metro/parameters/parameters.rb +2 -0
  100. data/lib/metro/sample.rb +40 -0
  101. data/lib/metro/scene.rb +477 -0
  102. data/lib/metro/scenes.rb +154 -0
  103. data/lib/metro/song.rb +56 -0
  104. data/lib/metro/template_message.rb +60 -0
  105. data/lib/metro/transitions/edit_transition_scene.rb +100 -0
  106. data/lib/metro/transitions/fade_transition_scene.rb +66 -0
  107. data/lib/metro/transitions/scene_transitions.rb +44 -0
  108. data/lib/metro/transitions/transition_scene.rb +19 -0
  109. data/lib/metro/units/bounds.rb +8 -0
  110. data/lib/metro/units/calculation_validations.rb +74 -0
  111. data/lib/metro/units/dimensions.rb +60 -0
  112. data/lib/metro/units/point.rb +51 -0
  113. data/lib/metro/units/rectangle_bounds.rb +85 -0
  114. data/lib/metro/units/scale.rb +46 -0
  115. data/lib/metro/units/units.rb +6 -0
  116. data/lib/metro/version.rb +32 -0
  117. data/lib/metro/views/json_view.rb +60 -0
  118. data/lib/metro/views/no_view.rb +34 -0
  119. data/lib/metro/views/parsers.rb +42 -0
  120. data/lib/metro/views/scene_view.rb +107 -0
  121. data/lib/metro/views/view.rb +133 -0
  122. data/lib/metro/views/writers.rb +43 -0
  123. data/lib/metro/views/yaml_view.rb +94 -0
  124. data/lib/metro/window.rb +94 -0
  125. data/lib/setup_handlers/exit_if_dry_run.rb +26 -0
  126. data/lib/setup_handlers/game_execution.rb +65 -0
  127. data/lib/setup_handlers/load_game_configuration.rb +65 -0
  128. data/lib/setup_handlers/load_game_files.rb +101 -0
  129. data/lib/setup_handlers/move_to_game_directory.rb +25 -0
  130. data/lib/setup_handlers/reload_game_on_game_file_changes.rb +79 -0
  131. data/lib/templates/game/README.md.tt +52 -0
  132. data/lib/templates/game/assets/brand.jpg +0 -0
  133. data/lib/templates/game/assets/hero.png +0 -0
  134. data/lib/templates/game/lib/custom_easing.rb +32 -0
  135. data/lib/templates/game/metro.tt +63 -0
  136. data/lib/templates/game/models/hero.rb +62 -0
  137. data/lib/templates/game/scenes/brand_scene.rb +19 -0
  138. data/lib/templates/game/scenes/brand_to_title_scene.rb +13 -0
  139. data/lib/templates/game/scenes/first_scene.rb +28 -0
  140. data/lib/templates/game/scenes/game_scene.rb +43 -0
  141. data/lib/templates/game/scenes/title_scene.rb +15 -0
  142. data/lib/templates/game/views/brand.yaml +4 -0
  143. data/lib/templates/game/views/brand_to_title.yaml +8 -0
  144. data/lib/templates/game/views/first.yaml +26 -0
  145. data/lib/templates/game/views/title.yaml +11 -0
  146. data/lib/templates/message.erb +23 -0
  147. data/lib/templates/model.rb.tt +111 -0
  148. data/lib/templates/scene.rb.tt +140 -0
  149. data/lib/templates/view.yaml.tt +11 -0
  150. data/lib/tmxed_ext/tile_set.rb +34 -0
  151. data/metro.gemspec +56 -0
  152. data/spec/core_ext/numeric_spec.rb +78 -0
  153. data/spec/core_ext/string_spec.rb +33 -0
  154. data/spec/gosu_ext/color_spec.rb +80 -0
  155. data/spec/metro/events/event_state_manager_spec.rb +5 -0
  156. data/spec/metro/models/key_value_coding_spec.rb +61 -0
  157. data/spec/metro/models/properties/array_property_spec.rb +60 -0
  158. data/spec/metro/models/properties/color_property_spec.rb +85 -0
  159. data/spec/metro/models/properties/dimensions_spec.rb +29 -0
  160. data/spec/metro/models/properties/font_property_spec.rb +127 -0
  161. data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
  162. data/spec/metro/models/properties/options_property/no_option_spec.rb +25 -0
  163. data/spec/metro/models/properties/options_property/options_property_spec.rb +133 -0
  164. data/spec/metro/models/properties/options_property/options_spec.rb +125 -0
  165. data/spec/metro/models/properties/position_property_spec.rb +90 -0
  166. data/spec/metro/models/ui/label_spec.rb +259 -0
  167. data/spec/metro/parameters/command_line_args_parser_spec.rb +42 -0
  168. data/spec/metro/scene_spec.rb +15 -0
  169. data/spec/metro/scene_views/json_view_spec.rb +27 -0
  170. data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
  171. data/spec/metro/scenes_spec.rb +77 -0
  172. data/spec/metro/units/point_spec.rb +132 -0
  173. data/spec/metro/views/view_spec.rb +53 -0
  174. data/spec/setup_handlers/exit_if_dry_run_spec.rb +27 -0
  175. data/spec/setup_handlers/reload_game_on_game_file_changes_spec.rb +68 -0
  176. data/spec/spec_helper.rb +20 -0
  177. metadata +374 -0
@@ -0,0 +1,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,300 @@
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
+ @custom_notifications ||= HashWithIndifferentAccess.new([])
70
+ end
71
+
72
+ attr_reader :target, :window
73
+
74
+ #
75
+ # Register for a button_down event. These events are fired when
76
+ # the button is pressed down. This event only fires once when the
77
+ # button moves from the not pressed to the down state.
78
+ #
79
+ # @example Registering for a button down event to call a method named 'previous_option'
80
+ #
81
+ # class ExampleScene
82
+ # event :on_down, GpLeft, GpUp, do: :previous_option
83
+ #
84
+ # def previous_option
85
+ # @selected_index = @selected_index - 1
86
+ # @selected_index = options.length - 1 if @selected_index <= -1
87
+ # end
88
+ # end
89
+ #
90
+ # Here in this scene if the GpLeft or GpUp buttons are pressed down the method
91
+ # `previous_options` will be executed.
92
+ #
93
+ #
94
+ # @example Registering for a button down event with a block of code to execute
95
+ #
96
+ # class ExampleScene
97
+ # event :on_down, GpLeft, GpUp do
98
+ # @selected_index = @selected_index - 1
99
+ # @selected_index = options.length - 1 if @selected_index <= -1
100
+ # end
101
+ # end
102
+ #
103
+ # This example uses a block instead of a method name but it is absolultey the same
104
+ # as the last example.
105
+ #
106
+ def on_down(*args,&block)
107
+ _on(@down_actions,args,block)
108
+ end
109
+
110
+ alias_method :button_down, :on_down
111
+
112
+ #
113
+ # Register for a button_up event. These events are fired when
114
+ # the button is released (from being pressed down). This event only fires
115
+ # once when the button moves from the pressed state to the up state.
116
+ #
117
+ # @example Registering for a button down event to call a method named 'next_option'
118
+ #
119
+ # class ExampleScene
120
+ # event :on_up, KbEscape, do: :leave_scene
121
+ #
122
+ # def leave_scene
123
+ # transition_to :title
124
+ # end
125
+ # end
126
+ #
127
+ # Here in this scene if the Escape Key is pressed and released the example scene
128
+ # will transition to the title scene.
129
+ #
130
+ # @example Registering for a button up event with a block of code to execute
131
+ #
132
+ # class ExampleScene
133
+ # event :on_up, KbEscape do
134
+ # transition_to :title
135
+ # end
136
+ # end
137
+ #
138
+ # This example uses a block instead of a method name but it is absolultey the same
139
+ # as the last example.
140
+ #
141
+ def on_up(*args,&block)
142
+ _on(@up_actions,args,block)
143
+ end
144
+
145
+ alias_method :button_up, :on_up
146
+
147
+ #
148
+ # Register for a button_held event. These events are fired when
149
+ # the button is currently in the downstate. This event continues to fire at the
150
+ # beginning of every update of a scene until the button is released.
151
+ #
152
+ # @note button_held events require that the window be specified during initialization.
153
+ #
154
+ # @example Registering for button held events
155
+ #
156
+ # class ExampleScene
157
+ # event :on_hold KbLeft, GpLeft do
158
+ # player.turn_left
159
+ # end
160
+ #
161
+ # event :on_hold, KbRight, GpRight do
162
+ # player.turn_right
163
+ # end
164
+ #
165
+ # event :on_hold, KbUp, Gosu::GpButton0, do: :calculate_accleration
166
+ #
167
+ # def calculate_acceleration
168
+ # long_complicated_calculated_result = 0
169
+ # # ... multi-line calculations to determine the player acceleration ...
170
+ # player.accelerate = long_complicated_calculated_result
171
+ # end
172
+ # end
173
+ #
174
+ def on_hold(*args,&block)
175
+ log.warn "Registering for a on_hold event requires that a window be provided." unless window
176
+ _on(@held_actions,args,block)
177
+ end
178
+
179
+ alias_method :button_hold, :on_hold
180
+ alias_method :button_held, :on_hold
181
+
182
+ #
183
+ # Register for a custom notification event. These events are fired when
184
+ # another object within the game posts a notification with matching criteria.
185
+ # If there has indeed been a match, then the stored action block will be fired.
186
+ #
187
+ # When the action block is specified is defined with no parameters it is assumed that
188
+ # that the code should be executed within the context of the object that defined
189
+ # the action, the 'target'.
190
+ #
191
+ # @example Registering for a save complete event that would re-enable a menu.
192
+ #
193
+ # class ExampleScene
194
+ # event :notification, :save_complete do
195
+ # menu.enabled!
196
+ # end
197
+ # end
198
+ #
199
+ # The action block can also be specified with two parameters. In this case the code is
200
+ # no longer executed within the context of the object and is instead provided the
201
+ # the action target and the action source.
202
+ #
203
+ # @example Registering for a win game event that explicitly states the target and source.
204
+ #
205
+ # class ExampleScene
206
+ #
207
+ # event :notification, :win_game do |target,winner|
208
+ # target.declare_winner winner
209
+ # end
210
+ #
211
+ # def declare_winner(winning_player)
212
+ # # ...
213
+ # end
214
+ # end
215
+ #
216
+ def notification(param,&block)
217
+ custom_notifications[param.to_sym] = custom_notifications[param.to_sym] + [ block ]
218
+ end
219
+
220
+ attr_reader :up_actions, :down_actions, :held_actions, :custom_notifications
221
+
222
+ def _on(hash,args,block)
223
+ options = (args.last.is_a?(Hash) ? args.pop : {})
224
+
225
+ args.each do |keystroke|
226
+ hash[keystroke] = block || lambda { |instance| send(options[:do]) }
227
+ end
228
+ end
229
+
230
+ #
231
+ # This is called by external or parent source of events, usually a Scene, when a button up event
232
+ # has been triggered.
233
+ #
234
+ def fire_button_up(id)
235
+ execute_block_for_target( &up_action(id) )
236
+ end
237
+
238
+ #
239
+ # This is called by external or parent source of events, usually a Scene, when a button down
240
+ # event has been triggered.
241
+ #
242
+ def fire_button_down(id)
243
+ execute_block_for_target( &down_action(id) )
244
+ end
245
+
246
+ #
247
+ # Fire the events mapped to the held buttons within the context
248
+ # of the specified target. This method is differently formatted because held buttons are not
249
+ # events but polling to see if the button is still being held.
250
+ #
251
+ def fire_events_for_held_buttons
252
+ held_actions.each do |key,action|
253
+ execute_block_for_target(&action) if window and window.button_down?(key)
254
+ end
255
+ end
256
+
257
+ def execute_block_for_target(&block)
258
+ event_data = EventData.new(window)
259
+ target.instance_exec(event_data,&block)
260
+ end
261
+
262
+ # @return a block of code that is mapped for the 'button_up' id
263
+ def up_action(id)
264
+ up_actions[id] || lambda {|no_op| }
265
+ end
266
+
267
+ # @return a block of code that is mapped for the 'button_down' id
268
+ def down_action(id)
269
+ down_actions[id] || lambda {|no_op| }
270
+ end
271
+
272
+ #
273
+ # Fire all events mapped to the matching notification.
274
+ #
275
+ def fire_events_for_notification(event,sender)
276
+ notification_actions = custom_notifications[event]
277
+ notification_actions.each do |action|
278
+ _fire_event_for_notification(event,sender,action)
279
+ end
280
+ end
281
+
282
+ #
283
+ # Fire a single event based on the matched notification.
284
+ #
285
+ # An action without any parameters is assumed to be executed within the contexxt
286
+ # of the target. If there are two parameters we will simply execute the action and
287
+ # pass it both the target and the sender.
288
+ #
289
+ def _fire_event_for_notification(event,sender,action)
290
+ if action.arity == 2
291
+ target.instance_exec(sender,event,&action)
292
+ elsif action.arity == 1
293
+ target.instance_exec(sender,&action)
294
+ else
295
+ target.instance_eval(&action)
296
+ end
297
+ end
298
+
299
+ end
300
+ end