lib/ray/scene.rb in ray-0.0.0.pre2 vs lib/ray/scene.rb in ray-0.0.1
- old
+ new
@@ -1,102 +1,271 @@
module Ray
+ # Scenes contain the main logic of a game.
+ #
+ # You can define a new scene using a block, which will be called every time
+ # your scene is about to be used. However, you may also want to subclass
+ # Ray::Scene. When doing this, you'll probably want to override the register
+ # method:
+ # def register
+ # on :some_event do some_stuff end
+ # end
+ #
+ # Another method is called before register: setup. Putting code in register or
+ # in setup doesn't matter, but setting the scene up inside register method
+ # seems (and, indeed, is) inappropriate. You can override it:
+ # def setup
+ # @sprite = sprite("image.png")
+ # end
+ #
+ # You can indicate how your scene should be rendered there:
+ # render do |win|
+ # # Do drawing here
+ # end
+ #
+ # Or you can override render:
+ # def render(win)
+ # # Do drawing here
+ # end
+ #
+ # Notice win is not filled with an empty color when render is called, i.e.
+ # it still contains the frame which appears to the user.
+ #
+ # Also, scenes are rendered lazily: only once when the scene is created,
+ # and then every time need_render! is called.
+ #
+ # Once your scene is loaded, you'll probably want to clean it up (set some
+ # instance variables to nil so they can be garbaged collected for instance).
+ # You can do that by passing a block to clean_up:
+ # clean_up do
+ # @some_big_resource = nil
+ # end
+ #
+ # Or by overriding it:
+ # def clean_up
+ # @some_big_resource = nil
+ # end
+ #
+ # == Managing the stack of scenes
+ # exit is called when you want to stop running the scene, but not to remove
+ # the last scene from the stack. It is useful if you want to push a new
+ # scene. Hence Ray::Scene#push_scene will call exit.
+ #
+ # exit! (or pop_scene), on the other hand, is used to go back to the previous
+ # scene in the hierarchy
+ #
+ # == Sending informations to a scene
+ # Scenes may need some arguments to work. You can pass those in push_scene:
+ # push_scene(:polygon, 6, Ray::Color.red)
+ # Then you can use them with scene_arguments:
+ # scene :polygon do
+ # sides, color = scene_arguments
+ # # ...
+ # end
+ #
+ # They are also passed to #setup:
+ # def setup(sides, color)
+ # # ...
+ # end
+ #
+ # == Limiting the loop rate
+ # You can prevent a scene from always running by using #loops_per_second=:
+ # self.loops_per_second = 30 # will sleep some time after each loop
+ #
+ # @see Ray::DSL::EventTranslator
class Scene
include Ray::Helper
class << self
- # If you want to subclass Scene, this is the method you need to call.
- # It will return a subclass, responding to bind, which allows you to
- # register it.
- #
- # @example
- # Klass = Scene.create(:foo) do ... end
- # Klass.bind(game)
- # game.push_scene(:foo)
- def create(scene_name, &block)
- klass = Class.new(self)
- klass.instance_variable_set("@block", block)
+ # Registers a scene to a game object, used for subclasses.
+ def bind(game)
+ game.scene(scene_name, self)
+ end
- (class << klass; self; end).class_eval do
- define_method(:bind) do |game|
- game.scene(scene_name, self, &@block)
- end
+ # @overload scene_name
+ # @return [Symbol] the name of the scene
+ # @overload scene_name(value)
+ # Sets the name of the scene
+ def scene_name(val = nil)
+ @scene_name = val || @scene_name
+ end
+ end
- define_method(:inspect) { "Scene:#{scene_name}" }
- define_method(:block) { @block }
- end
+ scene_name :scene
- klass.class_eval do
- define_method(:initialize) do
- super(&self.class.block)
- end
- end
+ # Creates a new scene. block will be instance evaluated when
+ # this scene becomes the current one.
+ def initialize(&block)
+ @scene_register_block = block
+ end
- return klass
+ def register_events
+ @scene_held_keys = []
+
+ on :key_press do |key, mod|
+ @scene_held_keys << key
end
+
+ on :key_release do |key, mod|
+ @scene_held_keys.reject! { |i| i == key }
+ end
+
+ if @scene_register_block
+ instance_eval(&@scene_register_block)
+ else
+ register
+ end
+
+ @scene_exit = false
end
- # Creates a new scene. block will be instance evaluated
- # every time the current scene changes.
- def initialize(&block)
- @exit = false
- @block = block
+ # Override this method in subclasses to setup the initial state
+ # of your scene.
+ def setup(*args)
end
- def register_events
- instance_eval(&@block)
+ # Override this method in subclasses to register your own events
+ def register
end
+ # @param [Symbol, Integer] val A symbol to find the key (its name)
+ # or an integer (Ray::Event::KEY_*)
+ #
+ # @return [true, false] True if the user is holding key.
+ def holding?(val)
+ if val.is_a? Symbol
+ val = key(val)
+ @scene_held_keys.any? { |o| val === o }
+ elsif val.is_a? DSL::Matcher
+ @scene_held_keys.any? { |o| val === o }
+ else
+ @scene_held_keys.include? val
+ end
+ end
+
# Runs until you exit the scene.
# This will also raise events if the mouse moves, ... allowing you
# to directly listen to a such event.
def run
- until @exit
- ev = DSL::EventTranslator.translate_event(Ray::Event.new)
- raise_event(*ev) if ev
+ until @scene_exit
+ loop_start = Time.now
- @always.call if @always
+ DSL::EventTranslator.translate_event(Ray::Event.new).each do |args|
+ raise_event(*args)
+ end
+ @scene_always_block.call if @scene_always_block
+
listener_runner.run
- if @need_render
- @need_render = false
+ if @scene_need_render
+ @scene_need_render = false
- @render.call(@window)
- @window.flip
+ render(@scene_window)
+ @scene_window.flip
end
+
+ if @scene_loops_per_second
+ ellapsed_time = Time.now - loop_start
+ time_per_loop = 1.0 / @scene_loops_per_second
+
+ sleep(time_per_loop - ellapsed_time) if ellapsed_time < time_per_loop
+ end
end
+
+ clean_up
end
- # Exits the scene, but does not pops the scene (the next scene
- # will be the same one)
+ # Exits the scene, but does not pop the scene.
+ #
+ # You may want to call this if you pushed a new scene, to switch to
+ # the new scene.
def exit
- @exit = true
+ @scene_exit = true
end
- # Exits the scene and pops it.
+ # Exits the scene and pops it (may not work as expected if the current
+ # scene is not the last one)
def exit!
exit
game.pop_scene
end
- # Register a block to be excuted as often as possible.
+ # Registers a block to be excuted as often as possible.
def always(&block)
- @always = block
+ @scene_always_block = block
end
# Marks the scene should be redrawn.
def need_render!
- @need_render = true
+ @scene_need_render = true
end
# Registers the block to draw the scene.
#
+ # Does nothing if no block is given, this method being called if you
+ # didn't register any render block. You can thus override it in subclasses
+ # instead of providing a block to it.
+ #
# @yield [window] Block to render this scene.
# @yieldparam [Ray::Image] window The window you should draw on
- def render(&block)
- @render = block
+ def render(win = nil, &block)
+ if block_given?
+ @scene_render_block = block
+ else
+ @scene_render_block.call(win) if @scene_render_block
+ end
end
- attr_accessor :game
- attr_accessor :window
+ # Pushes a scene in the stack, and exits that one
+ def push_scene(scene, *args)
+ game.push_scene(scene, *args)
+ exit
+ end
+
+ # @see Ray::Game#resize_window
+ def resize_window(w, h)
+ game.resize_window(w, h)
+ end
+
+ # Cleans the scene or registers a block to clean it.
+ def clean_up(&block)
+ if block_given?
+ @scene_clean_block = block
+ else
+ @scene_clean_block.call if @scene_clean_block
+ end
+ end
+
+ def inspect
+ "#<#{self.class} game=#{self.game.inspect}>"
+ end
+
+ alias :pop_scene :exit!
+
+ def game
+ @scene_game
+ end
+
+ def game=(val)
+ @scene_game = val
+ end
+
+ def window
+ @scene_window
+ end
+
+ def window=(val)
+ @scene_window = val
+ end
+
+ def loops_per_second
+ @scene_loops_per_second
+ end
+
+ def loops_per_second=(val)
+ @scene_loops_per_second = val
+ end
+
+ # The arguments passed to the scene with push_scene
+ attr_accessor :scene_arguments
end
end