PageRenderTime 39ms CodeModel.GetById 11ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/chingu/parallax.rb

http://github.com/ippa/chingu
Ruby | 209 lines | 89 code | 30 blank | 90 comment | 20 complexity | 70d05e156cc27c0c5e05b8d52c472cec MD5 | raw file
Possible License(s): LGPL-2.1
  1#--
  2#
  3# Chingu -- OpenGL accelerated 2D game framework for Ruby
  4# Copyright (C) 2009 ippa / ippa@rubylicio.us
  5#
  6# This library is free software; you can redistribute it and/or
  7# modify it under the terms of the GNU Lesser General Public
  8# License as published by the Free Software Foundation; either
  9# version 2.1 of the License, or (at your option) any later version.
 10#
 11# This library is distributed in the hope that it will be useful,
 12# but WITHOUT ANY WARRANTY; without even the implied warranty of
 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 14# Lesser General Public License for more details.
 15#
 16# You should have received a copy of the GNU Lesser General Public
 17# License along with this library; if not, write to the Free Software
 18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 19#
 20#++
 21
 22module Chingu
 23  #
 24  # Class for simple parallaxscrolling
 25  #
 26  # See http://en.wikipedia.org/wiki/Parallax_scrolling for information about parallaxscrolling.
 27  #
 28  # Basic usage:
 29  #   @parallax = Chingu::Parallax.create(:x => 0, :y => 0)
 30  #   @parallax << Chingu::ParallaxLayer.new(:image => "far_away_mountins.png", :damping => 20, :center => 0)
 31  #   @parallax << Chingu::ParallaxLayer.new(:image => "trees.png", :damping => 5, :center => 0)
 32  #
 33  class Parallax < Chingu::GameObject
 34    attr_reader :layers
 35
 36    #
 37    # Options (in hash-format):
 38    #
 39    # repeat_x: [true|false]  repeat layer on X-axis
 40    # repeat_y: [true|false]  repeat layer on Y-axis
 41    #
 42    def initialize(options = {})
 43      super(options)
 44      @repeat_x = options[:repeat_x] || true
 45      @repeat_y = options[:repeat_y] || false
 46      
 47      @layers = Array.new
 48    end
 49    
 50    #
 51    # Add one layer, either an ParallaxLayer-object or a Hash of options to create one
 52    # You can also add new layers with the shortcut "<<":
 53    #   @parallax << {:image => "landscape.png", :damping => 1}
 54    #
 55    def add_layer(arg)
 56      @layers << (arg.is_a?(ParallaxLayer) ? arg : ParallaxLayer.new(arg.merge({:parallax => self})))
 57    end
 58    alias << add_layer
 59
 60    
 61    #
 62    # returns true if any part of the parallax-scroller is inside the window
 63    #
 64    def inside_window?
 65      return true if @repeat_x || @repeat_y
 66      @layers.each { |layer| return true if layer.inside_window? }
 67      return false
 68    end
 69
 70    #
 71    # Returns true if all parallax-layers are outside the window
 72    #
 73    def outside_window?
 74      not inside_window?
 75    end
 76    
 77    #
 78    # Parallax#camera_x= works in inverse to Parallax#x (moving the "camera", not the image)
 79    #
 80    def camera_x=(x)
 81      @x = -x
 82    end
 83
 84    #
 85    # Parallax#camera_y= works in inverse to Parallax#y (moving the "camera", not the image)
 86    #
 87    def camera_y=(y)
 88      @y = -y
 89    end
 90
 91    #
 92    # Get the x-coordinate for the camera (inverse to x)
 93    #
 94    def camera_x
 95      -@x
 96    end
 97
 98    #
 99    # Get the y-coordinate for the camera (inverse to y)
100    #
101    def camera_y
102      -@y
103    end
104    
105    #
106    # TODO: make use of $window.milliseconds_since_last_update here!
107    #
108    def update
109      @layers.each do |layer|
110        layer.x = @x / layer.damping
111        layer.y = @y / layer.damping
112        
113        # This is the magic that repeats the layer to the left and right
114        layer.x -= layer.image.width  while (layer.repeat_x && layer.x > 0)
115       
116        # This is the magic that repeats the layer to the left and right
117        layer.y -= layer.image.height while (layer.repeat_y && layer.y > 0)
118      end
119    end
120    
121    #
122    # Draw 
123    #
124    def draw
125      @layers.each do |layer|
126        save_x, save_y = layer.x, layer.y
127        
128        # If layer lands inside our window and repeat_x is true (defaults to true), draw it until window ends
129        while layer.repeat_x && layer.x < $window.width
130          while layer.repeat_y && layer.y < $window.height
131            layer.draw
132            layer.y += layer.image.height
133          end
134          layer.y = save_y
135          
136          layer.draw
137          layer.x += layer.image.width
138        end
139        
140        # Special loop for when repeat_y is true but not repeat_x
141        if layer.repeat_y && !layer.repeat_x
142          while layer.repeat_y && layer.y < $window.height
143            layer.draw
144            layer.y += layer.image.height
145          end
146        end
147
148        layer.x = save_x
149      end
150      self
151    end
152  end
153  
154  #
155  # ParallaxLayer is mainly used by class Parallax to keep track of the different layers.
156  # If you @parallax << { :image => "foo.png" } a ParallaxLayer will be created automaticly from that Hash.
157  #
158  # If no zorder is provided the ParallaxLayer-class increments an internal zorder number which will
159  # put the last layer added on top of the rest.
160  #
161  class ParallaxLayer < Chingu::GameObject    
162    attr_reader :damping
163    attr_accessor :repeat_x, :repeat_y
164    
165    def initialize(options)      
166      @parallax = options[:parallax]      
167      # No auto update/draw, the parentclass Parallax takes care of that!
168      options.merge!(:visible => false, :paused => true)
169    
170      options = {:rotation_center => @parallax.options[:rotation_center]}.merge(options)  if @parallax
171      
172      #
173      # Default arguments for repeat_x and repeat_y
174      # If no zorder is given, use a global incrementing counter. 
175      # First added, furthest behind when drawn.
176      #
177      options = {
178          :repeat_x => true, 
179          :repeat_y => false, 
180          :zorder   => @parallax ? (@parallax.zorder + @parallax.layers.count) : 100
181      }.merge(options)
182            
183      @repeat_x = options[:repeat_x]
184      @repeat_y = options[:repeat_y]
185            
186      super(options)
187      
188      @damping = options[:damping] || 1
189    end
190        
191    #
192    # Gets pixel from layers image
193    # The pixel is from the window point of view, so coordinates are converted:
194    #
195    #   @parallax.layers.first.get_pixel(10, 10)        # the visible pixel at 10, 10
196    #   @parallax.layers.first.image.get_pixel(10, 10)  # gets pixel 10, 10 from layers image no matter where layer is positioned
197    #
198    def get_pixel(x, y)
199      image_x = x - @x
200      image_y = y - @y
201      
202      # On a 100 x 100 image, get_pixel works to 99 x 99
203      image_x -= @image.width   while image_x >= @image.width 
204      
205      @image.get_pixel(image_x, image_y)
206    end
207    
208  end
209end