metro 0.1.2 → 0.1.3

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