metro 0.0.6 → 0.1.0

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.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile CHANGED
@@ -2,3 +2,10 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in metro.gemspec
4
4
  gemspec
5
+
6
+
7
+ group 'development' do
8
+ gem 'guard'
9
+ gem 'guard-rspec'
10
+ gem 'rb-fsevent', '~> 0.9.1'
11
+ end
data/Guardfile ADDED
@@ -0,0 +1,4 @@
1
+ guard 'rspec', version: 2, cli: "-c -f d" do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ end
data/README.md CHANGED
@@ -1,3 +1,11 @@
1
+ ```
2
+ ______ ___ _____
3
+ ___ |/ /_____ __ /_______________
4
+ __ /|_/ / _ _ \_ __/__ ___/_ __ \
5
+ _ / / / / __// /_ _ / / /_/ /
6
+ /_/ /_/ \___/ \__/ /_/ \____/
7
+
8
+ ```
1
9
  # metro
2
10
 
3
11
  Metro is a framework built around [gosu](https://github.com/jlnr/gosu) (the 2D game development library in Ruby). The goal of Metro is to enforce common conceptual structures and conventions making it easier to quickly generate a game.
@@ -12,7 +12,7 @@ class Gosu::Color
12
12
  if value.is_a? Gosu::Color
13
13
  gosu_initialize value.alpha, value.red, value.green, value.blue
14
14
  elsif value.is_a? String
15
- gosu_initialize value.to_i(16)
15
+ gosu_initialize *Array(self.class.parse_string(value))
16
16
  else
17
17
  gosu_initialize value
18
18
  end
@@ -21,4 +21,28 @@ class Gosu::Color
21
21
  end
22
22
  end
23
23
 
24
+ def self.parse_string(value)
25
+ parse_hex(value) || parse_rgb(value) || parse_rgba(value) || [ 255, 255, 255, 255 ]
26
+ end
27
+
28
+ def self.parse_rgba(rgba)
29
+ if rgba =~ /rgba\(([\d]{1,3}),([\d]{1,3}),([\d]{1,3}),(\d(?:\.\d)?)\)/
30
+ [ (255 * $4.to_f).floor.to_i, $1.to_i, $2.to_i, $3.to_i ]
31
+ end
32
+ end
33
+
34
+ def self.parse_rgb(rgb)
35
+ if rgb =~ /rgb\(([\d]{1,3}),([\d]{1,3}),([\d]{1,3})\)/
36
+ [ 255, $1.to_i, $2.to_i, $3.to_i ]
37
+ end
38
+ end
39
+
40
+ def self.parse_hex(hex)
41
+ if hex =~ /0x([A-Fa-f0-9]{8})/
42
+ hex.to_i(16)
43
+ elsif hex =~ /#([A-Fa-f0-9]{6})/
44
+ "0xFF#{$1}".to_i(16)
45
+ end
46
+ end
47
+
24
48
  end
data/lib/metro.rb CHANGED
@@ -2,20 +2,24 @@ require 'gosu'
2
2
  require 'gosu_ext/color'
3
3
 
4
4
  require 'logger'
5
-
5
+ require 'erb'
6
6
 
7
7
  require 'metro/version'
8
+ require 'metro/error'
9
+ require 'metro/template_message'
8
10
  require 'metro/window'
9
11
  require 'metro/game'
10
12
  require 'metro/scene'
11
13
  require 'metro/models/model'
12
14
  require 'metro/models/generic'
13
15
 
16
+ require_relative 'metro/missing_scene'
17
+
18
+
14
19
  def asset_path(name)
15
20
  File.join Dir.pwd, "assets", name
16
21
  end
17
22
 
18
-
19
23
  def log
20
24
  @log ||= begin
21
25
  logger = Logger.new(STDOUT)
@@ -57,6 +61,7 @@ module Metro
57
61
  end
58
62
 
59
63
  def load_game_configuration(filename)
64
+ game_files_exist!(filename)
60
65
  game_contents = File.read(filename)
61
66
  game_block = lambda {|instance| eval(game_contents) }
62
67
  game = Game::DSL.parse(&game_block)
@@ -70,4 +75,27 @@ module Metro
70
75
  window.show
71
76
  end
72
77
 
78
+ def game_files_exist!(*files)
79
+ error_messages = files.compact.flatten.map { |file| game_file_exists?(file) }.reject {|exist| exist == true }
80
+ unless error_messages.empty?
81
+ display_error_message(error_messages)
82
+ exit 1
83
+ end
84
+ end
85
+
86
+ def game_file_exists?(file)
87
+ unless File.exists? file
88
+ Error.new title: "Unable to find Metro game file",
89
+ message: "The specified file `#{file}` which is required to run the game could not be found.",
90
+ details: [ "Ensure you have specified the correct file", "Ensure that the file exists at that location" ]
91
+ else
92
+ true
93
+ end
94
+ end
95
+
96
+ def display_error_message(messages)
97
+ message = TemplateMessage.new messages: messages, website: WEBSITE, email: CONTACT_EMAILS
98
+ warn message
99
+ end
100
+
73
101
  end
@@ -0,0 +1,14 @@
1
+ module Metro
2
+
3
+ class AnimationFactory
4
+
5
+ attr_reader :options, :on_complete_block
6
+
7
+ def initialize(options = {},&block)
8
+ @options = options
9
+ @on_complete_block = block
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'animation_factory'
2
+
3
+ module Metro
4
+ module HasAnimations
5
+
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ #
13
+ # Define an animation to execute when the scene starts.
14
+ #
15
+ # @example Defining an animation that fades in and moves a logo when it is
16
+ # done, transition to the title scene.
17
+ #
18
+ # animate actor: :logo, to: { y: 80, alpha: 50 }, interval: 120 do
19
+ # transition_to :title
20
+ # end
21
+ #
22
+ def animate(options,&block)
23
+ scene_animation = AnimationFactory.new options, &block
24
+ animations.push scene_animation
25
+ end
26
+
27
+ #
28
+ # All the animations that are defined for the scene to be run the scene starts.
29
+ #
30
+ def animations
31
+ @animations ||= []
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ class Error
2
+
3
+ def initialize(details = {})
4
+ @title = details[:title]
5
+ @message = details[:message]
6
+ @details = details[:details]
7
+ end
8
+
9
+ def title
10
+ "ERROR: #{@title.upcase}"
11
+ end
12
+
13
+ def message
14
+ @message
15
+ end
16
+
17
+ def details
18
+ @details.map {|detail| "* #{detail}" }.join("\n")
19
+ end
20
+
21
+ end
@@ -0,0 +1,15 @@
1
+ module Metro
2
+
3
+ class EventFactory
4
+
5
+ attr_reader :event, :buttons, :block
6
+
7
+ def initialize(event,buttons = [],&block)
8
+ @event = event
9
+ @buttons = buttons
10
+ @block = block
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -56,6 +56,7 @@ module Metro
56
56
  @up_actions ||= {}
57
57
  @down_actions ||= {}
58
58
  @held_actions ||= {}
59
+ @custom_notifications ||= Hash.new([])
59
60
  end
60
61
 
61
62
  attr_reader :target, :window
@@ -68,9 +69,7 @@ module Metro
68
69
  # @example Registering for a button down event to call a method named 'previous_option'
69
70
  #
70
71
  # class ExampleScene
71
- # def events(e)
72
- # e.on_down Gosu::GpLeft, Gosu::GpUp, do: :previous_option
73
- # end
72
+ # event :on_down, Gosu::GpLeft, Gosu::GpUp, do: :previous_option
74
73
  #
75
74
  # def previous_option
76
75
  # @selected_index = @selected_index - 1
@@ -85,11 +84,9 @@ module Metro
85
84
  # @example Registering for a button down event with a block of code to execute
86
85
  #
87
86
  # class ExampleScene
88
- # def events(e)
89
- # e.on_down Gosu::GpLeft, Gosu::GpUp do
90
- # @selected_index = @selected_index - 1
91
- # @selected_index = options.length - 1 if @selected_index <= -1
92
- # end
87
+ # event :on_down, Gosu::GpLeft, Gosu::GpUp do
88
+ # @selected_index = @selected_index - 1
89
+ # @selected_index = options.length - 1 if @selected_index <= -1
93
90
  # end
94
91
  # end
95
92
  #
@@ -108,9 +105,7 @@ module Metro
108
105
  # @example Registering for a button down event to call a method named 'next_option'
109
106
  #
110
107
  # class ExampleScene
111
- # def events(e)
112
- # e.on_up Gosu::KbEscape, do: :leave_scene
113
- # end
108
+ # event :on_up, Gosu::KbEscape, do: :leave_scene
114
109
  #
115
110
  # def leave_scene
116
111
  # transition_to :title
@@ -123,10 +118,8 @@ module Metro
123
118
  # @example Registering for a button up event with a block of code to execute
124
119
  #
125
120
  # class ExampleScene
126
- # def events(e)
127
- # e.on_up Gosu::KbEscape do
128
- # transition_to :title
129
- # end
121
+ # event :on_up, Gosu::KbEscape do
122
+ # transition_to :title
130
123
  # end
131
124
  # end
132
125
  #
@@ -147,18 +140,16 @@ module Metro
147
140
  # @example Registering for button held events
148
141
  #
149
142
  # class ExampleScene
150
- # def events(e)
151
- # e.on_hold Gosu::KbLeft, Gosu::GpLeft do
152
- # player.turn_left
153
- # end
154
- #
155
- # e.on_hold Gosu::KbRight, Gosu::GpRight do
156
- # player.turn_right
157
- # end
143
+ # event :on_hold Gosu::KbLeft, Gosu::GpLeft do
144
+ # player.turn_left
145
+ # end
158
146
  #
159
- # e.on_hold Gosu::KbUp, Gosu::GpButton0, do: :calculate_accleration
147
+ # event :on_hold, Gosu::KbRight, Gosu::GpRight do
148
+ # player.turn_right
160
149
  # end
161
150
  #
151
+ # event :on_hold, Gosu::KbUp, Gosu::GpButton0, do: :calculate_accleration
152
+ #
162
153
  # def calculate_acceleration
163
154
  # long_complicated_calculated_result = 0
164
155
  # # ... multi-line calculations to determine the player acceleration ...
@@ -171,7 +162,45 @@ module Metro
171
162
  _on(@held_actions,args,block)
172
163
  end
173
164
 
174
- attr_reader :up_actions, :down_actions, :held_actions
165
+ #
166
+ # Register for a custom notification event. These events are fired when
167
+ # another object within the game posts a notification with matching criteria.
168
+ # If there has indeed been a match, then the stored action block will be fired.
169
+ #
170
+ # When the action block is specified is defined with no parameters it is assumed that
171
+ # that the code should be executed within the context of the object that defined
172
+ # the action, the 'target'.
173
+ #
174
+ # @example Registering for a save complete event that would re-enable a menu.
175
+ #
176
+ # class ExampleScene
177
+ # event :notification, :save_complete do
178
+ # menu.enabled!
179
+ # end
180
+ # end
181
+ #
182
+ # The action block can also be specified with two parameters. In this case the code is
183
+ # no longer executed within the context of the object and is instead provided the
184
+ # the action target and the action source.
185
+ #
186
+ # @example Registering for a win game event that explicitly states the target and source.
187
+ #
188
+ # class ExampleScene
189
+ #
190
+ # event :notification, :win_game do |target,winner|
191
+ # target.declare_winner winner
192
+ # end
193
+ #
194
+ # def declare_winner(winning_player)
195
+ # # ...
196
+ # end
197
+ # end
198
+ #
199
+ def notification(param,&block)
200
+ custom_notifications[param.to_sym] = custom_notifications[param.to_sym] + [ block ]
201
+ end
202
+
203
+ attr_reader :up_actions, :down_actions, :held_actions, :custom_notifications
175
204
 
176
205
  def _on(hash,args,block)
177
206
  options = (args.last.is_a?(Hash) ? args.pop : {})
@@ -220,6 +249,35 @@ module Metro
220
249
  down_actions[id] || lambda {|instance| send(:down_action_missing,id) if respond_to?(:down_action_missing) }
221
250
  end
222
251
 
252
+ #
253
+ # Fire all events mapped to the matching notification.
254
+ #
255
+ def fire_events_for_notification(event,sender)
256
+ notification_actions = custom_notifications[event]
257
+ notification_actions.each do |action|
258
+ _fire_event_for_notification(event,sender,action)
259
+ end
260
+ end
261
+
262
+ #
263
+ # Fire a single event based on the matched notification.
264
+ #
265
+ # An action without any parameters is assumed to be executed within the contexxt
266
+ # of the target. If there are two parameters we will simply execute the action and
267
+ # pass it both the target and the sender.
268
+ #
269
+ # @TODO: Allow for the blocks to be specified with one parameter: source (and executed
270
+ # within the context of the target)
271
+ #
272
+ # @TODO: Allow for the blocks to be specified with three parameters: source, target, event
273
+ #
274
+ def _fire_event_for_notification(event,sender,action)
275
+ if action.arity == 2
276
+ action.call(target,sender)
277
+ else
278
+ target.instance_eval(&action)
279
+ end
280
+ end
223
281
 
224
282
  end
225
283
  end
@@ -0,0 +1,108 @@
1
+ require_relative 'event_factory'
2
+
3
+ module Metro
4
+
5
+ module HasEvents
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ #
14
+ # Register an event for the scene.
15
+ #
16
+ # @example Registering for a save complete event that would re-enable a menu.
17
+ #
18
+ # class ExampleScene
19
+ # event :notification, :save_complete do
20
+ # menu.enabled!
21
+ # end
22
+ # end
23
+ #
24
+ # @example Registering for button held events
25
+ #
26
+ # class ExampleScene
27
+ # event :on_hold Gosu::KbLeft, Gosu::GpLeft do
28
+ # player.turn_left
29
+ # end
30
+ #
31
+ # event :on_hold, Gosu::KbRight, Gosu::GpRight do
32
+ # player.turn_right
33
+ # end
34
+ #
35
+ # event :on_hold, Gosu::KbUp, Gosu::GpButton0, do: :calculate_accleration
36
+ #
37
+ # def calculate_acceleration
38
+ # long_complicated_calculated_result = 0
39
+ # # ... multi-line calculations to determine the player acceleration ...
40
+ # player.accelerate = long_complicated_calculated_result
41
+ # end
42
+ # end
43
+ #
44
+ # @example Registering for a button down event to call a method named 'next_option'
45
+ #
46
+ # class ExampleScene
47
+ # event :on_up, Gosu::KbEscape, do: :leave_scene
48
+ #
49
+ # def leave_scene
50
+ # transition_to :title
51
+ # end
52
+ # end
53
+ #
54
+ # Here in this scene if the Escape Key is pressed and released the example scene
55
+ # will transition to the title scene.
56
+ #
57
+ # @example Registering for a button up event with a block of code to execute
58
+ #
59
+ # class ExampleScene
60
+ # event :on_up, Gosu::KbEscape do
61
+ # transition_to :title
62
+ # end
63
+ # end
64
+ #
65
+ # @example Registering for a button down event to call a method named 'previous_option'
66
+ #
67
+ # class ExampleScene
68
+ # event :on_down, Gosu::GpLeft, Gosu::GpUp, do: :previous_option
69
+ #
70
+ # def previous_option
71
+ # @selected_index = @selected_index - 1
72
+ # @selected_index = options.length - 1 if @selected_index <= -1
73
+ # end
74
+ # end
75
+ #
76
+ # Here in this scene if the GpLeft or GpUp buttons are pressed down the method
77
+ # `previous_options` will be executed.
78
+ #
79
+ #
80
+ # @example Registering for a button down event with a block of code to execute
81
+ #
82
+ # class ExampleScene
83
+ # event :on_down, Gosu::GpLeft, Gosu::GpUp do
84
+ # @selected_index = @selected_index - 1
85
+ # @selected_index = options.length - 1 if @selected_index <= -1
86
+ # end
87
+ # end
88
+ #
89
+ # This example uses a block instead of a method name but it is absolultey the same
90
+ # as the last example.
91
+ #
92
+ def event(event_type,*buttons,&block)
93
+ scene_event = EventFactory.new event_type, buttons, &block
94
+ events.push scene_event
95
+ end
96
+
97
+ #
98
+ # @return a list of all the EventFactories defined for the scene
99
+ #
100
+ def events
101
+ @events ||= []
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+
108
+ end