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