metro 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +7 -0
- data/Guardfile +4 -0
- data/README.md +8 -0
- data/lib/gosu_ext/color.rb +25 -1
- data/lib/metro.rb +30 -2
- data/lib/metro/animation/animation_factory.rb +14 -0
- data/lib/metro/animation/has_animations.rb +37 -0
- data/lib/metro/error.rb +21 -0
- data/lib/metro/events/event_factory.rb +15 -0
- data/lib/metro/{event_relay.rb → events/event_relay.rb} +83 -25
- data/lib/metro/events/has_events.rb +108 -0
- data/lib/metro/events/unknown_sender.rb +5 -0
- data/lib/metro/game.rb +12 -0
- data/lib/metro/game/dsl.rb +21 -2
- data/lib/metro/missing_scene.rb +21 -0
- data/lib/metro/models/draws.rb +71 -0
- data/lib/metro/models/label.rb +2 -0
- data/lib/metro/models/menu.rb +32 -15
- data/lib/metro/models/model.rb +14 -0
- data/lib/metro/{scene_actor.rb → models/model_factory.rb} +1 -1
- data/lib/metro/scene.rb +124 -70
- data/lib/metro/scenes.rb +8 -5
- data/lib/metro/template_message.rb +31 -0
- data/lib/metro/version.rb +30 -1
- data/lib/templates/message.erb +24 -0
- data/metro.gemspec +38 -6
- data/spec/gosu_ext/color_spec.rb +80 -0
- data/spec/spec_helper.rb +18 -0
- metadata +69 -8
data/.rspec
ADDED
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
```
|
2
|
+
______ ___ _____
|
3
|
+
___ |/ /_____ __ /_______________
|
4
|
+
__ /|_/ / _ _ \_ __/__ ___/_ __ \
|
5
|
+
_ / / / / __// /_ _ / / /_/ /
|
6
|
+
/_/ /_/ \___/ \__/ /_/ \____/
|
7
|
+
|
8
|
+
```
|
1
9
|
# metro
|
2
10
|
|
3
11
|
Metro is a framework built around [gosu](https://github.com/jlnr/gosu) (the 2D game development library in Ruby). The goal of Metro is to enforce common conceptual structures and conventions making it easier to quickly generate a game.
|
data/lib/gosu_ext/color.rb
CHANGED
@@ -12,7 +12,7 @@ class Gosu::Color
|
|
12
12
|
if value.is_a? Gosu::Color
|
13
13
|
gosu_initialize value.alpha, value.red, value.green, value.blue
|
14
14
|
elsif value.is_a? String
|
15
|
-
gosu_initialize
|
15
|
+
gosu_initialize *Array(self.class.parse_string(value))
|
16
16
|
else
|
17
17
|
gosu_initialize value
|
18
18
|
end
|
@@ -21,4 +21,28 @@ class Gosu::Color
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def self.parse_string(value)
|
25
|
+
parse_hex(value) || parse_rgb(value) || parse_rgba(value) || [ 255, 255, 255, 255 ]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parse_rgba(rgba)
|
29
|
+
if rgba =~ /rgba\(([\d]{1,3}),([\d]{1,3}),([\d]{1,3}),(\d(?:\.\d)?)\)/
|
30
|
+
[ (255 * $4.to_f).floor.to_i, $1.to_i, $2.to_i, $3.to_i ]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.parse_rgb(rgb)
|
35
|
+
if rgb =~ /rgb\(([\d]{1,3}),([\d]{1,3}),([\d]{1,3})\)/
|
36
|
+
[ 255, $1.to_i, $2.to_i, $3.to_i ]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.parse_hex(hex)
|
41
|
+
if hex =~ /0x([A-Fa-f0-9]{8})/
|
42
|
+
hex.to_i(16)
|
43
|
+
elsif hex =~ /#([A-Fa-f0-9]{6})/
|
44
|
+
"0xFF#{$1}".to_i(16)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
24
48
|
end
|
data/lib/metro.rb
CHANGED
@@ -2,20 +2,24 @@ require 'gosu'
|
|
2
2
|
require 'gosu_ext/color'
|
3
3
|
|
4
4
|
require 'logger'
|
5
|
-
|
5
|
+
require 'erb'
|
6
6
|
|
7
7
|
require 'metro/version'
|
8
|
+
require 'metro/error'
|
9
|
+
require 'metro/template_message'
|
8
10
|
require 'metro/window'
|
9
11
|
require 'metro/game'
|
10
12
|
require 'metro/scene'
|
11
13
|
require 'metro/models/model'
|
12
14
|
require 'metro/models/generic'
|
13
15
|
|
16
|
+
require_relative 'metro/missing_scene'
|
17
|
+
|
18
|
+
|
14
19
|
def asset_path(name)
|
15
20
|
File.join Dir.pwd, "assets", name
|
16
21
|
end
|
17
22
|
|
18
|
-
|
19
23
|
def log
|
20
24
|
@log ||= begin
|
21
25
|
logger = Logger.new(STDOUT)
|
@@ -57,6 +61,7 @@ module Metro
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def load_game_configuration(filename)
|
64
|
+
game_files_exist!(filename)
|
60
65
|
game_contents = File.read(filename)
|
61
66
|
game_block = lambda {|instance| eval(game_contents) }
|
62
67
|
game = Game::DSL.parse(&game_block)
|
@@ -70,4 +75,27 @@ module Metro
|
|
70
75
|
window.show
|
71
76
|
end
|
72
77
|
|
78
|
+
def game_files_exist!(*files)
|
79
|
+
error_messages = files.compact.flatten.map { |file| game_file_exists?(file) }.reject {|exist| exist == true }
|
80
|
+
unless error_messages.empty?
|
81
|
+
display_error_message(error_messages)
|
82
|
+
exit 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def game_file_exists?(file)
|
87
|
+
unless File.exists? file
|
88
|
+
Error.new title: "Unable to find Metro game file",
|
89
|
+
message: "The specified file `#{file}` which is required to run the game could not be found.",
|
90
|
+
details: [ "Ensure you have specified the correct file", "Ensure that the file exists at that location" ]
|
91
|
+
else
|
92
|
+
true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def display_error_message(messages)
|
97
|
+
message = TemplateMessage.new messages: messages, website: WEBSITE, email: CONTACT_EMAILS
|
98
|
+
warn message
|
99
|
+
end
|
100
|
+
|
73
101
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'animation_factory'
|
2
|
+
|
3
|
+
module Metro
|
4
|
+
module HasAnimations
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
#
|
13
|
+
# Define an animation to execute when the scene starts.
|
14
|
+
#
|
15
|
+
# @example Defining an animation that fades in and moves a logo when it is
|
16
|
+
# done, transition to the title scene.
|
17
|
+
#
|
18
|
+
# animate actor: :logo, to: { y: 80, alpha: 50 }, interval: 120 do
|
19
|
+
# transition_to :title
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
def animate(options,&block)
|
23
|
+
scene_animation = AnimationFactory.new options, &block
|
24
|
+
animations.push scene_animation
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# All the animations that are defined for the scene to be run the scene starts.
|
29
|
+
#
|
30
|
+
def animations
|
31
|
+
@animations ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/metro/error.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Error
|
2
|
+
|
3
|
+
def initialize(details = {})
|
4
|
+
@title = details[:title]
|
5
|
+
@message = details[:message]
|
6
|
+
@details = details[:details]
|
7
|
+
end
|
8
|
+
|
9
|
+
def title
|
10
|
+
"ERROR: #{@title.upcase}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
@message
|
15
|
+
end
|
16
|
+
|
17
|
+
def details
|
18
|
+
@details.map {|detail| "* #{detail}" }.join("\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -56,6 +56,7 @@ module Metro
|
|
56
56
|
@up_actions ||= {}
|
57
57
|
@down_actions ||= {}
|
58
58
|
@held_actions ||= {}
|
59
|
+
@custom_notifications ||= Hash.new([])
|
59
60
|
end
|
60
61
|
|
61
62
|
attr_reader :target, :window
|
@@ -68,9 +69,7 @@ module Metro
|
|
68
69
|
# @example Registering for a button down event to call a method named 'previous_option'
|
69
70
|
#
|
70
71
|
# class ExampleScene
|
71
|
-
#
|
72
|
-
# e.on_down Gosu::GpLeft, Gosu::GpUp, do: :previous_option
|
73
|
-
# end
|
72
|
+
# event :on_down, Gosu::GpLeft, Gosu::GpUp, do: :previous_option
|
74
73
|
#
|
75
74
|
# def previous_option
|
76
75
|
# @selected_index = @selected_index - 1
|
@@ -85,11 +84,9 @@ module Metro
|
|
85
84
|
# @example Registering for a button down event with a block of code to execute
|
86
85
|
#
|
87
86
|
# class ExampleScene
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# @selected_index = options.length - 1 if @selected_index <= -1
|
92
|
-
# end
|
87
|
+
# event :on_down, Gosu::GpLeft, Gosu::GpUp do
|
88
|
+
# @selected_index = @selected_index - 1
|
89
|
+
# @selected_index = options.length - 1 if @selected_index <= -1
|
93
90
|
# end
|
94
91
|
# end
|
95
92
|
#
|
@@ -108,9 +105,7 @@ module Metro
|
|
108
105
|
# @example Registering for a button down event to call a method named 'next_option'
|
109
106
|
#
|
110
107
|
# class ExampleScene
|
111
|
-
#
|
112
|
-
# e.on_up Gosu::KbEscape, do: :leave_scene
|
113
|
-
# end
|
108
|
+
# event :on_up, Gosu::KbEscape, do: :leave_scene
|
114
109
|
#
|
115
110
|
# def leave_scene
|
116
111
|
# transition_to :title
|
@@ -123,10 +118,8 @@ module Metro
|
|
123
118
|
# @example Registering for a button up event with a block of code to execute
|
124
119
|
#
|
125
120
|
# class ExampleScene
|
126
|
-
#
|
127
|
-
#
|
128
|
-
# transition_to :title
|
129
|
-
# end
|
121
|
+
# event :on_up, Gosu::KbEscape do
|
122
|
+
# transition_to :title
|
130
123
|
# end
|
131
124
|
# end
|
132
125
|
#
|
@@ -147,18 +140,16 @@ module Metro
|
|
147
140
|
# @example Registering for button held events
|
148
141
|
#
|
149
142
|
# class ExampleScene
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
# end
|
154
|
-
#
|
155
|
-
# e.on_hold Gosu::KbRight, Gosu::GpRight do
|
156
|
-
# player.turn_right
|
157
|
-
# end
|
143
|
+
# event :on_hold Gosu::KbLeft, Gosu::GpLeft do
|
144
|
+
# player.turn_left
|
145
|
+
# end
|
158
146
|
#
|
159
|
-
#
|
147
|
+
# event :on_hold, Gosu::KbRight, Gosu::GpRight do
|
148
|
+
# player.turn_right
|
160
149
|
# end
|
161
150
|
#
|
151
|
+
# event :on_hold, Gosu::KbUp, Gosu::GpButton0, do: :calculate_accleration
|
152
|
+
#
|
162
153
|
# def calculate_acceleration
|
163
154
|
# long_complicated_calculated_result = 0
|
164
155
|
# # ... multi-line calculations to determine the player acceleration ...
|
@@ -171,7 +162,45 @@ module Metro
|
|
171
162
|
_on(@held_actions,args,block)
|
172
163
|
end
|
173
164
|
|
174
|
-
|
165
|
+
#
|
166
|
+
# Register for a custom notification event. These events are fired when
|
167
|
+
# another object within the game posts a notification with matching criteria.
|
168
|
+
# If there has indeed been a match, then the stored action block will be fired.
|
169
|
+
#
|
170
|
+
# When the action block is specified is defined with no parameters it is assumed that
|
171
|
+
# that the code should be executed within the context of the object that defined
|
172
|
+
# the action, the 'target'.
|
173
|
+
#
|
174
|
+
# @example Registering for a save complete event that would re-enable a menu.
|
175
|
+
#
|
176
|
+
# class ExampleScene
|
177
|
+
# event :notification, :save_complete do
|
178
|
+
# menu.enabled!
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# The action block can also be specified with two parameters. In this case the code is
|
183
|
+
# no longer executed within the context of the object and is instead provided the
|
184
|
+
# the action target and the action source.
|
185
|
+
#
|
186
|
+
# @example Registering for a win game event that explicitly states the target and source.
|
187
|
+
#
|
188
|
+
# class ExampleScene
|
189
|
+
#
|
190
|
+
# event :notification, :win_game do |target,winner|
|
191
|
+
# target.declare_winner winner
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# def declare_winner(winning_player)
|
195
|
+
# # ...
|
196
|
+
# end
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
def notification(param,&block)
|
200
|
+
custom_notifications[param.to_sym] = custom_notifications[param.to_sym] + [ block ]
|
201
|
+
end
|
202
|
+
|
203
|
+
attr_reader :up_actions, :down_actions, :held_actions, :custom_notifications
|
175
204
|
|
176
205
|
def _on(hash,args,block)
|
177
206
|
options = (args.last.is_a?(Hash) ? args.pop : {})
|
@@ -220,6 +249,35 @@ module Metro
|
|
220
249
|
down_actions[id] || lambda {|instance| send(:down_action_missing,id) if respond_to?(:down_action_missing) }
|
221
250
|
end
|
222
251
|
|
252
|
+
#
|
253
|
+
# Fire all events mapped to the matching notification.
|
254
|
+
#
|
255
|
+
def fire_events_for_notification(event,sender)
|
256
|
+
notification_actions = custom_notifications[event]
|
257
|
+
notification_actions.each do |action|
|
258
|
+
_fire_event_for_notification(event,sender,action)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Fire a single event based on the matched notification.
|
264
|
+
#
|
265
|
+
# An action without any parameters is assumed to be executed within the contexxt
|
266
|
+
# of the target. If there are two parameters we will simply execute the action and
|
267
|
+
# pass it both the target and the sender.
|
268
|
+
#
|
269
|
+
# @TODO: Allow for the blocks to be specified with one parameter: source (and executed
|
270
|
+
# within the context of the target)
|
271
|
+
#
|
272
|
+
# @TODO: Allow for the blocks to be specified with three parameters: source, target, event
|
273
|
+
#
|
274
|
+
def _fire_event_for_notification(event,sender,action)
|
275
|
+
if action.arity == 2
|
276
|
+
action.call(target,sender)
|
277
|
+
else
|
278
|
+
target.instance_eval(&action)
|
279
|
+
end
|
280
|
+
end
|
223
281
|
|
224
282
|
end
|
225
283
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require_relative 'event_factory'
|
2
|
+
|
3
|
+
module Metro
|
4
|
+
|
5
|
+
module HasEvents
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
#
|
14
|
+
# Register an event for the scene.
|
15
|
+
#
|
16
|
+
# @example Registering for a save complete event that would re-enable a menu.
|
17
|
+
#
|
18
|
+
# class ExampleScene
|
19
|
+
# event :notification, :save_complete do
|
20
|
+
# menu.enabled!
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example Registering for button held events
|
25
|
+
#
|
26
|
+
# class ExampleScene
|
27
|
+
# event :on_hold Gosu::KbLeft, Gosu::GpLeft do
|
28
|
+
# player.turn_left
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# event :on_hold, Gosu::KbRight, Gosu::GpRight do
|
32
|
+
# player.turn_right
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# event :on_hold, Gosu::KbUp, Gosu::GpButton0, do: :calculate_accleration
|
36
|
+
#
|
37
|
+
# def calculate_acceleration
|
38
|
+
# long_complicated_calculated_result = 0
|
39
|
+
# # ... multi-line calculations to determine the player acceleration ...
|
40
|
+
# player.accelerate = long_complicated_calculated_result
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @example Registering for a button down event to call a method named 'next_option'
|
45
|
+
#
|
46
|
+
# class ExampleScene
|
47
|
+
# event :on_up, Gosu::KbEscape, do: :leave_scene
|
48
|
+
#
|
49
|
+
# def leave_scene
|
50
|
+
# transition_to :title
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# Here in this scene if the Escape Key is pressed and released the example scene
|
55
|
+
# will transition to the title scene.
|
56
|
+
#
|
57
|
+
# @example Registering for a button up event with a block of code to execute
|
58
|
+
#
|
59
|
+
# class ExampleScene
|
60
|
+
# event :on_up, Gosu::KbEscape do
|
61
|
+
# transition_to :title
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# @example Registering for a button down event to call a method named 'previous_option'
|
66
|
+
#
|
67
|
+
# class ExampleScene
|
68
|
+
# event :on_down, Gosu::GpLeft, Gosu::GpUp, do: :previous_option
|
69
|
+
#
|
70
|
+
# def previous_option
|
71
|
+
# @selected_index = @selected_index - 1
|
72
|
+
# @selected_index = options.length - 1 if @selected_index <= -1
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# Here in this scene if the GpLeft or GpUp buttons are pressed down the method
|
77
|
+
# `previous_options` will be executed.
|
78
|
+
#
|
79
|
+
#
|
80
|
+
# @example Registering for a button down event with a block of code to execute
|
81
|
+
#
|
82
|
+
# class ExampleScene
|
83
|
+
# event :on_down, Gosu::GpLeft, Gosu::GpUp do
|
84
|
+
# @selected_index = @selected_index - 1
|
85
|
+
# @selected_index = options.length - 1 if @selected_index <= -1
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# This example uses a block instead of a method name but it is absolultey the same
|
90
|
+
# as the last example.
|
91
|
+
#
|
92
|
+
def event(event_type,*buttons,&block)
|
93
|
+
scene_event = EventFactory.new event_type, buttons, &block
|
94
|
+
events.push scene_event
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# @return a list of all the EventFactories defined for the scene
|
99
|
+
#
|
100
|
+
def events
|
101
|
+
@events ||= []
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|