/lib/chingu/parallax.rb

http://github.com/ippa/chingu · Ruby · 209 lines · 89 code · 30 blank · 90 comment · 20 complexity · 70d05e156cc27c0c5e05b8d52c472cec MD5 · raw file

  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. module Chingu
  22. #
  23. # Class for simple parallaxscrolling
  24. #
  25. # See http://en.wikipedia.org/wiki/Parallax_scrolling for information about parallaxscrolling.
  26. #
  27. # Basic usage:
  28. # @parallax = Chingu::Parallax.create(:x => 0, :y => 0)
  29. # @parallax << Chingu::ParallaxLayer.new(:image => "far_away_mountins.png", :damping => 20, :center => 0)
  30. # @parallax << Chingu::ParallaxLayer.new(:image => "trees.png", :damping => 5, :center => 0)
  31. #
  32. class Parallax < Chingu::GameObject
  33. attr_reader :layers
  34. #
  35. # Options (in hash-format):
  36. #
  37. # repeat_x: [true|false] repeat layer on X-axis
  38. # repeat_y: [true|false] repeat layer on Y-axis
  39. #
  40. def initialize(options = {})
  41. super(options)
  42. @repeat_x = options[:repeat_x] || true
  43. @repeat_y = options[:repeat_y] || false
  44. @layers = Array.new
  45. end
  46. #
  47. # Add one layer, either an ParallaxLayer-object or a Hash of options to create one
  48. # You can also add new layers with the shortcut "<<":
  49. # @parallax << {:image => "landscape.png", :damping => 1}
  50. #
  51. def add_layer(arg)
  52. @layers << (arg.is_a?(ParallaxLayer) ? arg : ParallaxLayer.new(arg.merge({:parallax => self})))
  53. end
  54. alias << add_layer
  55. #
  56. # returns true if any part of the parallax-scroller is inside the window
  57. #
  58. def inside_window?
  59. return true if @repeat_x || @repeat_y
  60. @layers.each { |layer| return true if layer.inside_window? }
  61. return false
  62. end
  63. #
  64. # Returns true if all parallax-layers are outside the window
  65. #
  66. def outside_window?
  67. not inside_window?
  68. end
  69. #
  70. # Parallax#camera_x= works in inverse to Parallax#x (moving the "camera", not the image)
  71. #
  72. def camera_x=(x)
  73. @x = -x
  74. end
  75. #
  76. # Parallax#camera_y= works in inverse to Parallax#y (moving the "camera", not the image)
  77. #
  78. def camera_y=(y)
  79. @y = -y
  80. end
  81. #
  82. # Get the x-coordinate for the camera (inverse to x)
  83. #
  84. def camera_x
  85. -@x
  86. end
  87. #
  88. # Get the y-coordinate for the camera (inverse to y)
  89. #
  90. def camera_y
  91. -@y
  92. end
  93. #
  94. # TODO: make use of $window.milliseconds_since_last_update here!
  95. #
  96. def update
  97. @layers.each do |layer|
  98. layer.x = @x / layer.damping
  99. layer.y = @y / layer.damping
  100. # This is the magic that repeats the layer to the left and right
  101. layer.x -= layer.image.width while (layer.repeat_x && layer.x > 0)
  102. # This is the magic that repeats the layer to the left and right
  103. layer.y -= layer.image.height while (layer.repeat_y && layer.y > 0)
  104. end
  105. end
  106. #
  107. # Draw
  108. #
  109. def draw
  110. @layers.each do |layer|
  111. save_x, save_y = layer.x, layer.y
  112. # If layer lands inside our window and repeat_x is true (defaults to true), draw it until window ends
  113. while layer.repeat_x && layer.x < $window.width
  114. while layer.repeat_y && layer.y < $window.height
  115. layer.draw
  116. layer.y += layer.image.height
  117. end
  118. layer.y = save_y
  119. layer.draw
  120. layer.x += layer.image.width
  121. end
  122. # Special loop for when repeat_y is true but not repeat_x
  123. if layer.repeat_y && !layer.repeat_x
  124. while layer.repeat_y && layer.y < $window.height
  125. layer.draw
  126. layer.y += layer.image.height
  127. end
  128. end
  129. layer.x = save_x
  130. end
  131. self
  132. end
  133. end
  134. #
  135. # ParallaxLayer is mainly used by class Parallax to keep track of the different layers.
  136. # If you @parallax << { :image => "foo.png" } a ParallaxLayer will be created automaticly from that Hash.
  137. #
  138. # If no zorder is provided the ParallaxLayer-class increments an internal zorder number which will
  139. # put the last layer added on top of the rest.
  140. #
  141. class ParallaxLayer < Chingu::GameObject
  142. attr_reader :damping
  143. attr_accessor :repeat_x, :repeat_y
  144. def initialize(options)
  145. @parallax = options[:parallax]
  146. # No auto update/draw, the parentclass Parallax takes care of that!
  147. options.merge!(:visible => false, :paused => true)
  148. options = {:rotation_center => @parallax.options[:rotation_center]}.merge(options) if @parallax
  149. #
  150. # Default arguments for repeat_x and repeat_y
  151. # If no zorder is given, use a global incrementing counter.
  152. # First added, furthest behind when drawn.
  153. #
  154. options = {
  155. :repeat_x => true,
  156. :repeat_y => false,
  157. :zorder => @parallax ? (@parallax.zorder + @parallax.layers.count) : 100
  158. }.merge(options)
  159. @repeat_x = options[:repeat_x]
  160. @repeat_y = options[:repeat_y]
  161. super(options)
  162. @damping = options[:damping] || 1
  163. end
  164. #
  165. # Gets pixel from layers image
  166. # The pixel is from the window point of view, so coordinates are converted:
  167. #
  168. # @parallax.layers.first.get_pixel(10, 10) # the visible pixel at 10, 10
  169. # @parallax.layers.first.image.get_pixel(10, 10) # gets pixel 10, 10 from layers image no matter where layer is positioned
  170. #
  171. def get_pixel(x, y)
  172. image_x = x - @x
  173. image_y = y - @y
  174. # On a 100 x 100 image, get_pixel works to 99 x 99
  175. image_x -= @image.width while image_x >= @image.width
  176. @image.get_pixel(image_x, image_y)
  177. end
  178. end
  179. end