shawn42/gamebox

View on GitHub
lib/gamebox/core/viewport.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# Viewport represents the current "camera" location.  Essensially it translates from
# world to screen coords and from screen coords to world coords.
class Viewport
  extend Publisher
  can_fire :scrolled

  construct_with :config_manager
  
  attr_accessor :x_offset, :y_offset, :follow_target, :width,
    :height, :x_offset_range, :y_offset_range, :boundary, :rotation

  attr_reader :speed
  alias :follow_target? :follow_target

  def debug
    "xoff:#{@x_offset} yoff:#{@y_offset}"
  end

  def initialize
    res = config_manager[:screen_resolution]
    @width = res[0]
    @height = res[1]
    reset
  end

  def reset
    @rotation = 0
    @speed = 1
    @x_offset = 0
    @y_offset = 0
  end

  def scroll(x_delta,y_delta)
    @x_offset += x_delta
    @y_offset += y_delta

    fire :scrolled
  end

  def speed=(new_speed)
    if new_speed > 1
      @speed = 1
    elsif new_speed < 0
      @speed = 0
    else
      @speed = new_speed
    end
  end

  def x_offset(layer=1)
    return 0 if layer == Float::INFINITY
    return @x_offset if layer == 1
    @x_offset / layer
  end

  def y_offset(layer=1)
    return 0 if layer == Float::INFINITY
    return @y_offset if layer == 1
    @y_offset / layer
  end

  def update(time)
    if follow_target?
      scrolled = move_towards_target
      clamp_to_boundary
      fire :scrolled if scrolled
    end
  end

  # Viewport will stay centered on the targets x,y
  #
  # args hash:
  # :x_offset - keep the viewport centered on x + x_offset
  # :y_offset - keep the viewport centered on y + y_offset
  # :x_chain_length - allow this much x slack when following
  # :y_chain_length - allow this much y slack when following
  def stay_centered_on(target, args={})
    offset_x = args[:x_offset] || 0
    offset_y = args[:y_offset] || 0
    x_chain_length = args[:x_chain_length] || 0
    y_chain_length = args[:y_chain_length] || 0
    follow target, [-offset_x, -offset_y], [x_chain_length, y_chain_length]
  end

  # deprecated; use #stay_centered_on
  def follow(target, off=[0,0], buff=[0,0])
    @follow_target = target
    @follow_offset_x = off[0]
    @follow_offset_y = off[1]
    @buffer_x = buff[0]
    @buffer_y = buff[1]

    @x_offset = @width/2 - @follow_target.x + @follow_offset_x
    @y_offset = @height/2 - @follow_target.y + @follow_offset_y

    fire :scrolled
  end

  def bounds
    left = -@x_offset
    top = -@y_offset
    # Rect.new left, top, left + @width, top + @height
    Rect.new left, top, @width, @height
  end

  private
  def move_towards_target
    scrolled = false

    x = @follow_target.x
    y = @follow_target.y

    if @x_offset_range
      x = @x_offset_range.min if @x_offset_range.min > x 
      x = @x_offset_range.max if @x_offset_range.max < x 
    end
    if @y_offset_range
      y = @y_offset_range.min if @y_offset_range.min > y 
      y = @y_offset_range.max if @y_offset_range.max < y 
    end
    x_diff = @width/2 + @follow_offset_x - x - @x_offset
    if x_diff.abs > @buffer_x
      # move screen 
      if x_diff > 0
        @x_offset += (x_diff - @buffer_x) * @speed
      else
        @x_offset += (x_diff + @buffer_x) * @speed
      end

      scrolled = true
    end

    y_diff = @height/2 + @follow_offset_y - y - @y_offset
    if y_diff.abs > @buffer_y
      # move screen
      if y_diff > 0
        @y_offset += (y_diff - @buffer_y) * @speed
      else
        @y_offset += (y_diff + @buffer_y) * @speed
      end
      scrolled = true
    end

    scrolled
  end

  def clamp_to_boundary
    if @boundary
      if @x_offset > 0 - @boundary[0] # Left-wall bump
        @x_offset = @boundary[0]
      elsif @x_offset < @width - @boundary[2] # right-wall bump
        @x_offset = @width - @boundary[2]
      end

      if @y_offset > 0 - @boundary[1]
        @y_offset = @boundary[1]
      elsif @y_offset < @height - @boundary[3]
        @y_offset = @height - @boundary[3]
      end
    end
  end

end