metro 0.1.2 → 0.1.3

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 (49) hide show
  1. data/README.md +29 -13
  2. data/changelog.md +10 -0
  3. data/lib/core_ext/numeric.rb +59 -0
  4. data/lib/gosu_ext/gosu_constants.rb +53 -0
  5. data/lib/locale/en.yml +16 -0
  6. data/lib/locale/locale.rb +1 -0
  7. data/lib/metro.rb +30 -16
  8. data/lib/metro/animation/after_interval_factory.rb +12 -0
  9. data/lib/metro/animation/animation_factory.rb +3 -2
  10. data/lib/metro/animation/has_animations.rb +3 -3
  11. data/lib/metro/animation/implicit_animation.rb +33 -19
  12. data/lib/metro/animation/{animation.rb → on_update_operation.rb} +6 -4
  13. data/lib/metro/animation/scene_animation.rb +16 -0
  14. data/lib/metro/events/control_definition.rb +11 -0
  15. data/lib/metro/events/controls.rb +42 -0
  16. data/lib/metro/events/event_relay.rb +49 -11
  17. data/lib/metro/events/has_events.rb +6 -5
  18. data/lib/metro/game.rb +17 -11
  19. data/lib/metro/game/dsl.rb +8 -0
  20. data/lib/metro/models/image.rb +3 -1
  21. data/lib/metro/models/key_value_coding.rb +38 -0
  22. data/lib/metro/models/label.rb +18 -3
  23. data/lib/metro/models/menu.rb +6 -5
  24. data/lib/metro/models/model.rb +11 -4
  25. data/lib/metro/models/rectangle.rb +28 -0
  26. data/lib/metro/scene.rb +76 -15
  27. data/lib/metro/scene_view/yaml_view.rb +11 -4
  28. data/lib/metro/scenes.rb +27 -3
  29. data/lib/metro/template_message.rb +33 -4
  30. data/lib/metro/transitions/fade_transition_scene.rb +59 -0
  31. data/lib/metro/transitions/scene_transitions.rb +30 -0
  32. data/lib/metro/transitions/transition_scene.rb +18 -0
  33. data/lib/metro/version.rb +1 -1
  34. data/lib/metro/window.rb +0 -2
  35. data/lib/templates/game/metro.tt +13 -0
  36. data/lib/templates/game/scenes/brand_scene.rb +10 -4
  37. data/lib/templates/game/scenes/brand_to_title_scene.rb +3 -3
  38. data/lib/templates/game/scenes/title_scene.rb +1 -1
  39. data/lib/templates/game/views/brand_to_title.yaml +2 -4
  40. data/lib/templates/game/views/title.yaml +2 -4
  41. data/lib/templates/message.erb +1 -1
  42. data/lib/templates/model.rb.erb +2 -2
  43. data/lib/templates/scene.rb.erb +3 -3
  44. data/metro.gemspec +1 -0
  45. data/spec/core_ext/numeric_spec.rb +78 -0
  46. data/spec/metro/models/key_value_coding_spec.rb +61 -0
  47. data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
  48. metadata +44 -6
  49. data/lib/metro/error.rb +0 -21
data/README.md CHANGED
@@ -12,28 +12,44 @@ 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
- Please take a look at the [example game project](https://github.com/burtlo/starry-knight) that I am building alongside of 'metro' to see how it can be used to develop games.
16
-
17
15
  ## Installation
18
16
 
19
- Add this line to your application's Gemfile:
20
-
21
- gem 'metro'
17
+ $ gem install metro
22
18
 
23
- And then execute:
19
+ ## Usage
24
20
 
25
- $ bundle
21
+ ### Running a Game
26
22
 
27
- Or install it yourself as:
23
+ By default `metro` will look for a file named 'metro' within the current working directory if no *gamefilename* has been provided.
28
24
 
29
- $ gem install metro
25
+ ```
26
+ metro [gamefilename]
27
+ ```
30
28
 
31
- ## Usage
29
+ Please take a look at the [example game project](https://github.com/burtlo/starry-knight) that is being built alongside of 'metro'. It currently showcases all the current features available to the game.
32
30
 
31
+ ```bash
32
+ $ git clone git://github.com/burtlo/starry-knight.git
33
+ $ cd starry-knight
34
+ $ metro
33
35
  ```
34
- metro [gamefilename]
36
+
37
+ ### Creating a Game
38
+
39
+ Metro contains content generators to assist you.
40
+
41
+ Creating a Game can be done with a single command.
42
+
43
+ ```bash
44
+ $ metro new GAMENAME
35
45
  ```
36
46
 
37
- By default Metro will look for a file named 'metro' within the current working directory if no *gamefilename* has been provided.
47
+ This should generate for you a starting game with a branding scene and a title scene. The game allows the player to start the game.
48
+
49
+ The game is missing the `first` scene of the game. This can be created with the scene generator:
50
+
51
+ ```bash
52
+ $ metro generate scene first
53
+ ```
38
54
 
39
- Please take a look at the [example game project](https://github.com/burtlo/starry-knight) that I am building alongside of 'metro' to see how it can be used to develop games.
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.
data/changelog.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Metro
2
2
 
3
+ ## 0.1.3 / 2012-10-28
4
+
5
+ * Fade Scene Transition support added
6
+ * Numeric#seconds and Numeric#ticks helpers added
7
+ * Scenes can now define delayed events `after 2.seconds do ; end`
8
+ * Labels have more defaults and more font options and size
9
+ * Labels and images will default to center of screen
10
+ * Able to define game controls within your metro file
11
+ * Implicit animations support color change.
12
+
3
13
  ## 0.1.2 / 2012-10-26
4
14
 
5
15
  * Generators for games, scenes, models, and views
@@ -0,0 +1,59 @@
1
+ class Numeric
2
+
3
+ #
4
+ # Set the tick interval which is used in conversion of seconds to
5
+ # ticks. By default
6
+ #
7
+ def self.tick_interval=(value)
8
+ @tick_interval = value.to_f
9
+ end
10
+
11
+ #
12
+ # @return the game tick interval.
13
+ #
14
+ def self.tick_interval
15
+ @tick_interval.to_f == 0 ? 16.666666 : @tick_interval.to_f
16
+ end
17
+
18
+
19
+
20
+ #
21
+ # Provides the suffix 'second' which will translate seconds to
22
+ # game ticks. This is to allow for a number of seconds to be expressed
23
+ # in situations where game ticks are used (e.g. animations).
24
+ #
25
+ # @example Expressing an animation that takes place over 3 seconds (180 ticks)
26
+ #
27
+ # class ExampleScene < Metro::Scene
28
+ # draws :title
29
+ #
30
+ # animate :title, to: { x: 320, y: 444 }, interval: 3.seconds
31
+ # end
32
+ #
33
+ def second
34
+ (self * 1000 / Numeric.tick_interval).floor
35
+ end
36
+
37
+ alias_method :seconds, :second
38
+ alias_method :secs, :second
39
+ alias_method :sec, :second
40
+
41
+ #
42
+ # Provides the suffix 'tick' which will simply express the number
43
+ # with a vanity suffix.
44
+ #
45
+ # @example Expressing an animation that takes place over 60 game ticks
46
+ #
47
+ # class ExampleScene < Metro::Scene
48
+ # draws :title
49
+ #
50
+ # animate :title, to: { x: 320, y: 444 }, interval: 60.ticks
51
+ # end
52
+ #
53
+ def tick
54
+ self
55
+ end
56
+
57
+ alias_method :ticks, :tick
58
+
59
+ end
@@ -0,0 +1,53 @@
1
+ module Metro
2
+
3
+ #
4
+ # The GosuEvents module creates aliases for the Keyboard and the Gamepad events
5
+ # within the Gosu Namespace. This is so Metro can use the events without requiring
6
+ # the namespace.
7
+ #
8
+ # This makes the interface of these events more portable.
9
+ #
10
+ module GosuConstants
11
+
12
+ def self.extended(base)
13
+ add constants: keyboard_events, to: base
14
+ add constants: gamepad_events, to: base
15
+ add constants: mouse_events, to: base
16
+ end
17
+
18
+ #
19
+ # @return the constant from which to search for all the other constants.
20
+ # This helper method is to to save sprinkling the constant value
21
+ # throughout the rest of the module.
22
+ def self.gosu
23
+ Gosu
24
+ end
25
+
26
+ def self.keyboard_events
27
+ find_all_constants_with_prefix "Kb"
28
+ end
29
+
30
+ def self.gamepad_events
31
+ find_all_constants_with_prefix "Gp"
32
+ end
33
+
34
+ def self.mouse_events
35
+ find_all_constants_with_prefix "Ms"
36
+ end
37
+
38
+ def self.find_all_constants_with_prefix(prefix)
39
+ gosu.constants.find_all { |c| c.to_s.start_with? prefix }
40
+ end
41
+
42
+ #
43
+ # Alias the list of given constants within the given class.
44
+ #
45
+ def self.add(options={})
46
+ events = options[:constants]
47
+ target = options[:to]
48
+
49
+ events.each {|event| target.const_set event, gosu.const_get(event) }
50
+ end
51
+
52
+ end
53
+ end
data/lib/locale/en.yml ADDED
@@ -0,0 +1,16 @@
1
+ ---
2
+ en:
3
+ website: "%{website}"
4
+ error:
5
+ missing_metro_file:
6
+ title: "Unable to find Metro game file"
7
+ message: "The specified file `%{file}` which is required to run the game could not be found."
8
+ actions:
9
+ - "Ensure you have specified the correct file"
10
+ - "Ensure that the file exists at that location"
11
+ reserved_control_name:
12
+ title: "Unable to define control: %{name}"
13
+ message: "The specified control name `%{name}` is RESERVED or ALREADY DEFINED."
14
+ actions:
15
+ - "Ensure that the control name is not already defined."
16
+ - "Replace the use of this control name with name"
@@ -0,0 +1 @@
1
+ I18n.load_path.push "#{File.dirname(__FILE__)}/en.yml"
data/lib/metro.rb CHANGED
@@ -1,22 +1,32 @@
1
1
  require 'gosu'
2
2
  require 'gosu_ext/color'
3
+ require 'gosu_ext/gosu_constants'
3
4
  require 'sender'
5
+ require 'i18n'
6
+
4
7
 
5
8
  require 'core_ext/string'
9
+ require 'core_ext/numeric'
6
10
  require 'logger'
7
11
  require 'erb'
8
12
 
13
+ require 'locale/locale'
9
14
  require 'metro/version'
10
- require 'metro/error'
11
15
  require 'metro/template_message'
12
16
  require 'metro/window'
13
17
  require 'metro/game'
14
18
  require 'metro/scene'
19
+ require 'metro/scenes'
15
20
  require 'metro/models/model'
16
21
  require 'metro/models/generic'
17
22
 
18
23
  require_relative 'metro/missing_scene'
19
24
 
25
+ #
26
+ # To allow an author an easier time accessing the Game object from within their game.
27
+ # They do not have to use the `Metro::Game` an instead use the `Game` constant.
28
+ #
29
+ Game = Metro::Game
20
30
 
21
31
  def asset_path(name)
22
32
  File.join Dir.pwd, "assets", name
@@ -30,8 +40,19 @@ def log
30
40
  end
31
41
  end
32
42
 
43
+ def error(messages, details = {})
44
+ details = { show: true }.merge details
45
+
46
+ message = TemplateMessage.new messages: messages, details: details,
47
+ website: Game.website, contact: Game.contact
48
+
49
+ warn message if details[:show]
50
+ exit 1
51
+ end
52
+
33
53
  module Metro
34
54
  extend self
55
+ extend GosuConstants
35
56
 
36
57
  #
37
58
  # @return [String] the default filename that contains the game contents
@@ -51,6 +72,7 @@ module Metro
51
72
  def run(filename=default_game_filename)
52
73
  load_game_files
53
74
  load_game_configuration(filename)
75
+ configure_controls!
54
76
  start_game
55
77
  end
56
78
 
@@ -70,6 +92,10 @@ module Metro
70
92
  Game.setup game
71
93
  end
72
94
 
95
+ def configure_controls!
96
+ EventRelay.define_controls Game.controls
97
+ end
98
+
73
99
  def start_game
74
100
  window = Window.new Game.width, Game.height, Game.fullscreen?
75
101
  window.caption = Game.name
@@ -78,26 +104,14 @@ module Metro
78
104
  end
79
105
 
80
106
  def game_files_exist!(*files)
81
- error_messages = files.compact.flatten.map { |file| game_file_exists?(file) }.reject {|exist| exist == true }
82
- unless error_messages.empty?
83
- display_error_message(error_messages)
84
- exit 1
85
- end
107
+ files.compact.flatten.each { |file| game_file_exists?(file) }
86
108
  end
87
109
 
110
+
88
111
  def game_file_exists?(file)
89
112
  unless File.exists? file
90
- Error.new title: "Unable to find Metro game file",
91
- message: "The specified file `#{file}` which is required to run the game could not be found.",
92
- details: [ "Ensure you have specified the correct file", "Ensure that the file exists at that location" ]
93
- else
94
- true
113
+ error "error.missing_metro_file", file: file
95
114
  end
96
115
  end
97
116
 
98
- def display_error_message(messages)
99
- message = TemplateMessage.new messages: messages, website: WEBSITE, email: CONTACT_EMAILS
100
- warn message
101
- end
102
-
103
117
  end
@@ -0,0 +1,12 @@
1
+ module Metro
2
+
3
+ class AfterIntervalFactory
4
+ attr_reader :ticks, :block
5
+
6
+ def initialize(ticks,&block)
7
+ @ticks = ticks
8
+ @block = block
9
+ end
10
+ end
11
+
12
+ end
@@ -2,9 +2,10 @@ module Metro
2
2
 
3
3
  class AnimationFactory
4
4
 
5
- attr_reader :options, :on_complete_block
5
+ attr_reader :actor, :options, :on_complete_block
6
6
 
7
- def initialize(options = {},&block)
7
+ def initialize(actor,options = {},&block)
8
+ @actor = actor
8
9
  @options = options
9
10
  @on_complete_block = block
10
11
  end
@@ -15,12 +15,12 @@ module Metro
15
15
  # @example Defining an animation that fades in and moves a logo when it is
16
16
  # done, transition to the title scene.
17
17
  #
18
- # animate actor: :logo, to: { y: 80, alpha: 50 }, interval: 120 do
18
+ # animate :logo, to: { y: 80, alpha: 50 }, interval: 120 do
19
19
  # transition_to :title
20
20
  # end
21
21
  #
22
- def animate(options,&block)
23
- scene_animation = AnimationFactory.new options, &block
22
+ def animate(actor_name,options,&block)
23
+ scene_animation = AnimationFactory.new actor_name, options, &block
24
24
  animations.push scene_animation
25
25
  end
26
26
 
@@ -32,18 +32,11 @@ module Metro
32
32
  # is executed. In this case, upon completition, transition the scene
33
33
  # from the current one to the main scene.
34
34
  #
35
- class ImplicitAnimation < Animation
35
+ class ImplicitAnimation < OnUpdateOperation
36
36
 
37
- #
38
- # @return the array of attributes that are being animated.
39
- #
40
- attr_reader :attributes
41
-
42
- #
43
- # @return a Hash that contains all the animation step deltas
44
- # for each attribute.
45
- #
46
- attr_reader :deltas
37
+ def animations
38
+ @animations ||= []
39
+ end
47
40
 
48
41
  #
49
42
  # @return the type of easing that the implicit animation should employ.
@@ -57,13 +50,36 @@ module Metro
57
50
  # that are going to be animated and to determine each of their deltas.
58
51
  #
59
52
  def after_initialize
60
- @deltas = {}
61
-
62
- @attributes = to.map { |attribute,final| attribute }
63
-
64
53
  to.each do |attribute,final|
65
54
  start = actor.send(attribute)
66
- deltas[attribute] = stepping(easing).calculate(start.to_f,final.to_f,interval.to_f)
55
+
56
+ if attribute == :color
57
+ final = Gosu::Color.new final
58
+
59
+ animations.push build_animation_step(:"color.red",start.red,final.red)
60
+ animations.push build_animation_step(:"color.green",start.green,final.green)
61
+ animations.push build_animation_step(:"color.blue",start.blue,final.blue)
62
+ animations.push build_animation_step(:"color.alpha",start.alpha,final.alpha)
63
+ else
64
+ animations.push build_animation_step(attribute,start,final)
65
+ end
66
+
67
+ end
68
+ end
69
+
70
+ def build_animation_step(attribute,start,final)
71
+ step = AnimationStep.new
72
+ step.actor = actor
73
+ step.attribute = attribute
74
+ step.deltas = stepping(easing).calculate(start.to_f,final.to_f,interval.to_f)
75
+ step
76
+ end
77
+
78
+ class AnimationStep
79
+ attr_accessor :actor, :attribute, :deltas
80
+
81
+ def execute_step(step)
82
+ actor.set(attribute,deltas.at(step))
67
83
  end
68
84
  end
69
85
 
@@ -86,9 +102,7 @@ module Metro
86
102
  # current animation step.
87
103
  #
88
104
  def execute_step
89
- attributes.each do |attribute|
90
- actor.send "#{attribute}=", delta_for_step(attribute)
91
- end
105
+ animations.each {|step| step.execute_step(current_step) }
92
106
  end
93
107
 
94
108
  #
@@ -1,11 +1,13 @@
1
1
  module Metro
2
2
 
3
3
  #
4
- # Animation is the motion that gives a system it's life. An animation
5
- # in this case is a really a mechanism that allows for an action to
6
- # be repeated for a given interval of time.
4
+ # OnUpdateOperation is an object that executes on the update cycle
5
+ # of the game. This usually take the form of an animation or some operation
6
+ # that needs to execute with each update of the game.
7
7
  #
8
- class Animation
8
+ class OnUpdateOperation
9
+
10
+ def after_initialize ; end
9
11
 
10
12
  def initialize(options)
11
13
  @current_step = 0