Search code examples
rubylibgosu

Looping background image in Ruby side-scrolling game


I want to loop the background image in a Ruby gosu side scrolling game.I have problem with the counters @k and @p which are used to translate the background image and the duplicated background image.I can't think of a good way to plus them.Here's the code to make it more clear.

require 'gosu'

class GameWindow < Gosu::Window
attr_accessor :x, :y

SHIFT = 700

 def initialize
  super 640,440
  @background  = Gosu::Image.new("./images/bg.png")
  @player      = Gosu::Image.new("./images/000.png")
  @x1, @y1 = 0, 0
  @player_x, @player_y = 50, 50
  @k = 0    #counter
  @p = 1    #counter
 end

 def update
  @x1 -= 3
  @player_y += 1 if @player_y <= 375
  @player_y -= 3 if button_down?(Gosu::KbSpace) and @player_y >= 0

  @coordinates = Gosu::Image.from_text(
    self, "#{@x1},#{@k}", Gosu.default_font_name, 30)
  #here should be the code for @k and @p
 end

 def draw
  @background.draw(@x1  + @k*SHIFT, @y1, 0)
  @background.draw(@x1 +  @p*SHIFT, @y1, 0)
  @player.draw(@player_x, @player_y, 0)
  @coordinates.draw(0, 0, 1)
 end

 def button_down(id)
  $window.close if id == Gosu::KbEscape
 end

end

window = GameWindow.new
window.show

So how do I plus the counters @k and @p.Tried this

if @x1 > -(SHIFT+5)*@p and @x1 < -SHIFT*@p  #705 and 700
  @k += 2
end

if @k > 0 and @x1 > -SHIFT*@k - 5 and @x1 < -SHIFT*@k - 3  #1405 and 1403
  @p += 2 
end

but it works only in the beginning(2-3 image shifts). Also tried this

if @x1 == -SHIFT*@p
  @k += 2
end

and it did not work.


Solution

  • Assumptions & Analysis

    I'm assuming that @x1 and @y1 are the offset of the left-most background's origin relative to the screen. You also have @player_x and @player_y, which denote the player's position on the screen. Your player is kept horizontally in the center of the screen, moving vertically when jumping or falling, and your background only scrolls horizontally. Also, your window size is 640x440, and your background image is 700px wide and at least 440px tall.

    Your update step handles things like jumping (but only to the top of the screen) and simple, constant-velocity gravity, both by modifying the player's screen coordinates. It also provides a constant horizontal scroll by subtracting 3 units per frame from @x1, the horizontal camera coordinate in world-space.

    Your draw step then takes the background image and draws two of them, offsetting them by the camera offset @x1 plus the shift for which image is which. This shift, however, isn't important, because we can calculate it fairly simply, and we're left without having to manipulate the additional state of remembering which one is which.

    Code Solution

    Instead of remembering the counter values @k and @p, we're going to just modulo by the image width to eliminate the excess in @x1 and get it where we need it to be. @k and @p are useless, so delete those. SHIFT can be deleted as well.

    Instead, we need to do the following:

    • Calculate the screen offset of the left-most image
    • Determine if two images need to be drawn instead of just one
    • Draw the image(s)

    Our goal looks something like this:

    sliding background

    Our new code:

    require 'gosu'
    
    class GameWindow < Gosu::Window
      attr_accessor :x, :y # side note - are these used at all?
    
      def initialize
        super 640,440
        @background  = Gosu::Image.new("./images/bg.png")
        @player      = Gosu::Image.new("./images/000.png")
        @x1, @y1 = 0, 0
        @player_x, @player_y = 50, 50
      end
    
      def update
        @x1 -= 3
        @player_y += 1 if @player_y <= 375
        @player_y -= 3 if button_down?(Gosu::KbSpace) and @player_y >= 0
    
        @coordinates = Gosu::Image.from_text(
          self, "#{@x1}", Gosu.default_font_name, 30)
      end
    
      def draw
        # the important bits!
        @local_x = @x1 % -@background.width
        @background.draw(@local_x, @y1, 0)
        @background.draw(@local_x + @background.width, @y1, 0) if @local_x < (@background.width - self.width)
    
        @player.draw(@player_x, @player_y, 0)
        @coordinates.draw(0, 0, 1)
      end
    
      def button_down(id) # Side note: Does this work correctly?
        $window.close if id == Gosu::KbEscape
      end
    
    end
    
    window = GameWindow.new
    window.show
    

    What this does is, it takes @x1 and modulos it with the (negative) width of the image. This means, it gives the remainder of an integer division of the two numbers. Modulo is very useful when trying to ensure that an integer value "wraps around" after exceeding a limit in either direction; it effectively maps it to between 0 (inclusive) and the given number (exclusive). The results of this are illustrated in the graphic above.

    Keep in mind this solution only works as-is for your circumstances; vertical scrolling will require more code, and this will break pretty quickly if your window becomes wider than your background image.