metro 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.md +2 -0
  2. data/changelog.md +10 -0
  3. data/lib/assets/missing.mp3 +0 -0
  4. data/lib/assets/missing.wav +0 -0
  5. data/lib/gosu_ext/image.rb +4 -0
  6. data/lib/gosu_ext/sample.rb +18 -0
  7. data/lib/gosu_ext/song.rb +18 -0
  8. data/lib/metro.rb +2 -0
  9. data/lib/metro/animation/easing/ease_in.rb +15 -0
  10. data/lib/metro/animation/easing/easing.rb +51 -0
  11. data/lib/metro/animation/easing/linear.rb +15 -0
  12. data/lib/metro/animation/has_animations.rb +17 -0
  13. data/lib/metro/animation/implicit_animation.rb +5 -24
  14. data/lib/metro/models/draws.rb +10 -1
  15. data/lib/metro/models/model.rb +117 -89
  16. data/lib/metro/models/models/image.rb +8 -9
  17. data/lib/metro/models/models/label.rb +4 -8
  18. data/lib/metro/models/models/menu.rb +7 -9
  19. data/lib/metro/models/models/rectangle.rb +7 -12
  20. data/lib/metro/models/models/song.rb +33 -0
  21. data/lib/metro/models/properties/dimensions_property.rb +18 -1
  22. data/lib/metro/models/properties/image_property.rb +2 -2
  23. data/lib/metro/models/properties/position_property.rb +6 -0
  24. data/lib/metro/models/properties/property.rb +129 -23
  25. data/lib/metro/models/properties/sample_property.rb +97 -0
  26. data/lib/metro/models/properties/song_property.rb +113 -0
  27. data/lib/metro/scene.rb +2 -11
  28. data/lib/metro/scenes.rb +14 -7
  29. data/lib/metro/transitions/fade_transition_scene.rb +8 -1
  30. data/lib/metro/units/bounds.rb +8 -0
  31. data/lib/metro/units/dimensions.rb +23 -5
  32. data/lib/metro/units/point.rb +26 -1
  33. data/lib/metro/units/rectangle_bounds.rb +52 -0
  34. data/lib/metro/units/scale.rb +16 -0
  35. data/lib/metro/units/units.rb +3 -1
  36. data/lib/metro/version.rb +1 -1
  37. data/lib/metro/window.rb +7 -0
  38. data/lib/templates/game/README.md.tt +20 -1
  39. data/spec/metro/models/models/label_spec.rb +4 -4
  40. data/spec/metro/models/properties/dimensions_spec.rb +29 -0
  41. data/spec/metro/models/properties/position_property_spec.rb +5 -5
  42. data/spec/spec_helper.rb +3 -1
  43. metadata +22 -9
  44. data/lib/metro/animation/easing.rb +0 -31
  45. data/lib/metro/models/rectangle_bounds.rb +0 -28
data/README.md CHANGED
@@ -12,6 +12,8 @@ Metro is a framework built around [gosu](https://github.com/jlnr/gosu) (the 2D g
12
12
 
13
13
  > NOTE: This project is very early in development and at this point mostly a prototype to explore more of theses concepts to gain an understanding of core tools necessary to make games.
14
14
 
15
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/burtlo/metro)
16
+
15
17
  ## Installation
16
18
 
17
19
  $ gem install metro
data/changelog.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Metro
2
2
 
3
+ ## 0.2.2 / 2012-11-10
4
+
5
+ * Song support added (scene methods and model properties)
6
+ * Sample support added (model properties)
7
+ * Added a missing sample/song
8
+ * Implicit Animation easings can now be more easily created and registered.
9
+ * Properties can now be defined with a block
10
+ * FIX Dimensions parse creation called wrong method
11
+ * Removed support for specifying a color in animation
12
+
3
13
  ## 0.2.1 / 2012-11-08
4
14
 
5
15
  * FIX Scene fade transition color changing and implicit animations
Binary file
Binary file
@@ -34,5 +34,9 @@ module Metro
34
34
  # The tileability of the image
35
35
  attr_reader :tileable
36
36
 
37
+ def dimensions
38
+ Metro::Units::Dimensions.of width, height
39
+ end
40
+
37
41
  end
38
42
  end
@@ -0,0 +1,18 @@
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
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Metro
2
+
3
+ #
4
+ # Song is a wrapper class for a Gosu Song. This allows for additional data to be stored
5
+ # without relying on monkey-patching on functionality.
6
+ #
7
+ class Song < SimpleDelegator
8
+
9
+ attr_accessor :song, :path
10
+
11
+ def initialize(song,path)
12
+ super(song)
13
+ @song = song
14
+ @path = path
15
+ end
16
+
17
+ end
18
+ end
data/lib/metro.rb CHANGED
@@ -13,6 +13,8 @@ require 'active_support/hash_with_indifferent_access'
13
13
  require 'gosu_ext/color'
14
14
  require 'gosu_ext/image'
15
15
  require 'gosu_ext/animation'
16
+ require 'gosu_ext/song'
17
+ require 'gosu_ext/sample'
16
18
  require 'gosu_ext/gosu_constants'
17
19
  require 'core_ext/numeric'
18
20
 
@@ -0,0 +1,15 @@
1
+ module Metro
2
+ class Easing
3
+
4
+ #
5
+ # Perform a ease-in motion between the start position and the final position.
6
+ #
7
+ class EaseIn < Easing
8
+ def self.calculation(moment,start,change,interval)
9
+ change * (moment = moment / interval) * moment + start
10
+ end
11
+ end
12
+
13
+ register :ease_in, EaseIn
14
+ end
15
+ end
@@ -0,0 +1,51 @@
1
+ module Metro
2
+
3
+ #
4
+ # An easing is a means to calculate the steps between the start and the final
5
+ # over an interval.
6
+ #
7
+ class Easing
8
+
9
+ #
10
+ # The calculate method is called within the ImplicitAnimation and will create
11
+ # an array of all the states between the start and the final value.
12
+ #
13
+ def self.calculate(start,final,interval)
14
+ change = final - start
15
+ (1..interval).map { |time| calculation(time.to_f,start,change,interval) }
16
+ end
17
+
18
+ #
19
+ # The calculation method is to be overriden in the Easing subclasses.
20
+ # This calculation figures out the value at the current moemnt.
21
+ #
22
+ def self.calculation(moment,start,change,interval) ; 0 ; end
23
+
24
+ #
25
+ # Register an easing within the game system.
26
+ #
27
+ def self.register(name,easing_class)
28
+ easings[name] = easing_class.to_s
29
+ end
30
+
31
+ #
32
+ # @return the easing class based on the specified easing name.
33
+ #
34
+ def self.easing_for(easing)
35
+ easing_classname = easings[easing]
36
+ easing_classname.constantize
37
+ end
38
+
39
+ #
40
+ # A hash of all the supported easings within metro or game.
41
+ #
42
+ def self.easings
43
+ @easings_hash ||= HashWithIndifferentAccess.new("Metro::Easing::Linear")
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ require_relative 'linear'
51
+ require_relative 'ease_in'
@@ -0,0 +1,15 @@
1
+ module Metro
2
+ class Easing
3
+
4
+ #
5
+ # Perform a linear motion between the start position and the final position.
6
+ #
7
+ class Linear < Easing
8
+ def self.calculation(moment,start,change,interval)
9
+ change * moment / interval + start
10
+ end
11
+ end
12
+
13
+ register :linear, Linear
14
+ end
15
+ end
@@ -7,6 +7,20 @@ module Metro
7
7
  base.extend ClassMethods
8
8
  end
9
9
 
10
+ #
11
+ # A simplier syntax to enqueue an animation. At the moment this animation is going
12
+ # to be an implicit animation.
13
+ #
14
+ def animate(actor_or_actor_name,options,&block)
15
+ options[:actor] = actor(actor_or_actor_name)
16
+ options[:context] = self
17
+ animation_group = SceneAnimation.build options, &block
18
+ enqueue animation_group
19
+ end
20
+
21
+ # An alternative to stating `animate` syntax.
22
+ alias_method :change, :animate
23
+
10
24
  module ClassMethods
11
25
 
12
26
  #
@@ -24,6 +38,9 @@ module Metro
24
38
  animations.push scene_animation
25
39
  end
26
40
 
41
+ # Provide an alternative to the `animate` syntax.
42
+ alias_method :change, :animate
43
+
27
44
  #
28
45
  # All the animations that are defined for the scene to be run the scene starts.
29
46
  #
@@ -1,4 +1,4 @@
1
- require_relative 'easing'
1
+ require_relative 'easing/easing'
2
2
 
3
3
  module Metro
4
4
 
@@ -52,21 +52,7 @@ module Metro
52
52
  def after_initialize
53
53
  to.each do |attribute,final|
54
54
  start = actor.send(attribute)
55
-
56
- if attribute == :color
57
- final = Gosu::Color.new final
58
-
59
- # @TODO: This is not going to work when the color uses a non-standard
60
- # name for the color property.
61
-
62
- animations.push build_animation_step(:red,start.red,final.red)
63
- animations.push build_animation_step(:green,start.green,final.green)
64
- animations.push build_animation_step(:blue,start.blue,final.blue)
65
- animations.push build_animation_step(:alpha,start.alpha,final.alpha)
66
- else
67
- animations.push build_animation_step(attribute,start,final)
68
- end
69
-
55
+ animations.push build_animation_step(attribute,start,final)
70
56
  end
71
57
  end
72
58
 
@@ -74,7 +60,7 @@ module Metro
74
60
  step = AnimationStep.new
75
61
  step.actor = actor
76
62
  step.attribute = attribute
77
- step.deltas = stepping(easing).calculate(start.to_f,final.to_f,interval.to_f)
63
+ step.deltas = easing_for(easing).calculate(start.to_f,final.to_f,interval.to_f)
78
64
  step
79
65
  end
80
66
 
@@ -90,13 +76,8 @@ module Metro
90
76
  # @return the correct easing based on the specified name. When the name
91
77
  # provided does not match anything then default to linear easing.
92
78
  #
93
- def stepping(stepping)
94
- @steppings ||= begin
95
- hash = HashWithIndifferentAccess.new("Metro::Easing::Linear")
96
- hash.merge! linear: "Metro::Easing::Linear",
97
- ease_in: "Metro::Easing::EaseIn"
98
- end
99
- @steppings[stepping].constantize
79
+ def easing_for(name)
80
+ Metro::Easing.easing_for(name)
100
81
  end
101
82
 
102
83
  #
@@ -12,7 +12,7 @@ module Metro
12
12
  #
13
13
  # Define an actor with the given name and options.
14
14
  #
15
- # As a convience the draw method will define `getter` and `setter`
15
+ # As a convenience the draw method will define `getter` and `setter`
16
16
  # methods for the specified actor.
17
17
  #
18
18
  # @example Defining a title label within a scene
@@ -48,6 +48,15 @@ module Metro
48
48
  drawings.push scene_actor
49
49
  end
50
50
 
51
+ #
52
+ # Define a sound actor with the given anem and options.
53
+ #
54
+ # @see #draw
55
+ #
56
+ def play(song_name,options={})
57
+ draw song_name, options.merge(model: "metro::models::song")
58
+ end
59
+
51
60
  #
52
61
  # Define several actors to be drawn.
53
62
  #
@@ -1,5 +1,4 @@
1
1
  require_relative 'key_value_coding'
2
- require_relative 'rectangle_bounds'
3
2
  require_relative 'properties/property'
4
3
 
5
4
  module Metro
@@ -16,6 +15,31 @@ module Metro
16
15
  class Model
17
16
  include Units
18
17
 
18
+ #
19
+ # This is an entry point for customization. As the model's {#initialize}
20
+ # method performs may perform some initialization that may be necessary.
21
+ #
22
+ # At this point the model has been created. However, the window and scene
23
+ # of the model will not have been defined and defined properties rely on
24
+ # the window or scene will return nil values. Other properties also will
25
+ # likely not be set.
26
+ #
27
+ # @note This method should be implemented in the Model subclass.
28
+ #
29
+ def after_initialize ; end
30
+
31
+
32
+ #
33
+ # This is an entry point for customization. After the model's properties
34
+ # have been set and the model has been assigned to the window and scene
35
+ # this method is called. Here is where customization of properties or
36
+ # final positioning can be performed.
37
+ #
38
+ # @note This method may be implemented in the Model subclass.
39
+ #
40
+ def show ; end
41
+
42
+
19
43
  #
20
44
  # This is called every update interval while the actor is in the scene
21
45
  #
@@ -23,6 +47,7 @@ module Metro
23
47
  #
24
48
  def update ; end
25
49
 
50
+
26
51
  #
27
52
  # This is called after an update. A model normally is not removed after
28
53
  # an update, however if the model responds true to #completed? then it
@@ -34,7 +59,41 @@ module Metro
34
59
  def completed? ; false ; end
35
60
 
36
61
 
37
- def self.property(name,options={})
62
+ #
63
+ # This is called after every {#update} and when the OS wants the window to
64
+ # repaint itself.
65
+ #
66
+ # @note This method should be implemented in the Model subclass.
67
+ #
68
+ def draw ; end
69
+
70
+
71
+
72
+ #
73
+ # Define a property for the model. A property has a name and then can optionally specify
74
+ # a property type which will receive additional options.
75
+ #
76
+ # @example Defining various propertys for a model
77
+ #
78
+ # class Player
79
+ # property :position
80
+ # property :angle, default: 0.0
81
+ # property :turn_amount, default: 4.5
82
+ # property :image, path: "player.png"
83
+ # property :motto, type: :text, default: 'Hometown Heroes!'
84
+ # end
85
+ #
86
+ # When the property name matches a property definition with that name they will be used. This is what
87
+ # happens for the 'position' and 'image' properties defined above. Both of those map to respective
88
+ # properties with matching names.
89
+ #
90
+ # Properties by default are assumed to be numeric properties so the types does not have to be stated.
91
+ # This is the case for 'angle' and 'turn_amount' properties.
92
+ #
93
+ # You may use any particular name for your properties as long as you specify the type. This is the case
94
+ # for the 'motto' property.
95
+ #
96
+ def self.property(name,options={},&block)
38
97
 
39
98
  # Use the name as the property type if one has not been provided.
40
99
 
@@ -42,6 +101,18 @@ module Metro
42
101
 
43
102
  property_class = Property.property(property_type)
44
103
 
104
+ define_method name do
105
+ raw_value = properties[name]
106
+ property_class.new(self,options,&block).get raw_value
107
+ end
108
+
109
+ define_method "#{name}=" do |value|
110
+ prepared_value = property_class.new(self,options).set(value)
111
+ properties[name] = prepared_value
112
+ end
113
+
114
+ # Define any sub-properties defined on this property
115
+
45
116
  # When the name does not match the property type then we want to force
46
117
  # the prefixing to be on for our sub-properties. This is to make sure
47
118
  # that when people define multiple fonts and colors that they do not
@@ -49,72 +120,60 @@ module Metro
49
120
 
50
121
  override_prefix = !(name == property_type)
51
122
 
52
- # Define any properties defined on this property
53
-
54
123
  property_class.defined_properties.each do |subproperty|
55
124
  sub_options = { prefix: override_prefix }.merge(subproperty.options)
56
125
  sub_options = sub_options.merge(parents: (Array(sub_options[:parents]) + [name]))
57
-
58
- property subproperty.name, sub_options
126
+ _sub_property subproperty.name, sub_options
59
127
  end
60
128
 
61
- # To ensure that our sub-properties are aware of the of their
62
- # parent property for which they are dependent on we will need
63
- # to know this information because we need to behave appropriately
64
- # within the getter and setter blocks below.
65
-
66
- is_a_dependency = true if options[:parents]
67
-
68
- if is_a_dependency
129
+ end
69
130
 
70
- parents = Array(options[:parents])
131
+ #
132
+ # Defines the sub-properties defined within the property. This is to be used internally
133
+ # by the #property method.
134
+ #
135
+ def self._sub_property(name,options={},&block)
71
136
 
72
- method_name = name
137
+ # Use the name as the property type if one has not been provided.
73
138
 
74
- if options[:prefix]
75
- method_name = (parents + [name]).join("_")
76
- end
139
+ property_type = options[:type] || name
77
140
 
78
- # Define a getter for the sub-property that will traverse the
79
- # parent properties, finally returning the filtered value
141
+ property_class = Property.property(property_type)
80
142
 
81
- define_method method_name do
82
- raw_value = (parents + [name]).inject(self) {|current,method| current.send(method) }
83
- property_class.new(self,options).get raw_value
84
- end
143
+ parents = Array(options[:parents])
85
144
 
86
- # Define a setter for the sub-property that will find the parent
87
- # value and set itself on that with the filtered value. The parent
88
- # is then set.
89
- #
90
- # @TODO: If getters return dups and not instances of the original object then a very
91
- # deep setter will not be valid.
92
- #
93
- define_method "#{method_name}=" do |value|
94
- parent_value = parents.inject(self) {|current,method| current.send(method) }
145
+ method_name = name
95
146
 
96
- prepared_value = property_class.new(self,options).set(value)
97
- parent_value.send("#{name}=",prepared_value)
147
+ if options[:prefix]
148
+ method_name = (parents + [name]).join("_")
149
+ end
98
150
 
99
- send("#{parents.last}=",parent_value)
100
- end
151
+ # Define a getter for the sub-property that will traverse the
152
+ # parent properties, finally returning the filtered value
101
153
 
102
- else
154
+ define_method method_name do
155
+ raw_value = (parents + [name]).inject(self) {|current,method| current.send(method) }
156
+ property_class.new(self,options).get raw_value
157
+ end
103
158
 
104
- define_method name do
105
- raw_value = properties[name]
106
- property_class.new(self,options).get raw_value
107
- end
159
+ # Define a setter for the sub-property that will find the parent
160
+ # value and set itself on that with the filtered value. The parent
161
+ # is then set.
162
+ #
163
+ # @TODO: If getters return dups and not instances of the original object then a very
164
+ # deep setter will not be valid.
165
+ #
166
+ define_method "#{method_name}=" do |value|
167
+ parent_value = parents.inject(self) {|current,method| current.send(method) }
108
168
 
109
- define_method "#{name}=" do |value|
110
- prepared_value = property_class.new(self,options).set(value)
111
- properties[name] = prepared_value
112
- end
169
+ prepared_value = property_class.new(self,options,&block).set(value)
170
+ parent_value.send("#{name}=",prepared_value)
113
171
 
172
+ send("#{parents.last}=",parent_value)
114
173
  end
115
-
116
174
  end
117
175
 
176
+
118
177
  def properties
119
178
  @properties ||= {}
120
179
  end
@@ -122,14 +181,6 @@ module Metro
122
181
  property :model, type: :text
123
182
  property :name, type: :text
124
183
 
125
- #
126
- # This is called after every {#update} and when the OS wants the window to
127
- # repaint itself.
128
- #
129
- # @note This method should be implemented in the Model subclass.
130
- #
131
- def draw ; end
132
-
133
184
  #
134
185
  # The window that this model that this window is currently being
135
186
  # displayed.
@@ -153,14 +204,6 @@ module Metro
153
204
 
154
205
  include KeyValueCoding
155
206
 
156
- #
157
- # This is an entry point for customization. As the model's {#initialize}
158
- # method performs may perform some initialization that may be necessary.
159
- #
160
- # @note This method should be implemented in the Model subclass.
161
- #
162
- def after_initialize ; end
163
-
164
207
  #
165
208
  # Generate a custom notification event with the given name.
166
209
  #
@@ -253,19 +296,12 @@ module Metro
253
296
  #
254
297
  # Captures all classes that subclass Model.
255
298
  #
256
- # @see #self.scenes
299
+ # @see #self.models_hash
257
300
  #
258
- def self.inherited(base)
259
- models << base.to_s
260
- end
261
-
262
- #
263
- # All subclasses of Model, this should be all the defined model within the game.
264
- #
265
- # @return an Array of Scene subclasses
266
- #
267
- def self.models
268
- @models ||= []
301
+ def self.inherited(model)
302
+ models_hash[model.to_s] = model.to_s
303
+ models_hash[model.to_s.downcase] = model.to_s
304
+ models_hash[model.to_s.underscore] = model.to_s
269
305
  end
270
306
 
271
307
  #
@@ -273,20 +309,11 @@ module Metro
273
309
  #
274
310
  # @return the Model class given the specified model name.
275
311
  def self.model(name)
276
- @models_hash ||= begin
277
-
278
- hash = HashWithIndifferentAccess.new("Metro::Models::Generic")
279
-
280
- models.each do |model|
281
- hash[model.to_s] = model
282
- hash[model.downcase] = model
283
- hash[model.to_s.underscore] = model
284
- end
285
-
286
- hash
287
- end
312
+ models_hash[name]
313
+ end
288
314
 
289
- @models_hash[name]
315
+ def self.models_hash
316
+ @models_hash ||= HashWithIndifferentAccess.new("Metro::Models::Generic")
290
317
  end
291
318
 
292
319
  end
@@ -298,3 +325,4 @@ require_relative 'models/menu'
298
325
  require_relative 'models/image'
299
326
  require_relative 'models/rectangle'
300
327
  require_relative 'models/grid_drawer'
328
+ require_relative 'models/song'