metro 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ before_install:
6
+ - sudo apt-get install build-essential freeglut3-dev libfreeimage-dev libgl1-mesa-dev libopenal-dev libpango1.0-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsndfile-dev libxinerama-dev
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in metro.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'rake'
6
7
 
7
8
  group 'development' do
8
9
  gem 'guard'
data/README.md CHANGED
@@ -11,24 +11,33 @@ Metro is a framework built around [gosu](https://github.com/jlnr/gosu) (the 2D g
11
11
 
12
12
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/burtlo/metro)
13
13
 
14
+ [![Build Status](https://travis-ci.org/burtlo/metro.png)](https://travis-ci.org/burtlo/metro)
15
+
16
+ [![Dependency Status](https://gemnasium.com/burtlo/metro.png)](https://gemnasium.com/burtlo/metro)
17
+
14
18
  ## Why Use Metro?
15
19
 
16
20
  You want to develop games in Ruby.
17
21
 
18
22
  ### Why not just use Gosu?
19
23
 
20
- Gosu does not a lot for you. When you finish the [initial tutorial](https://github.com/jlnr/gosu/wiki/Ruby-Tutorial) you are left with a brittle game that is very resistant to changes or new features.
24
+ Gosu does a lot of great work bringing OpenGL to Ruby. However, when you finish the [initial tutorial](https://github.com/jlnr/gosu/wiki/Ruby-Tutorial) you are left with a brittle game that is very resistant to changes or new features.
21
25
 
22
26
  * Metro provides the concept of a [Scene](https://github.com/burtlo/metro/wiki/Scenes) which is the first abstraction you would likely build after completing the tutorial.
23
27
 
24
- * Sane management of images, animations, fonts, songs, and samples.
28
+ > Developing your game in individual scenes will make it easier to logically layout your game.
29
+
30
+ * Sane management of images, animations, fonts, songs, and samples through [model properties](https://github.com/burtlo/metro/wiki/Model-properties).
31
+
32
+ > Having to load and cache fonts and images in every one of your models is tedious. It is also is wasteful as several of the same fonts are being used all over the place.
25
33
 
26
- * [Implicit animations](https://github.com/burtlo/metro/wiki/Animations)
34
+ * [Key-frame animations](https://github.com/burtlo/metro/wiki/Animations)
35
+
36
+ > Metro makes it simple to move an actor from one position to another position. So simple movements, fades, color changes, and really any property change over time is defined very simply.
27
37
 
28
38
  * [Event Handling](https://github.com/burtlo/metro/wiki/Events)
29
39
 
30
- Metro is built on top of Gosu providing a moderate structure which should
31
- development joyful.
40
+ > Delete those huge `if ... elsif ... else` input checking structures for keyboard, gamepad, and mouse button presses (down,up, and held). Metro makes it easy to define them and an attach a course of action to take when the event happens.
32
41
 
33
42
  ### Why not use Chingu or Gamebox?
34
43
 
@@ -37,8 +46,29 @@ Both [Gamebox](https://github.com/shawn42/gamebox) and
37
46
  larger set of features.
38
47
 
39
48
  With Metro the focus is on a smaller set of features with an emphasis on an
40
- implementation that leads to joyful development. The features have been
41
- developed with documentation and examples.
49
+ implementation that leads to joyful development. An emphasis has been applied to creating elegant solutions which have documentation and examples.
50
+
51
+ * Active Reloading while building your scenes.
52
+
53
+ > Adjustments to your game code while working on a scene will automatically
54
+ reload your game code. The template game sets up a shortcut key (**Ctrl+R**) that allows you to explicitly reload the game and the current scene.
55
+
56
+ * Scene Edit Support
57
+
58
+ > All scenes can have their visual component layout re-adjusted through an edit
59
+ mode. The edit mode layout works for all labels, images, and menus.
60
+
61
+ ### Why you shouldn't use Metro?
62
+
63
+ Metro has some the following limitations:
64
+
65
+ * Limited to the gems defined within Metro
66
+
67
+ > At this point in time you are not able to define and package additional dependencies with your game. This means if you are using a gem that is not already defined by Metro you will run into trouble when running it on alternate systems. This will likely be addressed in the future when more demand arises.
68
+
69
+ * Difficult Deployment
70
+
71
+ > For individuals to play your game, they will also have to install Metro. However, work is being made to bring some simple packaging to Metro games to make them stand-along executables.
42
72
 
43
73
  ## Installation
44
74
 
data/Rakefile CHANGED
@@ -1 +1,18 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ begin
4
+
5
+ require 'rspec/core/rake_task'
6
+
7
+ desc "Run all rspecs"
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.pattern = 'spec/**/*_spec.rb'
10
+ t.rspec_opts = '-c'
11
+ end
12
+
13
+ task :default => :spec
14
+
15
+ rescue LoadError
16
+ puts "please install rspec to run tests"
17
+ puts "install with gem install rspec"
18
+ end
data/changelog.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Metro
2
2
 
3
+ ## 0.3.2 / 2012-11-26
4
+
5
+ * Debug Mode will now automatically reload the game and scene on source
6
+ file changes.
7
+ * Reloading the game will no longer take down the app for syntax errors
8
+ and other errors that are easily detected by simply loading the code.
9
+ * Template game is now automatically has debug mode enabled by default
10
+
3
11
  ## 0.3.1 / 2012-11-25
4
12
 
5
13
  * FIX issue with some Gosu example code left in the oven
@@ -63,11 +71,11 @@
63
71
 
64
72
  ## 0.2.1 / 2012-11-08
65
73
 
66
- * FIX Scene fade transition color changing and implicit animations
74
+ * FIX Scene fade transition color changing and implicit animations
67
75
  for colors
68
76
  * Games creating custom properties will appear in the property list
69
77
  * Properties now correctly default to numeric properties
70
- * Point objects can be added to other point objects.
78
+ * Point objects can be added to other point objects.
71
79
 
72
80
  ## 0.2.0 / 2012-11-07
73
81
 
data/lib/locale/en.yml CHANGED
@@ -2,6 +2,10 @@
2
2
  en:
3
3
  website: "%{website}"
4
4
  error:
5
+ unloadable_source:
6
+ title: "Metro CANNOT load your game code!"
7
+ message: "There is an error within your game code that needs to be fixed before Metro\nis able to replace the current running game code:\n\n%{output}"
8
+ actions: []
5
9
  missing_metro_file:
6
10
  title: "Unable to find Metro game file"
7
11
  message: "The specified file `%{file}` which is required to run the game could not be found."
@@ -22,3 +26,10 @@ en:
22
26
  actions:
23
27
  - "Ensure that the control name is not already defined."
24
28
  - "Replace the use of this control name with name"
29
+ dry_run:
30
+ success:
31
+ title: "Metro Game Dependencies Have Been Met!"
32
+ message: "Your game should be able to be run."
33
+ actions:
34
+ - "Metro dependencies have successfully been loaded"
35
+ - "Game dependencies have successfully been loaded"
data/lib/metro.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'delegate'
2
2
  require 'logger'
3
3
  require 'erb'
4
+ require 'open3'
4
5
 
5
6
  require 'gosu'
6
7
  require 'i18n'
8
+ require 'listen'
7
9
  require 'active_support'
8
10
  require 'active_support/dependencies'
9
11
  require 'active_support/inflector'
@@ -16,6 +18,7 @@ require 'core_ext/numeric'
16
18
 
17
19
  require 'locale/locale'
18
20
 
21
+ require 'metro/parameters/parameters'
19
22
  require 'metro/asset_path'
20
23
  require 'metro/units/units'
21
24
  require 'metro/logging'
@@ -31,8 +34,7 @@ require 'metro/game'
31
34
  require 'metro/scene'
32
35
  require 'metro/scenes'
33
36
  require 'metro/models/model'
34
-
35
- require_relative 'metro/missing_scene'
37
+ require 'metro/missing_scene'
36
38
 
37
39
  #
38
40
  # To allow an author an easier time accessing the Game object from within their game.
@@ -45,123 +47,91 @@ module Metro
45
47
  extend GosuConstants
46
48
 
47
49
  #
48
- # @return [String] the default filename that contains the game contents
50
+ # @return [String] the filepath to the Metro executable
49
51
  #
50
- def default_game_filename
51
- 'metro'
52
+ def executable_path
53
+ File.absolute_path File.join(File.dirname(__FILE__), "..", "bin", "metro")
52
54
  end
53
55
 
56
+ #
57
+ # @return [String] the filepath to the Metro assets
58
+ #
54
59
  def asset_dir
55
60
  File.join File.dirname(__FILE__), "assets"
56
61
  end
57
62
 
58
63
  #
59
- # Run will load the contents of the game contents and game files
60
- # within the current working directory and start the game. By default
61
- # calling run with no parameter will look for a game file
62
- #
63
- # @param [String] filename the name of the game file to run. When not specified
64
- # the value uses the default filename.
64
+ # @return [Array] an array of all the handlers that will be executed prior
65
+ # to the game being launched.
65
66
  #
66
- def run(filename=default_game_filename)
67
- move_to_game_directory!(filename)
68
- load_game_files!
69
- load_game_configuration(filename)
70
- configure_controls!
71
- start_game
67
+ def setup_handlers
68
+ @setup_handlers ||= []
72
69
  end
73
70
 
74
- def load_game_files!
75
- EventDictionary.reset!
76
- prepare_watcher!
77
- load_game_files
78
- execute_watcher!
71
+ #
72
+ # Register a setup handler. While this method is present, it is far
73
+ # too late for game code to be executed as these pregame handlers will already
74
+ # have started executing. This allows for modularity within the Metro library
75
+ # with the possibility that this functionality could become available to
76
+ # individual games if the load process were to be updated.
77
+ #
78
+ def register_setup_handler(handler)
79
+ setup_handlers.push handler
79
80
  end
80
81
 
81
- alias_method :reload!, :load_game_files!
82
-
83
- private
84
-
85
- def move_to_game_directory!(filename)
86
- game_directory = File.dirname(filename)
87
- Dir.chdir game_directory
82
+ #
83
+ # Run will load the contents of the game contents and game files
84
+ # within the current working directory and start the game.
85
+ #
86
+ # @param [Array<String>] parameters an array of parameters that contains
87
+ # the commands in the format that would normally be parsed into the
88
+ # ARGV array.
89
+ #
90
+ def run(*parameters)
91
+ options = Parameters::CommandLineArgsParser.parse(parameters)
92
+ setup_handlers.each { |handler| handler.setup(options) }
93
+ start_game
88
94
  end
89
95
 
90
96
  #
91
- # The watcher will keep track of all the constants that were added to the Object
92
- # Namespace after the start of the execution of the game. This will allow for only
93
- # those objects to be reloaded.
97
+ # Start the game by lanunching a window with the game configuration and data
98
+ # that has been loaded.
94
99
  #
95
- def watcher
96
- @watcher ||= ActiveSupport::Dependencies::WatchStack.new
100
+ def start_game
101
+ Game.start!
97
102
  end
98
103
 
99
104
  #
100
- # The watcher should watch the Object Namespace for any changes. Any constants
101
- # that are added will be tracked from this point forward.
105
+ # When called all the game-related code will be unloaded and reloaded.
106
+ # Providding an opportunity for a game author to tweak the code without having
107
+ # to restart the game.
102
108
  #
103
- def prepare_watcher!
104
- ActiveSupport::Dependencies.clear
105
- watcher.watch_namespaces([ Object ])
109
+ def reload!
110
+ SetupHandlers::LoadGameFiles.new.load_game_files!
106
111
  end
107
112
 
108
113
  #
109
- # The watcher will now mark all the constants that it has watched being loaded
110
- # as unloadable. Doing so exhausts the list of constants found so the watcher
111
- # will be empty.
114
+ # When called the game-related code will be loaded in a sub-process to see
115
+ # if the code is valid. This is used in tandem with {#reload} and should be
116
+ # called prior to ensure that the code that is replacing the current code
117
+ # is valid.
112
118
  #
113
- # @note an exception is raised if the watcher is not prepared every time this
114
- # is called.
119
+ # @return [TrueClass,FalseClass] true if the game code that was loaded was
120
+ # loaded successfully. false if the game code was not able to be loaded.
115
121
  #
116
- def execute_watcher!
117
- watcher.new_constants.each { |constant| unloadable constant }
118
- end
122
+ def game_has_valid_code?
123
+ execution = SetupHandlers::LoadGameFiles.new.launch_game_in_dry_run_mode
119
124
 
120
- def load_game_files
121
- $LOAD_PATH.unshift(Dir.pwd) unless $LOAD_PATH.include?(Dir.pwd)
122
- load_paths 'lib'
123
- load_path 'scenes', prioritize: 'game_scene.rb'
124
- load_path 'models', prioritize: 'game_model.rb'
125
- end
126
-
127
- def load_paths(*paths)
128
- paths.flatten.compact.each {|path| load_path path }
129
- end
130
-
131
- def load_path(path,options = {})
132
- files = Dir["#{path}/**/*.rb"]
133
- files.sort! {|file| File.basename(file) == options[:prioritize] ? -1 : 1 }
134
- files.each {|file| require_or_load file }
135
- end
125
+ if execution.invalid?
126
+ error! 'error.unloadable_source', output: execution.output, exit: false
127
+ end
136
128
 
137
- def load_game_configuration(filename)
138
- gamefile = File.basename(filename)
139
- game_files_exist!(gamefile)
140
- game_contents = File.read(gamefile)
141
- game_block = lambda {|instance| eval(game_contents) }
142
- game = Game::DSL.parse(&game_block)
143
- Game.setup game
129
+ execution.valid?
144
130
  end
145
-
146
- def configure_controls!
147
- EventRelay.define_controls Game.controls
148
- end
149
-
150
- def start_game
151
- window = Window.new Game.width, Game.height, Game.fullscreen?
152
- window.caption = Game.name
153
- window.scene = Scenes.generate(Game.first_scene)
154
- window.show
155
- end
156
-
157
- def game_files_exist!(*files)
158
- files.compact.flatten.each { |file| game_file_exists?(file) }
159
- end
160
-
161
-
162
- def game_file_exists?(file)
163
- error!("error.missing_metro_file",file: file) unless File.exists?(file)
164
- error!("error.specified_directory",directory: file) if File.directory?(file)
165
- end
166
-
167
131
  end
132
+
133
+ require 'setup_handlers/move_to_game_directory'
134
+ require 'setup_handlers/load_game_files'
135
+ require 'setup_handlers/load_game_configuration'
136
+ require 'setup_handlers/exit_if_dry_run'
137
+ require 'setup_handlers/reload_game_on_game_file_changes'
data/lib/metro/game.rb CHANGED
@@ -8,7 +8,37 @@ module Metro
8
8
  @config = game_configuration
9
9
  end
10
10
 
11
- attr_reader :config
11
+ #
12
+ # Creates a window and starts the game with the game parameters.
13
+ #
14
+ def start!
15
+ @window = Window.new width, height, fullscreen?
16
+ window.caption = name
17
+ window.scene = Scenes.generate(first_scene)
18
+ window.show
19
+ end
20
+
21
+ # The original parameters specified during execution. These are the args
22
+ # found on the command-line that are passed in when the game started.
23
+ def execution_parameters
24
+ @execution_parameters ||= []
25
+ end
26
+
27
+ attr_writer :execution_parameters
28
+
29
+ #
30
+ # @return the current game window.
31
+ #
32
+ attr_reader :window
33
+
34
+ #
35
+ # @return [Scene,NilClass] the current scene that is being displayed. If
36
+ # this is called before the window is being displayed when this will return
37
+ # a nil value.
38
+ #
39
+ def current_scene
40
+ window ? window.scene : nil
41
+ end
12
42
 
13
43
  def first_scene
14
44
  fetch(:first_scene)
@@ -62,6 +92,7 @@ module Metro
62
92
  config.send(name) rescue fallback
63
93
  end
64
94
 
95
+ attr_reader :config
65
96
 
66
97
  end
67
98
  end
data/lib/metro/logging.rb CHANGED
@@ -23,11 +23,11 @@ end
23
23
  # for the localized error messages.
24
24
  #
25
25
  def error!(messages, details = {})
26
- details = { show: true }.merge details
26
+ details = { show: true, exit: true }.merge details
27
27
 
28
28
  message = TemplateMessage.new messages: messages, details: details,
29
29
  website: Game.website, contact: Game.contact
30
30
 
31
31
  warn message if details[:show]
32
- exit 1
32
+ exit 1 if details[:exit]
33
33
  end
@@ -0,0 +1,68 @@
1
+ module Metro
2
+ module Parameters
3
+
4
+ #
5
+ # The CommandLineArgsParser converts the argument list passed in from the
6
+ # command-line and generates an Metro::Parameters::Options object.
7
+ #
8
+ module CommandLineArgsParser
9
+ extend self
10
+
11
+ #
12
+ # Given the array of parameters usually from the Command-Line ARGV and
13
+ # convert that information into various parameters
14
+ #
15
+ def parse(*parameters)
16
+ parameters = parameters.flatten.compact
17
+ options = { execution_parameters: parameters.dup }
18
+
19
+ command_flags = extract_command_flags!(parameters)
20
+ filename = extract_game_file!(parameters)
21
+
22
+ Options.new options.merge(filename: filename).merge(command_flags)
23
+ end
24
+
25
+ private
26
+
27
+ #
28
+ # Find all the flags within the array of parameters, extract them and
29
+ # generate a hash. Their presence within the array means that they should
30
+ # have a true value.
31
+ #
32
+ # @return [Hash] a hash that contains all the flags as keys and true as
33
+ # their values. As the presence of the flag means the value is true.
34
+ #
35
+ def extract_command_flags!(parameters)
36
+ raw_command_flags = parameters.flatten.find_all { |arg| arg.start_with? "--" }
37
+ parameters.delete_if { |param| raw_command_flags.include? param }
38
+
39
+ flag_names = raw_command_flags.map { |flag| flag[/--(.+)$/,1].underscore.to_sym }
40
+ flag_values = [ true ] * flag_names.count
41
+ Hash[flag_names.zip(flag_values)]
42
+ end
43
+
44
+ #
45
+ # From the current parameters array remove the first element which should
46
+ # be the default game file. When there is no value then use the default
47
+ # game filename
48
+ #
49
+ # @return [String] the game file name to use
50
+ #
51
+ def extract_game_file!(parameters)
52
+ parameters.delete_at(0) || default_game_filename
53
+ end
54
+
55
+ #
56
+ # The default for games is to have a game file called 'metro'. So if they
57
+ # do not provide the file parameter we assume that it is this file.
58
+ #
59
+ # @return [String] the default filename that contains the game contents
60
+ #
61
+ def default_game_filename
62
+ 'metro'
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,25 @@
1
+ module Metro
2
+ module Parameters
3
+
4
+ #
5
+ # Options are the result of a parameters parser. The options class defines
6
+ # a read-only structure that provides getters for all the parameters
7
+ # specified within the has.
8
+ #
9
+ # @see CommandLineArgsParser
10
+ #
11
+ class Options
12
+ def initialize(params = {})
13
+ params.each do |key,value|
14
+ self.class.send(:define_method,key) { value }
15
+ self.class.send(:define_method,"#{key}?") { value }
16
+ end
17
+ end
18
+
19
+ def method_missing(name,*args,&block)
20
+ return false
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'command_line_args_parser'
2
+ require_relative 'options'
data/lib/metro/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Metro
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  WEBSITE = "https://github.com/burtlo/metro"
4
- CONTACT_EMAILS = ["franklin.webber@gmail.com"]
4
+ CONTACT_EMAILS = ["dev@rubymetro.com"]
5
5
 
6
6
  def self.changes_for_version(version)
7
7
 
@@ -0,0 +1,26 @@
1
+ module Metro
2
+ module SetupHandlers
3
+
4
+ #
5
+ # If the user has enabled the dry-run flag, this is the point at which the
6
+ # game will exit.
7
+ #
8
+ # Dry run mode is useful for determine if all dependencies have successfully
9
+ # been met and the source code will load successfully.
10
+ #
11
+ class ExitIfDryRun
12
+ #
13
+ # @param [Metro::Parameters::Options] options the options that the game
14
+ # was provided when it was launched.
15
+ #
16
+ def setup(options)
17
+ return unless options.dry_run?
18
+ puts TemplateMessage.new message: 'dry_run.success'
19
+ exit
20
+ end
21
+ end
22
+ end
23
+
24
+ register_setup_handler SetupHandlers::ExitIfDryRun.new
25
+
26
+ end
@@ -0,0 +1,65 @@
1
+ module Metro
2
+ module SetupHandlers
3
+
4
+ #
5
+ # The GameExecution allows for a game to be executed. This is used by Metro
6
+ # to validate that the code can be loaded and run before actually running
7
+ # it (specifically when reloading live running code)
8
+ #
9
+ class GameExecution
10
+
11
+ #
12
+ # Perform a game execution with the specified parameters and return the
13
+ # result of that game execution.
14
+ #
15
+ def self.execute(parameters)
16
+ execution = new(parameters)
17
+ execution.execute!
18
+ execution
19
+ end
20
+
21
+ # @return the output generated from the execution of code.
22
+ attr_reader :output
23
+
24
+ # @return an array of parameters that will be provided to the execution
25
+ # of the game.
26
+ attr_reader :parameters
27
+
28
+ #
29
+ # @param [Array] parameters an array of the game parameters that are
30
+ # to be provided to this execution of the game.
31
+ #
32
+ def initialize(parameters)
33
+ @parameters = parameters
34
+ end
35
+
36
+ # @return the status code that was returned when the game execution
37
+ # has completed.
38
+ def status
39
+ @status ||= 0
40
+ end
41
+
42
+ # Perform the game execution.
43
+ def execute!
44
+ @output, @status = Open3.capture2e(game_execution_string)
45
+ end
46
+
47
+ # @return [TrueClass,FalseClass] true if the execution was successful.
48
+ def valid?
49
+ status == 0
50
+ end
51
+
52
+ # @return [TrueClass,FalseClass] true if the execution was a failure.
53
+ def invalid?
54
+ status != 0
55
+ end
56
+
57
+ private
58
+
59
+ def game_execution_string
60
+ "#{Metro.executable_path} #{parameters.join(" ")}"
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,65 @@
1
+ module Metro
2
+ module SetupHandlers
3
+
4
+ #
5
+ # LoadGameConfiguration is a meta pregame setup handler as it defines multiple
6
+ # setup handlers to be executed related to game configuration.
7
+ #
8
+ class GameConfiguration
9
+ #
10
+ # @param [Metro::Parameters::Options] options the options that the game
11
+ # was provided when it was launched.
12
+ #
13
+ def setup(options)
14
+ ParseAndLoadGameConfiguration.new.setup(options)
15
+ ConfigureControls.new.setup(options)
16
+ end
17
+ end
18
+
19
+ #
20
+ # Loads the game configuration information and sets up a Game object with the
21
+ # content loaded from the game configuration.
22
+ #
23
+ class ParseAndLoadGameConfiguration
24
+
25
+ #
26
+ # @param [Metro::Parameters::Options] options the options that the game
27
+ # was provided when it was launched.
28
+ #
29
+ def setup(options)
30
+ filename = options.filename
31
+
32
+ Game.execution_parameters = options.execution_parameters
33
+
34
+ gamefile = File.basename(filename)
35
+ game_files_exist!(gamefile)
36
+ game_contents = File.read(gamefile)
37
+ game_block = lambda {|instance| eval(game_contents) }
38
+ game = Game::DSL.parse(&game_block)
39
+ Game.setup game
40
+ end
41
+
42
+ def game_files_exist!(*files)
43
+ files.compact.flatten.each { |file| game_file_exists?(file) }
44
+ end
45
+
46
+ def game_file_exists?(file)
47
+ error!("error.missing_metro_file",file: file) unless File.exists?(file)
48
+ error!("error.specified_directory",directory: file) if File.directory?(file)
49
+ end
50
+ end
51
+
52
+ #
53
+ # After the game has been configured it is time to configure the controls for
54
+ # the game.
55
+ #
56
+ class ConfigureControls
57
+ def setup(options)
58
+ EventRelay.define_controls Game.controls
59
+ end
60
+ end
61
+ end
62
+
63
+ register_setup_handler SetupHandlers::GameConfiguration.new
64
+
65
+ end
@@ -0,0 +1,101 @@
1
+ require_relative 'game_execution'
2
+
3
+ module Metro
4
+ module SetupHandlers
5
+
6
+ #
7
+ # LoadGameFiles will load all the game files within the current working
8
+ # directory that are in the pre-defined Metro game folders.
9
+ #
10
+ # LoadGameFiles uses ActiveSupport::Dependencies::WatchStack to spy on all
11
+ # the constants that are added when the game runs. This grants the class
12
+ # the ability to provide dynamic reloading of the classes as they change.
13
+ #
14
+ class LoadGameFiles
15
+
16
+ #
17
+ # @param [Metro::Parameters::Options] options the options that the game
18
+ # was provided when it was launched.
19
+ #
20
+ def setup(options)
21
+ load_game_files!
22
+ end
23
+
24
+ #
25
+ # Drop any already defined events. Drop all the existing classes, reload
26
+ # the game files, and prepare the new game files for unloading the next
27
+ # time around that reload has been called.
28
+ #
29
+ def load_game_files!
30
+ EventDictionary.reset!
31
+ Image.images.clear
32
+ Animation.images.clear
33
+ Song.songs.clear
34
+ Font.fonts.clear
35
+
36
+ prepare_watcher!
37
+ load_game_files
38
+ execute_watcher!
39
+ end
40
+
41
+ #
42
+ # Launch the game in a sub-process in dry-run mode. Starting the
43
+ # game in dry-run mode here makes it so it will not launch a window
44
+ # and simply check to see if the code is valid and working correctly.
45
+ #
46
+ def launch_game_in_dry_run_mode
47
+ GameExecution.execute Game.execution_parameters + [ "--dry-run" ]
48
+ end
49
+
50
+ #
51
+ # The watcher should watch the Object Namespace for any changes. Any constants
52
+ # that are added will be tracked from this point forward.
53
+ #
54
+ def prepare_watcher!
55
+ ActiveSupport::Dependencies.clear
56
+ watcher.watch_namespaces([ Object ])
57
+ end
58
+
59
+ #
60
+ # The watcher will keep track of all the constants that were added to the Object
61
+ # Namespace after the start of the execution of the game. This will allow for only
62
+ # those objects to be reloaded.
63
+ #
64
+ def watcher
65
+ @watcher ||= ActiveSupport::Dependencies::WatchStack.new
66
+ end
67
+
68
+ def load_game_files
69
+ $LOAD_PATH.unshift(Dir.pwd) unless $LOAD_PATH.include?(Dir.pwd)
70
+ load_paths 'lib'
71
+ load_path 'scenes', prioritize: 'game_scene.rb'
72
+ load_path 'models', prioritize: 'game_model.rb'
73
+ end
74
+
75
+ def load_paths(*paths)
76
+ paths.flatten.compact.each {|path| load_path path }
77
+ end
78
+
79
+ def load_path(path,options = {})
80
+ files = Dir["#{path}/**/*.rb"]
81
+ files.sort! {|file| File.basename(file) == options[:prioritize] ? -1 : 1 }
82
+ files.each {|file| require_or_load file }
83
+ end
84
+
85
+ #
86
+ # The watcher will now mark all the constants that it has watched being loaded
87
+ # as unloadable. Doing so exhausts the list of constants found so the watcher
88
+ # will be empty.
89
+ #
90
+ # @note an exception is raised if the watcher is not prepared every time this
91
+ # is called.
92
+ #
93
+ def execute_watcher!
94
+ watcher.new_constants.each { |constant| unloadable constant }
95
+ end
96
+ end
97
+ end
98
+
99
+ register_setup_handler SetupHandlers::LoadGameFiles.new
100
+
101
+ end
@@ -0,0 +1,25 @@
1
+ module Metro
2
+ module SetupHandlers
3
+
4
+ #
5
+ # If the filename provide contains path information, then we need to move the
6
+ # current working directory into the root of the game directory. This is
7
+ # important because assets and other various paths are dependent on that
8
+ # being the location during execution.
9
+ #
10
+ class MoveToGameDirectory
11
+ #
12
+ # @param [Metro::Parameters::Options] options the options that the game
13
+ # was provided when it was launched.
14
+ #
15
+ def setup(options)
16
+ filename = options.filename
17
+ game_directory = File.dirname(filename)
18
+ Dir.chdir game_directory
19
+ end
20
+ end
21
+ end
22
+
23
+ register_setup_handler SetupHandlers::MoveToGameDirectory.new
24
+
25
+ end
@@ -0,0 +1,74 @@
1
+ module Metro
2
+ module SetupHandlers
3
+
4
+ #
5
+ # When the game is launched in **debug** mode the game directories will be
6
+ # monitored for changes. When a change occurs within one of the game source
7
+ # files the game and scene will be reloaded.
8
+ #
9
+ class ReloadGameOnGameFileChanges
10
+
11
+ #
12
+ # @NOTE this is duplication of the paths is also defined in LoadGameFiles.
13
+ # @see Metro::SetupHandlers::LoadGameFiles
14
+ #
15
+ def source_filepaths
16
+ [ 'lib', 'scenes', 'models' ]
17
+ end
18
+
19
+ def view_filepaths
20
+ [ 'views' ]
21
+ end
22
+
23
+ def asset_filepaths
24
+ [ 'assets' ]
25
+ end
26
+
27
+ def all_filepaths
28
+ source_filepaths + view_filepaths + asset_filepaths
29
+ end
30
+
31
+ #
32
+ # @param [Metro::Parameters::Options] options the options that the game
33
+ # was provided when it was launched.
34
+ #
35
+ def setup(options)
36
+ start_watcher if Game.debug?
37
+ end
38
+
39
+ def start_watcher
40
+ Thread.abort_on_exception = true
41
+ Thread.new { watch_filepaths(all_filepaths) }
42
+ end
43
+
44
+ #
45
+ # Defines the listener that will watch the filepaths
46
+ #
47
+ def watch_filepaths(filepaths)
48
+ listener = Listen.to(*filepaths)
49
+ listener.change(&on_change)
50
+ listener.start
51
+ end
52
+
53
+ #
54
+ # @return [Proc] the body of code to execute when a file change event has
55
+ # been received.
56
+ #
57
+ def on_change
58
+ Proc.new { |modified,added,removed| reload_game_because_files_changed(modified + added + removed) }
59
+ end
60
+
61
+ #
62
+ # Perform a game reload if the game source is valid.
63
+ #
64
+ def reload_game_because_files_changed(changed)
65
+ log.debug "Metro has detected #{changed.count} game source #{changed.count != 1 ? 'files have' : 'file has'} changed. RELOADING GAME CODE!"
66
+ if Metro.game_has_valid_code?
67
+ Game.current_scene.after(1.tick) { Metro.reload! ; transition_to(scene_name) }
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ register_setup_handler SetupHandlers::ReloadGameOnGameFileChanges.new
74
+ end
@@ -0,0 +1,32 @@
1
+ #
2
+ # The lib folder is a great place for you to define:
3
+ #
4
+ # * New Units
5
+ # * Monkey Patches for Metro
6
+ # * New model properties
7
+ # * Custom Easings
8
+ # * Custom View Parers/Writers
9
+ # * Anything that isn't quite a model, scene, or view
10
+ #
11
+
12
+ #
13
+ # This custom easing is an exact copy of the 'Ease In' easing defined in Metro.
14
+ # It is present here as an example.
15
+ #
16
+ class CustomEasing < Metro::Easing
17
+
18
+ #
19
+ # @param [Float] moment the point in time within the interval. For example for
20
+ # a 60 tick interval, this would be the values 0, 1, 2, 3, 4, 5, 6, ... 59.
21
+ # @param [Float] start the starting value of the property
22
+ # @param [Float] change the final value for the property
23
+ # @param [Float] interval the total length of the interval
24
+ #
25
+ # @return [Float] the value at the particular moment of the interval
26
+ def self.calculation(moment,start,change,interval)
27
+ # @note this is the exact same as the already defined Ease In
28
+ change * (moment = moment / interval) * moment + start
29
+ end
30
+ end
31
+
32
+ Metro::Easing.register :custom, CustomEasing
@@ -15,7 +15,7 @@ name "<%= name %>"
15
15
  # The author of the game. This method can be executed
16
16
  # twice if more people are working on this game.
17
17
  #
18
- author "John Carmack"
18
+ author "Your Name"
19
19
 
20
20
  #
21
21
  # The website where the person playing this game
@@ -42,7 +42,6 @@ resolution 640, 480
42
42
  #
43
43
  first_scene :brand
44
44
 
45
-
46
45
  #
47
46
  # Defines controls which are meta events. They can be used group button
48
47
  # events together under a single name so that you can use it throughout
@@ -61,4 +60,4 @@ end
61
60
  # Game.debug? method will return a true
62
61
  # value.
63
62
  #
64
- # debug true
63
+ debug true
@@ -11,8 +11,9 @@ class GameScene < Metro::Scene
11
11
  #
12
12
  event :on_up, KbR do |event|
13
13
  if event.control?
14
- Metro.reload!
15
- transition_to scene_name
14
+ if Metro.game_has_valid_code?
15
+ after(1.tick) { Metro.reload! ; transition_to(scene_name) }
16
+ end
16
17
  end
17
18
  end
18
19
 
@@ -28,7 +29,7 @@ class GameScene < Metro::Scene
28
29
 
29
30
  #
30
31
  # This animation helper will fade in and fade out information.
31
- #
32
+ #
32
33
  def fade_in_and_out(name)
33
34
  animate name, to: { alpha: 255 }, interval: 2.seconds do
34
35
  after 1.second do
@@ -1,24 +1,23 @@
1
1
  ********************************************************************************
2
- ```
3
- ______ ___ _____
4
- ___ |/ /_____ __ /_______________
5
- __ /|_/ / _ _ \_ __/__ ___/_ __ \
6
- _ / / / / __// /_ _ / / /_/ /
7
- /_/ /_/ \___/ \__/ /_/ \____/
2
+ ______ ___ _____
3
+ ___ |/ /_____ __ /_______________
4
+ __ /|_/ / _ _ \_ __/__ ___/_ __ \
5
+ _ / / / / __// /_ _ / / /_/ /
6
+ /_/ /_/ \___/ \__/ /_/ \____/
8
7
 
9
- ```
10
- -------------------------------------------------------------------------------
8
+ --------------------------------------------------------------------------------
11
9
  <% messages.each do |message| %>
12
10
  ## <%= message.title %>
13
11
 
14
12
  <%= message.message %>
15
13
 
16
- ## Details
14
+ <% unless message.actions.empty? %>## Details
17
15
 
18
- <%= message.actions %>
19
- <% end %>
16
+ <%= message.actions %><% end %><% end %>
17
+ <% if website.present? || email.present? %>
20
18
  ## Contact
21
19
 
22
20
  <%= website %>
23
21
  <%= email %>
22
+ <% end %>
24
23
  ********************************************************************************
data/metro.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
10
10
  gem.version = Metro::VERSION
11
11
  gem.authors = ["Franklin Webber"]
12
12
  gem.email = Metro::CONTACT_EMAILS
13
-
13
+ gem.license = "MIT"
14
14
  gem.summary = <<-EOS
15
15
  Metro is a 2D Gaming framework built around gosu (game development library).
16
16
  Metro makes it easy to create games by enforcing common conceptual structures
@@ -28,6 +28,7 @@ Gem::Specification.new do |gem|
28
28
  gem.add_dependency 'thor', '~> 0.16.0'
29
29
  gem.add_dependency 'i18n', '~> 0.6.1'
30
30
  gem.add_dependency 'active_support', '~> 3.0.0'
31
+ gem.add_dependency 'listen', '~> 0.6.0'
31
32
  gem.add_development_dependency 'rspec', '~> 2.11'
32
33
 
33
34
  gem.files = `git ls-files`.split($/)
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Metro::Parameters::CommandLineArgsParser do
4
+
5
+ describe "Class Methods" do
6
+
7
+ subject { described_class }
8
+
9
+ describe "#parse" do
10
+ context "when given no parameters" do
11
+
12
+ end
13
+
14
+ context "when given an array of parameters" do
15
+
16
+ subject { described_class.parse parameters }
17
+ let(:parameters) { [ '--upset-the-world', expected_filename, '--check-dependencies', 'unread_command' ] }
18
+ let(:expected_filename) { 'metro' }
19
+
20
+ it "should maintain an original parameter list" do
21
+ subject.execution_parameters.should eq parameters
22
+ end
23
+
24
+ it "should consider the first non-flag the game file" do
25
+ subject.filename.should eq expected_filename
26
+ end
27
+
28
+ it "should find all the flags" do
29
+ subject.upset_the_world?.should be_true
30
+ subject.check_dependencies?.should be_true
31
+ end
32
+
33
+ it "should return false when non-existant flags are present" do
34
+ subject.wants_food_mild?.should be_false
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -75,6 +75,22 @@ dependencies:
75
75
  - - ~>
76
76
  - !ruby/object:Gem::Version
77
77
  version: 3.0.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: listen
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.6.0
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.6.0
78
94
  - !ruby/object:Gem::Dependency
79
95
  name: rspec
80
96
  requirement: !ruby/object:Gem::Requirement
@@ -95,7 +111,7 @@ description: ! " Metro is a 2D Gaming framework built around gosu (game devel
95
111
  library).\n Metro makes it easy to create games by enforcing common conceptual
96
112
  structures\n and conventions.\n"
97
113
  email:
98
- - franklin.webber@gmail.com
114
+ - dev@rubymetro.com
99
115
  executables:
100
116
  - metro
101
117
  extensions: []
@@ -103,6 +119,7 @@ extra_rdoc_files: []
103
119
  files:
104
120
  - .gitignore
105
121
  - .rspec
122
+ - .travis.yml
106
123
  - Gemfile
107
124
  - Guardfile
108
125
  - LICENSE.txt
@@ -182,6 +199,9 @@ files:
182
199
  - lib/metro/models/ui/label.rb
183
200
  - lib/metro/models/ui/menu.rb
184
201
  - lib/metro/models/ui/rectangle.rb
202
+ - lib/metro/parameters/command_line_args_parser.rb
203
+ - lib/metro/parameters/options.rb
204
+ - lib/metro/parameters/parameters.rb
185
205
  - lib/metro/sample.rb
186
206
  - lib/metro/scene.rb
187
207
  - lib/metro/scenes.rb
@@ -206,9 +226,16 @@ files:
206
226
  - lib/metro/views/writers.rb
207
227
  - lib/metro/views/yaml_view.rb
208
228
  - lib/metro/window.rb
229
+ - lib/setup_handlers/exit_if_dry_run.rb
230
+ - lib/setup_handlers/game_execution.rb
231
+ - lib/setup_handlers/load_game_configuration.rb
232
+ - lib/setup_handlers/load_game_files.rb
233
+ - lib/setup_handlers/move_to_game_directory.rb
234
+ - lib/setup_handlers/reload_game_on_game_file_changes.rb
209
235
  - lib/templates/game/README.md.tt
210
236
  - lib/templates/game/assets/brand.jpg
211
237
  - lib/templates/game/assets/hero.png
238
+ - lib/templates/game/lib/custom_easing.rb
212
239
  - lib/templates/game/metro.tt
213
240
  - lib/templates/game/models/hero.rb
214
241
  - lib/templates/game/scenes/brand_scene.rb
@@ -239,6 +266,7 @@ files:
239
266
  - spec/metro/models/properties/options_property/options_spec.rb
240
267
  - spec/metro/models/properties/position_property_spec.rb
241
268
  - spec/metro/models/ui/label_spec.rb
269
+ - spec/metro/parameters/command_line_args_parser_spec.rb
242
270
  - spec/metro/scene_spec.rb
243
271
  - spec/metro/scene_views/json_view_spec.rb
244
272
  - spec/metro/scene_views/yaml_view_spec.rb
@@ -246,13 +274,17 @@ files:
246
274
  - spec/metro/views/view_spec.rb
247
275
  - spec/spec_helper.rb
248
276
  homepage: https://github.com/burtlo/metro
249
- licenses: []
277
+ licenses:
278
+ - MIT
250
279
  post_install_message: ! " ______ ___ _____\n ___ |/ /_____ __ /_______________\n
251
280
  \ __ /|_/ / _ _ \\_ __/__ ___/_ __ \\\n _ / / / / __// /_ _ / /
252
281
  /_/ /\n /_/ /_/ \\___/ \\__/ /_/ \\____/\n\n Thank you for installing
253
- metro 0.3.1 / 2012-11-25.\n ---------------------------------------------------------------------\n
254
- \ Changes:\n \n * FIX issue with some Gosu example code left in the oven\n \n\n
255
- \ ---------------------------------------------------------------------\n"
282
+ metro 0.3.2 / 2012-11-26.\n ---------------------------------------------------------------------\n
283
+ \ Changes:\n \n * Debug Mode will now automatically reload the game and scene
284
+ on source\n file changes.\n * Reloading the game will no longer take down the
285
+ app for syntax errors\n and other errors that are easily detected by simply loading
286
+ the code.\n * Template game is now automatically has debug mode enabled by default\n
287
+ \ \n\n ---------------------------------------------------------------------\n"
256
288
  rdoc_options: []
257
289
  require_paths:
258
290
  - lib
@@ -291,6 +323,7 @@ test_files:
291
323
  - spec/metro/models/properties/options_property/options_spec.rb
292
324
  - spec/metro/models/properties/position_property_spec.rb
293
325
  - spec/metro/models/ui/label_spec.rb
326
+ - spec/metro/parameters/command_line_args_parser_spec.rb
294
327
  - spec/metro/scene_spec.rb
295
328
  - spec/metro/scene_views/json_view_spec.rb
296
329
  - spec/metro/scene_views/yaml_view_spec.rb