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.
- data/README.md +29 -13
- data/changelog.md +10 -0
- data/lib/core_ext/numeric.rb +59 -0
- data/lib/gosu_ext/gosu_constants.rb +53 -0
- data/lib/locale/en.yml +16 -0
- data/lib/locale/locale.rb +1 -0
- data/lib/metro.rb +30 -16
- data/lib/metro/animation/after_interval_factory.rb +12 -0
- data/lib/metro/animation/animation_factory.rb +3 -2
- data/lib/metro/animation/has_animations.rb +3 -3
- data/lib/metro/animation/implicit_animation.rb +33 -19
- data/lib/metro/animation/{animation.rb → on_update_operation.rb} +6 -4
- data/lib/metro/animation/scene_animation.rb +16 -0
- data/lib/metro/events/control_definition.rb +11 -0
- data/lib/metro/events/controls.rb +42 -0
- data/lib/metro/events/event_relay.rb +49 -11
- data/lib/metro/events/has_events.rb +6 -5
- data/lib/metro/game.rb +17 -11
- data/lib/metro/game/dsl.rb +8 -0
- data/lib/metro/models/image.rb +3 -1
- data/lib/metro/models/key_value_coding.rb +38 -0
- data/lib/metro/models/label.rb +18 -3
- data/lib/metro/models/menu.rb +6 -5
- data/lib/metro/models/model.rb +11 -4
- data/lib/metro/models/rectangle.rb +28 -0
- data/lib/metro/scene.rb +76 -15
- data/lib/metro/scene_view/yaml_view.rb +11 -4
- data/lib/metro/scenes.rb +27 -3
- data/lib/metro/template_message.rb +33 -4
- data/lib/metro/transitions/fade_transition_scene.rb +59 -0
- data/lib/metro/transitions/scene_transitions.rb +30 -0
- data/lib/metro/transitions/transition_scene.rb +18 -0
- data/lib/metro/version.rb +1 -1
- data/lib/metro/window.rb +0 -2
- data/lib/templates/game/metro.tt +13 -0
- data/lib/templates/game/scenes/brand_scene.rb +10 -4
- data/lib/templates/game/scenes/brand_to_title_scene.rb +3 -3
- data/lib/templates/game/scenes/title_scene.rb +1 -1
- data/lib/templates/game/views/brand_to_title.yaml +2 -4
- data/lib/templates/game/views/title.yaml +2 -4
- data/lib/templates/message.erb +1 -1
- data/lib/templates/model.rb.erb +2 -2
- data/lib/templates/scene.rb.erb +3 -3
- data/metro.gemspec +1 -0
- data/spec/core_ext/numeric_spec.rb +78 -0
- data/spec/metro/models/key_value_coding_spec.rb +61 -0
- data/spec/metro/scene_views/yaml_view_spec.rb +38 -0
- metadata +44 -6
- 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
|
-
|
20
|
-
|
21
|
-
gem 'metro'
|
17
|
+
$ gem install metro
|
22
18
|
|
23
|
-
|
19
|
+
## Usage
|
24
20
|
|
25
|
-
|
21
|
+
### Running a Game
|
26
22
|
|
27
|
-
|
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
|
-
|
25
|
+
```
|
26
|
+
metro [gamefilename]
|
27
|
+
```
|
30
28
|
|
31
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|
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 <
|
35
|
+
class ImplicitAnimation < OnUpdateOperation
|
36
36
|
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
|
8
|
+
class OnUpdateOperation
|
9
|
+
|
10
|
+
def after_initialize ; end
|
9
11
|
|
10
12
|
def initialize(options)
|
11
13
|
@current_step = 0
|