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