metro 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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,