metro 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/README.md +19 -0
  2. data/changelog.md +8 -0
  3. data/lib/assets/missing_animation.png +0 -0
  4. data/lib/commands/generate_model.rb +2 -2
  5. data/lib/commands/generate_scene.rb +12 -2
  6. data/lib/commands/generate_view.rb +1 -1
  7. data/lib/gosu_ext/color.rb +1 -1
  8. data/lib/gosu_ext/image.rb +5 -0
  9. data/lib/metro.rb +18 -5
  10. data/lib/metro/animation/animation.rb +31 -0
  11. data/lib/metro/asset_path.rb +9 -0
  12. data/lib/metro/events/event_dictionary.rb +53 -0
  13. data/lib/metro/events/event_factory.rb +5 -3
  14. data/lib/metro/events/has_events.rb +5 -6
  15. data/lib/metro/game.rb +1 -1
  16. data/lib/metro/models/dimensions.rb +21 -0
  17. data/lib/metro/models/model.rb +149 -52
  18. data/lib/metro/models/model_factory.rb +4 -5
  19. data/lib/metro/models/{generic.rb → models/generic.rb} +0 -0
  20. data/lib/metro/models/{grid_drawer.rb → models/grid_drawer.rb} +4 -9
  21. data/lib/metro/models/{image.rb → models/image.rb} +11 -14
  22. data/lib/metro/models/models/label.rb +44 -0
  23. data/lib/metro/models/{menu.rb → models/menu.rb} +23 -18
  24. data/lib/metro/models/{rectangle.rb → models/rectangle.rb} +6 -5
  25. data/lib/metro/models/point.rb +23 -0
  26. data/lib/metro/models/properties/angle.rb +43 -0
  27. data/lib/metro/models/properties/animation.rb +143 -0
  28. data/lib/metro/models/properties/color.rb +113 -0
  29. data/lib/metro/models/properties/dimensions.rb +66 -0
  30. data/lib/metro/models/properties/font.rb +155 -0
  31. data/lib/metro/models/properties/image.rb +101 -0
  32. data/lib/metro/models/properties/numeric.rb +29 -0
  33. data/lib/metro/models/properties/position.rb +84 -0
  34. data/lib/metro/models/properties/property.rb +111 -0
  35. data/lib/metro/models/properties/scale.rb +89 -0
  36. data/lib/metro/models/properties/text.rb +66 -0
  37. data/lib/metro/models/properties/velocity.rb +80 -0
  38. data/lib/metro/models/scale.rb +21 -0
  39. data/lib/metro/scene.rb +19 -1
  40. data/lib/metro/scenes.rb +91 -31
  41. data/lib/metro/transitions/scene_transitions.rb +8 -0
  42. data/lib/metro/version.rb +1 -1
  43. data/lib/metro/views/view.rb +9 -1
  44. data/lib/templates/game/metro.tt +1 -1
  45. data/lib/templates/game/models/game_model.rb +3 -0
  46. data/lib/templates/game/scenes/brand_scene.rb +1 -1
  47. data/lib/templates/game/scenes/brand_to_title_scene.rb +1 -1
  48. data/lib/templates/game/scenes/game_scene.rb +19 -0
  49. data/lib/templates/game/scenes/title_scene.rb +1 -1
  50. data/lib/templates/game/views/brand_to_title.yaml +2 -2
  51. data/lib/templates/game/views/title.yaml +3 -3
  52. data/lib/templates/{model.rb.erb → model.rb.tt} +1 -1
  53. data/lib/templates/{scene.rb.erb → scene.rb.tt} +1 -1
  54. data/lib/templates/view.yaml.tt +6 -0
  55. data/spec/metro/models/models/label_spec.rb +110 -0
  56. data/spec/metro/models/properties/color_spec.rb +85 -0
  57. data/spec/metro/models/properties/font_spec.rb +129 -0
  58. data/spec/metro/models/properties/numeric_property_spec.rb +46 -0
  59. data/spec/metro/models/properties/position_property_spec.rb +90 -0
  60. data/spec/metro/scenes_spec.rb +77 -0
  61. metadata +50 -16
  62. data/lib/metro/models/label.rb +0 -63
  63. data/lib/templates/view.yaml.erb +0 -32
data/README.md CHANGED
@@ -53,3 +53,22 @@ $ metro generate scene first
53
53
  ```
54
54
 
55
55
  This should generate a scene in the scenes directory. The scene file contains a lot of examples of how to draw, animate and have your scene listen to events.
56
+
57
+
58
+ ### Resources
59
+
60
+ #### Art
61
+
62
+ * [Lost Garden](http://www.lostgarden.com/2007/05/dancs-miraculously-flexible-game.html)
63
+
64
+ #### Books
65
+
66
+ * [Rules of Play](http://www.amazon.com/dp/0262240459)
67
+ * [Game Programming Gems 8](http://www.amazon.com/dp/1584507020)
68
+ * [Game Feel](http://www.amazon.com/dp/0123743281)
69
+ * [Game Coding Complete](http://www.amazon.com/dp/1584506806)
70
+ * [Game Design Workshop](http://www.amazon.com/dp/0240809742)
71
+ * [Challenges For Game Designers](http://www.amazon.com/dp/158450580X)
72
+ * [The Art of Game Design: A book of lenses](http://www.amazon.com/dp/0123694965)
73
+ * [A Theory of Fun](http://www.theoryoffun.com)
74
+ * [Andrew Rollings and Ernest Adams on Game Design](http://www.amazon.com/dp/1592730019)
@@ -1,5 +1,13 @@
1
1
  # Metro
2
2
 
3
+ ## 0.1.6 / 2012-11-07
4
+
5
+ * Events are shared from superclasses to subclases.
6
+ * Templates updated to use GameScene and GameModel for each game.
7
+ * Models are automatically added to the update loop
8
+ * Model properties now make it easier to store/retrieve various
9
+ common numeric, position font, image, and animation properties.
10
+
3
11
  ## 0.1.5 / 2012-11-01
4
12
 
5
13
  * Metro.reload! will reload all game classes
@@ -9,7 +9,7 @@ module Metro
9
9
  end
10
10
 
11
11
  def model_name
12
- name.classify
12
+ name.camelize
13
13
  end
14
14
 
15
15
  end
@@ -17,7 +17,7 @@ module Metro
17
17
  argument :name
18
18
 
19
19
  def create_model_file
20
- template "model.rb.erb", "models/#{model_filename}.rb"
20
+ template "model.rb.tt", "models/#{model_filename}.rb"
21
21
  end
22
22
 
23
23
  end
@@ -11,7 +11,12 @@ module Metro
11
11
 
12
12
  def scene_class_name
13
13
  scene_name = name.gsub(/_?Scene$/i,'')
14
- "#{scene_name.classify}Scene"
14
+ "#{scene_name.camelize}Scene"
15
+ end
16
+
17
+ def view_filename
18
+ view_name = name.to_s.gsub(/_?Scene$/i,'')
19
+ view_name.underscore
15
20
  end
16
21
 
17
22
  end
@@ -19,8 +24,13 @@ module Metro
19
24
  argument :name
20
25
 
21
26
  def create_scene_file
22
- template "scene.rb.erb", "scenes/#{scene_filename}.rb"
27
+ template "scene.rb.tt", "scenes/#{scene_filename}.rb"
23
28
  end
29
+
30
+ def create_view_file
31
+ template "view.yaml.tt", "views/#{view_filename}.yaml"
32
+ end
33
+
24
34
  end
25
35
 
26
36
  end
@@ -14,7 +14,7 @@ module Metro
14
14
  argument :name
15
15
 
16
16
  def create_view_file
17
- template "view.yaml.erb", "views/#{view_filename}.yaml"
17
+ template "view.yaml.tt", "views/#{view_filename}.yaml"
18
18
  end
19
19
  end
20
20
 
@@ -26,7 +26,7 @@ class Gosu::Color
26
26
  end
27
27
 
28
28
  def self.parse_rgba(rgba)
29
- if rgba =~ /rgba\(([\d]{1,3}),([\d]{1,3}),([\d]{1,3}),(\d(?:\.\d)?)\)/
29
+ if rgba =~ /rgba\(([\d]{1,3}(?:\.\d*)?),([\d]{1,3}(?:\.\d*)?),([\d]{1,3}(?:\.\d*)?),(\d(?:\.\d*)?)\)/
30
30
  [ (255 * $4.to_f).floor.to_i, $1.to_i, $2.to_i, $3.to_i ]
31
31
  end
32
32
  end
@@ -0,0 +1,5 @@
1
+ class Gosu::Image
2
+
3
+ attr_accessor :path, :tileable
4
+
5
+ end
@@ -1,10 +1,13 @@
1
1
  require 'gosu'
2
2
  require 'gosu_ext/color'
3
+ require 'gosu_ext/image'
3
4
  require 'gosu_ext/gosu_constants'
4
5
  require 'i18n'
5
6
  require 'active_support'
6
7
  require 'active_support/dependencies'
7
8
  require 'active_support/inflector'
9
+ require 'active_support/core_ext/hash'
10
+ require 'active_support/hash_with_indifferent_access'
8
11
 
9
12
  require 'core_ext/numeric'
10
13
  require 'logger'
@@ -12,6 +15,9 @@ require 'erb'
12
15
 
13
16
  require 'locale/locale'
14
17
  require 'metro/asset_path'
18
+ require 'metro/models/point'
19
+ require 'metro/models/scale'
20
+ require 'metro/models/dimensions'
15
21
  require 'metro/logging'
16
22
  require 'metro/version'
17
23
  require 'metro/template_message'
@@ -20,7 +26,6 @@ require 'metro/game'
20
26
  require 'metro/scene'
21
27
  require 'metro/scenes'
22
28
  require 'metro/models/model'
23
- require 'metro/models/generic'
24
29
 
25
30
  require_relative 'metro/missing_scene'
26
31
 
@@ -41,6 +46,10 @@ module Metro
41
46
  'metro'
42
47
  end
43
48
 
49
+ def asset_dir
50
+ File.join File.dirname(__FILE__), "assets"
51
+ end
52
+
44
53
  #
45
54
  # Run will load the contents of the game contents and game files
46
55
  # within the current working directory and start the game. By default
@@ -58,6 +67,7 @@ module Metro
58
67
  end
59
68
 
60
69
  def load_game_files!
70
+ EventDictionary.reset!
61
71
  prepare_watcher!
62
72
  load_game_files
63
73
  execute_watcher!
@@ -104,18 +114,21 @@ module Metro
104
114
 
105
115
  def load_game_files
106
116
  $LOAD_PATH.unshift(Dir.pwd) unless $LOAD_PATH.include?(Dir.pwd)
107
- load_paths 'models', 'scenes', 'lib'
117
+ load_paths 'lib'
118
+ load_path 'scenes', prioritize: 'game_scene.rb'
119
+ load_path 'models', prioritize: 'game_model.rb'
108
120
  end
109
121
 
110
122
  def load_paths(*paths)
111
123
  paths.flatten.compact.each {|path| load_path path }
112
124
  end
113
125
 
114
- def load_path(path)
115
- Dir["#{path}/**/*.rb"].each {|model| require_or_load model }
126
+ def load_path(path,options = {})
127
+ files = Dir["#{path}/**/*.rb"]
128
+ files.sort! {|file| File.basename(file) == options[:prioritize] ? -1 : 1 }
129
+ files.each {|model| require_or_load model }
116
130
  end
117
131
 
118
-
119
132
  def load_game_configuration(filename)
120
133
  gamefile = File.basename(filename)
121
134
  game_files_exist!(gamefile)
@@ -0,0 +1,31 @@
1
+ module Metro
2
+
3
+ #
4
+ # The animation is an wrapper object for an array of Gosu::Images that also contains
5
+ # the additional information on the path, height, width, and tileability.
6
+ #
7
+ class Animation
8
+
9
+ attr_accessor :images, :path, :height, :width, :tileable
10
+
11
+ def initialize(params = {})
12
+ @images = Array(params[:images])
13
+ @path = params[:path]
14
+ @height = params[:height]
15
+ @width = params[:width]
16
+ @tileable = params[:tileable]
17
+ end
18
+
19
+ def to_hash
20
+ { path: path, width: width.to_i, height: height.to_i, tileable: !!tileable }
21
+ end
22
+
23
+ #
24
+ # @return a Gosu::Image to be displayed in a animation sequence.
25
+ #
26
+ def image
27
+ images[Gosu::milliseconds / 100 % images.size]
28
+ end
29
+
30
+ end
31
+ end
@@ -20,3 +20,12 @@
20
20
  def asset_path(name)
21
21
  File.join Dir.pwd, "assets", name
22
22
  end
23
+
24
+
25
+ #
26
+ # The metro_asset_path is a helper which will generate a filepath based on the directory
27
+ # of the metro library. This is used to retrieve assets internally for missing images.
28
+ #
29
+ def metro_asset_path(name)
30
+ File.join Metro.asset_dir, name
31
+ end
@@ -0,0 +1,53 @@
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 ||= Hash.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
+ log.debug "Retrieved (#{found_events.count}) events for [#{list.join(",")}]"
32
+ found_events
33
+ end
34
+
35
+ #
36
+ # Return the events for the specified target
37
+ #
38
+ def events_for_target(scene_name)
39
+ events[scene_name] ||= []
40
+ end
41
+
42
+ #
43
+ # When the game is reset the event dictionary needs to flush out all of the events that it
44
+ # has loaded as the game files will be reloaded. All metro related components will not
45
+ # be removed as those files are not reloaded when the game is reloaded.
46
+ #
47
+ def reset!
48
+ events.delete_if { |name,events| ! name.start_with? "metro/" }
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -2,11 +2,13 @@ module Metro
2
2
 
3
3
  class EventFactory
4
4
 
5
- attr_reader :event, :buttons, :block
5
+ attr_reader :event, :args, :block
6
6
 
7
- def initialize(event,buttons = [],&block)
7
+ alias_method :buttons, :args
8
+
9
+ def initialize(event,args=[],&block)
8
10
  @event = event
9
- @buttons = buttons
11
+ @args = args
10
12
  @block = block
11
13
  end
12
14
 
@@ -1,4 +1,4 @@
1
- require_relative 'event_factory'
1
+ require_relative 'event_dictionary'
2
2
 
3
3
  module Metro
4
4
 
@@ -90,16 +90,15 @@ module Metro
90
90
  # This example uses a block instead of a method name but it is absolultey the same
91
91
  # as the last example.
92
92
  #
93
- def event(event_type,*buttons,&block)
94
- scene_event = EventFactory.new event_type, buttons, &block
95
- events.push scene_event
93
+ def event(event_type,*args,&block)
94
+ EventDictionary.add target: metro_name, type: event_type, args: args, block: block
96
95
  end
97
96
 
98
97
  #
99
- # @return a list of all the EventFactories defined for the scene
98
+ # @return a list of all the EventFactories defined for this event holding object
100
99
  #
101
100
  def events
102
- @events ||= []
101
+ EventDictionary.events_for_targets(hierarchy)
103
102
  end
104
103
 
105
104
  end
@@ -27,7 +27,7 @@ module Metro
27
27
  end
28
28
 
29
29
  def center
30
- [ width / 2 , height / 2 ]
30
+ Point.at width / 2 , height / 2
31
31
  end
32
32
 
33
33
  def fullscreen?
@@ -0,0 +1,21 @@
1
+ module Metro
2
+ class Dimensions < Struct.new(:width,:height)
3
+
4
+ def self.none
5
+ new 0.0, 0.0
6
+ end
7
+
8
+ def self.of(width,height)
9
+ new width.to_f, height.to_f
10
+ end
11
+
12
+ def self.parse(string)
13
+ to *string.split(",",2).map(&:to_f)
14
+ end
15
+
16
+ def to_s
17
+ "#{width},#{height}"
18
+ end
19
+
20
+ end
21
+ end
@@ -1,5 +1,6 @@
1
1
  require_relative 'key_value_coding'
2
2
  require_relative 'rectangle_bounds'
3
+ require_relative 'properties/property'
3
4
 
4
5
  module Metro
5
6
 
@@ -14,6 +15,120 @@ module Metro
14
15
  #
15
16
  class Model
16
17
 
18
+ #
19
+ # This is called every update interval while the actor is in the scene
20
+ #
21
+ # @note This method should be implemented in the Model subclass
22
+ #
23
+ def update ; end
24
+
25
+ #
26
+ # This is called after an update. A model normally is not removed after
27
+ # an update, however if the model responds true to #completed? then it
28
+ # will be removed.
29
+ #
30
+ # @note This method should be implemented in the Model sublclass if you
31
+ # are interested in having the model be removed from the scene.
32
+ #
33
+ def completed? ; false ; end
34
+
35
+
36
+ def self.property(name,options={})
37
+
38
+ # Use the name as the property type if one has not been provided.
39
+
40
+ property_type = options[:type] || name
41
+
42
+ property_class = Property.property(property_type)
43
+
44
+ # When the name does not match the property type then we want to force
45
+ # the prefixing to be on for our sub-properties. This is to make sure
46
+ # that when people define multiple fonts and colors that they do not
47
+ # overlap.
48
+
49
+ override_prefix = !(name == property_type)
50
+
51
+ # Define any properties defined on this property
52
+
53
+ property_class.defined_properties.each do |subproperty|
54
+ sub_options = { prefix: override_prefix }.merge(subproperty.options)
55
+ sub_options = sub_options.merge(parents: (Array(sub_options[:parents]) + [name]))
56
+
57
+ property subproperty.name, sub_options
58
+ end
59
+
60
+ # To ensure that our sub-properties are aware of the of their
61
+ # parent property for which they are dependent on we will need
62
+ # to know this information because we need to behave appropriately
63
+ # within the getter and setter blocks below.
64
+
65
+ is_a_dependency = true if options[:parents]
66
+
67
+ if is_a_dependency
68
+
69
+ parents = Array(options[:parents])
70
+
71
+ method_name = name
72
+
73
+ if options[:prefix]
74
+ method_name = (parents + [name]).join("_")
75
+ end
76
+
77
+ # Define a getter for the sub-property that will traverse the
78
+ # parent properties, finally returning the filtered value
79
+
80
+ define_method method_name do
81
+ raw_value = (parents + [name]).inject(self) {|current,method| current.send(method) }
82
+ property_class.new(self,options).get raw_value
83
+ end
84
+
85
+ # Define a setter for the sub-property that will find the parent
86
+ # value and set itself on that with the filtered value. The parent
87
+ # is then set.
88
+ #
89
+ # @TODO: If getters return dups and not instances of the original object then a very
90
+ # deep setter will not be valid.
91
+ #
92
+ define_method "#{method_name}=" do |value|
93
+ parent_value = parents.inject(self) {|current,method| current.send(method) }
94
+
95
+ prepared_value = property_class.new(self,options).set(value)
96
+ parent_value.send("#{name}=",prepared_value)
97
+
98
+ send("#{parents.last}=",parent_value)
99
+ end
100
+
101
+ else
102
+
103
+ define_method name do
104
+ raw_value = properties[name]
105
+ property_class.new(self,options).get raw_value
106
+ end
107
+
108
+ define_method "#{name}=" do |value|
109
+ prepared_value = property_class.new(self,options).set(value)
110
+ properties[name] = prepared_value
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+ def properties
118
+ @properties ||= {}
119
+ end
120
+
121
+ property :model, type: :text
122
+ property :name, type: :text
123
+
124
+ #
125
+ # This is called after every {#update} and when the OS wants the window to
126
+ # repaint itself.
127
+ #
128
+ # @note This method should be implemented in the Model subclass.
129
+ #
130
+ def draw ; end
131
+
17
132
  #
18
133
  # The window that this model that this window is currently being
19
134
  # displayed.
@@ -59,46 +174,6 @@ module Metro
59
174
  #
60
175
  include HasEvents
61
176
 
62
- #
63
- # Returns the color of the model. In most cases where color is a prominent
64
- # attribute (e.g. label) this will be the color. In the cases where color
65
- # is less promenint (e.g. image) this will likely be a color that can be
66
- # used to influence the drawing of it.
67
- #
68
- # @see #alpha
69
- #
70
- def color
71
- @color
72
- end
73
-
74
- #
75
- # Sets the color of the model.
76
- #
77
- # @param [String,Fixnum,Gosu::Color] value the new color to set.
78
- #
79
- def color=(value)
80
- @color = Gosu::Color.new(value)
81
- end
82
-
83
- #
84
- # @return the alpha value of the model's color. This is an integer value
85
- # between 0 and 255.
86
- #
87
- def alpha
88
- color.alpha
89
- end
90
-
91
- #
92
- # Sets the alpha of the model.
93
- #
94
- # @param [String,Fixnum] value the new value of the alpha level for the model.
95
- # This value should be between 0 and 255.
96
- #
97
- def alpha=(value)
98
- # TODO: coerce the value is between 0 and 255
99
- color.alpha = value.to_i
100
- end
101
-
102
177
  def saveable?
103
178
  true
104
179
  end
@@ -107,7 +182,7 @@ module Metro
107
182
  def contains?(x,y)
108
183
  false
109
184
  end
110
-
185
+
111
186
  # Belongs to positionable items only
112
187
  def offset(x,y)
113
188
  self.x += x
@@ -121,6 +196,7 @@ module Metro
121
196
  # method or done with care to ensure that functionality is preserved.
122
197
  #
123
198
  def initialize(options = {})
199
+ _load(options)
124
200
  after_initialize
125
201
  end
126
202
 
@@ -135,22 +211,29 @@ module Metro
135
211
  options = {} unless options
136
212
 
137
213
  options.each do |raw_key,value|
138
-
139
214
  key = raw_key.to_s.dup
140
215
  key = key.gsub(/-/,'_').underscore
141
216
 
217
+
142
218
  unless respond_to? key
143
- self.class.send :define_method, key do
144
- instance_variable_get("@#{key}")
219
+ log.warn "Unknown Property #{key}"
220
+ self.class.send(:define_method,key) do
221
+ properties[key]
145
222
  end
223
+ #raise "Do not know (#{key}) property"
146
224
  end
147
225
 
148
226
  unless respond_to? "#{key}="
149
- self.class.send :define_method, "#{key}=" do |value|
150
- instance_variable_set("@#{key}",value)
227
+ log.warn "Unknown Property #{key}="
228
+ self.class.send(:define_method,"#{key}=") do |value|
229
+ properties[key] = value
151
230
  end
231
+
232
+ # raise "Do not know (#{key}=) property"
152
233
  end
153
234
 
235
+
236
+
154
237
  _loaded_options.push key
155
238
  send "#{key}=", value
156
239
  end
@@ -194,6 +277,20 @@ module Metro
194
277
  { name => hash }
195
278
  end
196
279
 
280
+ #
281
+ # @return a common name that can be used through the system as a common identifier.
282
+ #
283
+ def self.metro_name
284
+ name.underscore
285
+ end
286
+
287
+ #
288
+ # @return an array of all ancestor models by name
289
+ #
290
+ def self.hierarchy
291
+ ancestors.find_all {|a| a.respond_to? :metro_name }.map(&:metro_name)
292
+ end
293
+
197
294
  #
198
295
  # Captures all classes that subclass Model.
199
296
  #
@@ -237,9 +334,9 @@ module Metro
237
334
  end
238
335
  end
239
336
 
240
- require_relative 'generic'
241
- require_relative 'label'
242
- require_relative 'menu'
243
- require_relative 'image'
244
- require_relative 'rectangle'
245
- require_relative 'grid_drawer'
337
+ require_relative 'models/generic'
338
+ require_relative 'models/label'
339
+ require_relative 'models/menu'
340
+ require_relative 'models/image'
341
+ require_relative 'models/rectangle'
342
+ require_relative 'models/grid_drawer'