metro 0.0.6 → 0.1.0
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/.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
|