metro 0.1.4 → 0.1.5

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 (42) hide show
  1. data/changelog.md +9 -0
  2. data/lib/commands/generate_model.rb +2 -2
  3. data/lib/commands/generate_scene.rb +2 -2
  4. data/lib/commands/generate_view.rb +1 -1
  5. data/lib/gosu_ext/color.rb +16 -2
  6. data/lib/metro.rb +50 -10
  7. data/lib/metro/events/event_data.rb +61 -0
  8. data/lib/metro/events/event_relay.rb +10 -3
  9. data/lib/metro/events/hit_list.rb +77 -0
  10. data/lib/metro/models/draws.rb +6 -1
  11. data/lib/metro/models/grid_drawer.rb +57 -0
  12. data/lib/metro/models/image.rb +18 -2
  13. data/lib/metro/models/label.rb +22 -6
  14. data/lib/metro/models/menu.rb +20 -0
  15. data/lib/metro/models/model.rb +47 -6
  16. data/lib/metro/models/model_factory.rb +2 -2
  17. data/lib/metro/models/rectangle_bounds.rb +28 -0
  18. data/lib/metro/scene.rb +46 -11
  19. data/lib/metro/scenes.rb +10 -9
  20. data/lib/metro/transitions/edit_transition_scene.rb +73 -0
  21. data/lib/metro/transitions/scene_transitions.rb +8 -2
  22. data/lib/metro/transitions/transition_scene.rb +2 -1
  23. data/lib/metro/version.rb +1 -1
  24. data/lib/metro/views/json_view.rb +60 -0
  25. data/lib/metro/{scene_view → views}/no_view.rb +12 -4
  26. data/lib/metro/views/parsers.rb +26 -0
  27. data/lib/metro/views/scene_view.rb +107 -0
  28. data/lib/metro/views/view.rb +125 -0
  29. data/lib/metro/views/writers.rb +28 -0
  30. data/lib/metro/views/yaml_view.rb +94 -0
  31. data/lib/metro/window.rb +19 -0
  32. data/metro.gemspec +1 -0
  33. data/spec/core_ext/string_spec.rb +13 -13
  34. data/spec/metro/scene_spec.rb +15 -0
  35. data/spec/metro/scene_views/json_view_spec.rb +27 -0
  36. data/spec/metro/scene_views/yaml_view_spec.rb +1 -1
  37. data/spec/metro/views/view_spec.rb +53 -0
  38. metadata +41 -11
  39. data/lib/core_ext/string.rb +0 -15
  40. data/lib/metro/scene_view/json_view.rb +0 -41
  41. data/lib/metro/scene_view/scene_view.rb +0 -83
  42. data/lib/metro/scene_view/yaml_view.rb +0 -45
data/changelog.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Metro
2
2
 
3
+ ## 0.1.5 / 2012-11-01
4
+
5
+ * Metro.reload! will reload all game classes
6
+ * Scenes can now an editable state where the position of items can be
7
+ updated and saved.
8
+ * Event blocks can optionally receive an event object. The event object
9
+ includes modifier key information.
10
+
11
+
3
12
  ## 0.1.4 / 2012-10-28
4
13
 
5
14
  * Removed dependency on the sender gem so metro is playable on Windows
@@ -5,11 +5,11 @@ module Metro
5
5
  no_tasks do
6
6
 
7
7
  def model_filename
8
- name.snake_case
8
+ name.underscore
9
9
  end
10
10
 
11
11
  def model_name
12
- name.camel_case
12
+ name.classify
13
13
  end
14
14
 
15
15
  end
@@ -6,12 +6,12 @@ module Metro
6
6
 
7
7
  def scene_filename
8
8
  scene_name = name.gsub(/_?Scene$/i,'')
9
- "#{scene_name.snake_case}_scene"
9
+ "#{scene_name.underscore}_scene"
10
10
  end
11
11
 
12
12
  def scene_class_name
13
13
  scene_name = name.gsub(/_?Scene$/i,'')
14
- "#{scene_name.camel_case}Scene"
14
+ "#{scene_name.classify}Scene"
15
15
  end
16
16
 
17
17
  end
@@ -6,7 +6,7 @@ module Metro
6
6
 
7
7
  def view_filename
8
8
  view_name = name.to_s.gsub(/_?Scene$/i,'')
9
- view_name.snake_case
9
+ view_name.underscore
10
10
  end
11
11
 
12
12
  end
@@ -22,9 +22,9 @@ class Gosu::Color
22
22
  end
23
23
 
24
24
  def self.parse_string(value)
25
- parse_hex(value) || parse_rgb(value) || parse_rgba(value) || [ 255, 255, 255, 255 ]
25
+ parse_hex(value) || parse_rgb(value) || parse_rgba(value) || parse_gosu_color_string(value) || [ 255, 255, 255, 255 ]
26
26
  end
27
-
27
+
28
28
  def self.parse_rgba(rgba)
29
29
  if rgba =~ /rgba\(([\d]{1,3}),([\d]{1,3}),([\d]{1,3}),(\d(?:\.\d)?)\)/
30
30
  [ (255 * $4.to_f).floor.to_i, $1.to_i, $2.to_i, $3.to_i ]
@@ -45,4 +45,18 @@ class Gosu::Color
45
45
  end
46
46
  end
47
47
 
48
+ def self.parse_gosu_color_string(string)
49
+ if string =~ /\(ARGB: ([\d]{1,3})\/([\d]{1,3})\/([\d]{1,3})\/([\d]{1,3})\)/
50
+ [ $1.to_i, $2.to_i, $3.to_i, $4.to_i ]
51
+ end
52
+ end
53
+
54
+ def to_s
55
+ "rgba(#{red},#{green},#{blue},#{alpha / 255.to_f})"
56
+ end
57
+
58
+ def to_json(*params)
59
+ to_s.to_json
60
+ end
61
+
48
62
  end
data/lib/metro.rb CHANGED
@@ -2,9 +2,10 @@ require 'gosu'
2
2
  require 'gosu_ext/color'
3
3
  require 'gosu_ext/gosu_constants'
4
4
  require 'i18n'
5
+ require 'active_support'
6
+ require 'active_support/dependencies'
7
+ require 'active_support/inflector'
5
8
 
6
-
7
- require 'core_ext/string'
8
9
  require 'core_ext/numeric'
9
10
  require 'logger'
10
11
  require 'erb'
@@ -49,33 +50,72 @@ module Metro
49
50
  # the value uses the default filename.
50
51
  #
51
52
  def run(filename=default_game_filename)
52
- load_game_files
53
- change_into_game_directory(filename)
53
+ move_to_game_directory!(filename)
54
+ load_game_files!
54
55
  load_game_configuration(filename)
55
56
  configure_controls!
56
57
  start_game
57
58
  end
58
59
 
60
+ def load_game_files!
61
+ prepare_watcher!
62
+ load_game_files
63
+ execute_watcher!
64
+ end
65
+
66
+ alias_method :reload!, :load_game_files!
67
+
59
68
  private
60
69
 
70
+ def move_to_game_directory!(filename)
71
+ game_directory = File.dirname(filename)
72
+ Dir.chdir game_directory
73
+ end
74
+
75
+ #
76
+ # The watcher will keep track of all the constants that were added to the Object
77
+ # Namespace after the start of the execution of the game. This will allow for only
78
+ # those objects to be reloaded.
79
+ #
80
+ def watcher
81
+ @watcher ||= ActiveSupport::Dependencies::WatchStack.new
82
+ end
83
+
84
+ #
85
+ # The watcher should watch the Object Namespace for any changes. Any constants
86
+ # that are added will be tracked from this point forward.
87
+ #
88
+ def prepare_watcher!
89
+ ActiveSupport::Dependencies.clear
90
+ watcher.watch_namespaces([ Object ])
91
+ end
92
+
93
+ #
94
+ # The watcher will now mark all the constants that it has watched being loaded
95
+ # as unloadable. Doing so exhausts the list of constants found so the watcher
96
+ # will be empty.
97
+ #
98
+ # @note an exception is raised if the watcher is not prepared every time this
99
+ # is called.
100
+ #
101
+ def execute_watcher!
102
+ watcher.new_constants.each { |constant| unloadable constant }
103
+ end
104
+
61
105
  def load_game_files
62
106
  $LOAD_PATH.unshift(Dir.pwd) unless $LOAD_PATH.include?(Dir.pwd)
63
107
  load_paths 'models', 'scenes', 'lib'
64
108
  end
65
-
66
- def change_into_game_directory(filename)
67
- game_directory = File.dirname(filename)
68
- Dir.chdir game_directory
69
- end
70
109
 
71
110
  def load_paths(*paths)
72
111
  paths.flatten.compact.each {|path| load_path path }
73
112
  end
74
113
 
75
114
  def load_path(path)
76
- Dir["#{path}/**/*.rb"].each {|model| require model }
115
+ Dir["#{path}/**/*.rb"].each {|model| require_or_load model }
77
116
  end
78
117
 
118
+
79
119
  def load_game_configuration(filename)
80
120
  gamefile = File.basename(filename)
81
121
  game_files_exist!(gamefile)
@@ -0,0 +1,61 @@
1
+ module Metro
2
+ class EventData
3
+
4
+ attr_reader :mouse_x, :mouse_y, :created_at
5
+
6
+ def initialize(window)
7
+ @created_at = Time.now
8
+ @mouse_x = window.mouse_x
9
+ @mouse_y = window.mouse_y
10
+
11
+ capture_modifier_keys(window)
12
+ end
13
+
14
+ def modifier_keys
15
+ @modifier_keys ||= {}
16
+ end
17
+
18
+ def capture_modifier_keys(window)
19
+ self.class.modifier_key_list.each do |key|
20
+ modifier_keys[key] = window.button_down?(key)
21
+ end
22
+ end
23
+
24
+ #
25
+ # TODO: This attempt to reduce duplication is brittle and will likely end in heartache.
26
+ #
27
+
28
+ def self.modifier_key_list_names
29
+ @modifier_key_list_names ||= %w[ KbLeftControl KbRightControl
30
+ KbLeftAlt KbRightAlt
31
+ KbLeftMeta KbRightMeta
32
+ KbLeftShift KbRightShift ]
33
+ end
34
+
35
+ def self.modifier_key_list
36
+ @modifier_key_list ||= modifier_key_list_names.map {|key| "Gosu::#{key}".constantize }
37
+ end
38
+
39
+ #
40
+ # Generate methods for each left and right modifier key
41
+ #
42
+
43
+ modifier_key_list_names.each_with_index do |key_name,index|
44
+ define_method "#{key_name.gsub(/^Kb/,'').underscore}?" do
45
+ modifier_keys[self.class.modifier_key_list[index]]
46
+ end
47
+ end
48
+
49
+ #
50
+ # Define generic modifier keys that is not concerned with whether it
51
+ # was the left key or the right key.
52
+ #
53
+
54
+ [ :control?, :alt?, :meta?, :shift? ].each do |generic|
55
+ define_method generic do
56
+ send("left_#{generic}") or send("right_#{generic}")
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -1,3 +1,5 @@
1
+ require_relative 'event_data'
2
+
1
3
  module Metro
2
4
 
3
5
  #
@@ -253,7 +255,7 @@ module Metro
253
255
  # has been triggered.
254
256
  #
255
257
  def fire_button_up(id)
256
- target.instance_eval( &up_action(id) )
258
+ execute_block_for_target( &up_action(id) )
257
259
  end
258
260
 
259
261
  #
@@ -261,7 +263,7 @@ module Metro
261
263
  # event has been triggered.
262
264
  #
263
265
  def fire_button_down(id)
264
- target.instance_eval( &down_action(id) )
266
+ execute_block_for_target( &down_action(id) )
265
267
  end
266
268
 
267
269
  #
@@ -271,9 +273,14 @@ module Metro
271
273
  #
272
274
  def fire_events_for_held_buttons
273
275
  held_actions.each do |key,action|
274
- target.instance_eval(&action) if window and window.button_down?(key)
276
+ execute_block_for_target(&action) if window and window.button_down?(key)
275
277
  end
276
278
  end
279
+
280
+ def execute_block_for_target(&block)
281
+ event_data = EventData.new(window)
282
+ target.instance_exec(event_data,&block)
283
+ end
277
284
 
278
285
  # @return a block of code that is mapped for the 'button_up' id or a block that will attempt to call out
279
286
  # to the action missing method.
@@ -0,0 +1,77 @@
1
+ module Metro
2
+
3
+ #
4
+ # The HitList is an object that maintains when an object is touched/clicked
5
+ # and then moved and finally released. The object attempts to work through
6
+ # the process:
7
+ #
8
+ # hit_list.hit(first_event)
9
+ # hit_list.update(next_event)
10
+ # hit_list.update(next_event)
11
+ # hit_list.release(last_event)
12
+ #
13
+ #
14
+ # @see EditTransitionScene
15
+ #
16
+ class HitList
17
+
18
+ def initialize(drawers)
19
+ @drawers = drawers
20
+ end
21
+
22
+ attr_reader :drawers
23
+
24
+ def hit(event)
25
+ add drawers_at(event.mouse_x,event.mouse_y)
26
+ save_event event
27
+ end
28
+
29
+ def update(event)
30
+ offset_x, offset_y = offset_from_last_event(event)
31
+
32
+ list.each { |d| d.offset(offset_x,offset_y) }
33
+
34
+ save_event event
35
+ end
36
+
37
+ def release(event)
38
+ offset_x, offset_y = offset_from_last_event(event)
39
+
40
+ list.each { |d| d.offset(offset_x,offset_y) }
41
+
42
+ save_event event
43
+ clear
44
+ end
45
+
46
+ def drawers_at(x,y)
47
+ hit_drawers = drawers.find_all { |drawer| drawer.contains?(x,y) }
48
+
49
+ # assumed that we only want one item
50
+ top_drawer = hit_drawers.inject(hit_drawers.first) {|top,drawer| drawer.z_order > top.z_order ? drawer : top }
51
+ [ top_drawer ].compact
52
+ end
53
+
54
+ def offset_from_last_event(event)
55
+ [ (event.mouse_x - @last_event.mouse_x).to_i, (event.mouse_y - @last_event.mouse_y).to_i ]
56
+ end
57
+
58
+ def save_event(event)
59
+ @first_event = event unless @first_event
60
+ @last_event = event
61
+ end
62
+
63
+ def list
64
+ @list ||= []
65
+ end
66
+
67
+ def add(hits)
68
+ Array(hits).each {|hit| list.push hit }
69
+ end
70
+
71
+ def clear
72
+ list.clear
73
+ @first_event = nil
74
+ end
75
+
76
+ end
77
+ end
@@ -30,7 +30,12 @@ module Metro
30
30
  # end
31
31
  #
32
32
  def draw(actor_name,options = {})
33
- scene_actor = ModelFactory.new actor_name, options
33
+
34
+ view_content_for_actor = view.content[actor_name.to_s]
35
+ actor_data = view_content_for_actor.merge(options)
36
+ actor_data[:name] = actor_name
37
+
38
+ scene_actor = ModelFactory.new actor_name, actor_data
34
39
 
35
40
  define_method actor_name do
36
41
  instance_variable_get("@#{actor_name}")
@@ -0,0 +1,57 @@
1
+ module Metro
2
+ module Models
3
+
4
+ class GridDrawer < Model
5
+
6
+ attr_writer :color, :spacing, :height, :width
7
+
8
+ def name
9
+ self.class.name
10
+ end
11
+
12
+ def spacing
13
+ @spacing ||= 10
14
+ end
15
+
16
+ def height
17
+ @height || Game.height
18
+ end
19
+
20
+ def width
21
+ @width || Game.width
22
+ end
23
+
24
+ def color
25
+ @color ||= Gosu::Color.new("rgba(255,255,255,0.1)")
26
+ end
27
+
28
+ def saveable?
29
+ false
30
+ end
31
+
32
+ def contains?(x,y)
33
+ false
34
+ end
35
+
36
+ def draw
37
+ draw_horizontal_lines
38
+ draw_vertical_lines
39
+ end
40
+
41
+ def draw_vertical_lines
42
+ xs = (width / spacing + 1).times.map {|segment| segment * spacing }
43
+ xs.each do |x|
44
+ window.draw_line(x, 1, color, x, height, color, 0, :additive)
45
+ end
46
+ end
47
+
48
+ def draw_horizontal_lines
49
+ ys = (height / spacing + 1).times.map {|segment| segment * spacing }
50
+ ys.each do |y|
51
+ window.draw_line(1, y, color, width, y, color, 0, :additive)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -3,10 +3,10 @@ module Metro
3
3
 
4
4
  #
5
5
  # Draws an Image
6
- #
6
+ #
7
7
  # @example Using the Image in a view file
8
8
  # model: "metro::models::image"
9
- #
9
+ #
10
10
  class Image < Model
11
11
 
12
12
  attr_accessor :x, :y, :angle, :center_x, :center_y, :x_factor, :y_factor, :z_order
@@ -25,6 +25,22 @@ module Metro
25
25
  @image ||= Gosu::Image.new(window,asset_path(path))
26
26
  end
27
27
 
28
+ def contains?(x,y)
29
+ bounds.contains?(x,y)
30
+ end
31
+
32
+ def bounds
33
+ Bounds.new x - (width * center_x), y - (height * center_y), x + (width * center_x), y + (height * center_y)
34
+ end
35
+
36
+ def width
37
+ image.width
38
+ end
39
+
40
+ def height
41
+ image.height
42
+ end
43
+
28
44
  def draw
29
45
  image.draw_rot x, y, z_order,
30
46
  angle.to_f,