lib/gamebox/core/viewport.rb
# 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