class Sprite < Widget
attr_reader :left, :top, :width, :height, :canvas,
:velocity_x, :velocity_y,
:max_velocity_x, :max_velocity_y,
:min_velocity_x, :min_velocity_y,
:gravity_x, :gravity_y,
:friction_x, :friction_y
alias_method :x, :left
alias_method :y, :top
def initialize( args={}, &block )
super()
@args = { :width => 32,
:height => 32,
:top => 0,
:left => 0 }.merge!( args )
@left, @top = @args[:left], @args[:top]
@width, @height = @args[:width], @args[:height]
@velocity_x, @velocity_y = 0, 0
@max_velocity_x, @max_velocity_y = 200, 200
@min_velocity_x, @min_velocity_y = 4, 4
@gravity_x, @gravity_y = 0, 80
@friction_x, @friction_y = 0.70, 0.70
@canvas = image args
@canvas.instance_eval( &block )
@canvas.animate( 24 ) do |n|
now = Time.now.to_f
@last_time ||= now
update_movement( now - @last_time )
@last_time = now
end
end
def siblings
parent.contents.select { |c| c != self }
end
def relations
siblings.unshift parent
end
def gravity( gx, gy )
@gravity_x, @gravity_y = gx, gy
@stuck = false
end
def push( px, py )
@velocity_x += px
@velocity_y += py
@velocity_x = @max_velocity_x if @velocity_x > @max_velocity_x
@velocity_y = @max_velocity_y if @velocity_y > @max_velocity_y
@velocity_x = -@max_velocity_x if @velocity_x < -@max_velocity_x
@velocity_y = -@max_velocity_y if @velocity_y < -@max_velocity_y
@stuck = false
end
def update_bounce( dx, dy )
if parent.style[:bouncy]
pw, ph = parent.width, parent.height
if @left < 0
@left = 1
if @gravity_x < 0 &&
(@gravity_x * @friction_x - @velocity_x) < @min_velocity_x
@velocity_x = 0
else
@velocity_x = -@velocity_x
end
elsif @left + @width > pw
@left = pw - @width - 1
if @gravity_x > 0 &&
(@velocity_x - @gravity_x * @friction_x) < @min_velocity_x
@velocity_x = 0
else
@velocity_x = -@velocity_x
end
end
if @top < 0
@top = 1
if @gravity_y < 0 &&
(@gravity_y * @friction_y - @velocity_y) < @min_velocity_y
@velocity_y = 0
else
@velocity_y = -@velocity_y
end
elsif @top + @height > ph
@top = ph - @height - 1
if @gravity_y > 0 &&
(@velocity_y - @gravity_y * @friction_y) < @min_velocity_y
@velocity_y = 0
else
@velocity_y = -@velocity_y
end
end
end
siblings.each do |sib|
next unless sib.style[:bouncy]
sx, sy, sw, sh = sib.left, sib.top, sib.width, sib.height
rebound = sib.style[:push]
unless @left + @width <= sx || @left >= sx + sw ||
@top + @height <= sy || @top >= sy + sh
# collision!
if @velocity_y < 0 && @top - dy > sy + sh
@top = sy + sh - 1
@velocity_y = -@velocity_y + rebound
end
if @velocity_y > 0 && @top - dy + @height < sy
@top = sy - @height + 1
@velocity_y = -@velocity_y - rebound
end
if @velocity_x < 0 && @left - dx > sx + sw
@left = sx + sw - 1
@velocity_x = -@velocity_x + rebound
end
if @velocity_x > 0 && @left - dx + @width < sx
@left = sx - @width + 1
@velocity_x = -@velocity_x - rebound
end
end
end
end
def update_stick( dx, dy )
if parent.style[:sticky]
pw, ph = parent.width, parent.height
if @velocity_y < 0 && @top < 0
@top = 1
@velocity_x = 0
@velocity_y = 0
@stuck = true
end
if @velocity_y > 0 && @top + @height > ph
@top = ph - @height - 1
@velocity_x = 0
@velocity_y = 0
@stuck = true
end
if @velocity_x < 0 && @left < 0
@left = 1
@velocity_x = 0
@velocity_y = 0
@stuck = true
end
if @velocity_x > 0 && @left + @width > pw
@left = pw - @width - 1
@velocity_x = 0
@velocity_y = 0
@stuck = true
end
end
siblings.each do |sib|
next unless sib.style[:sticky]
sx, sy, sw, sh = sib.left, sib.top, sib.width, sib.height
rebound = sib.style[:push]
unless @left + @width < sx || @left > sx + sw ||
@top + @height < sy || @top > sy + sh
# collision!
@stuck = true
if @velocity_y < 0 && @top - dy > sy + sh
@top = sy + sh + 1
@velocity_x = 0
@velocity_y = 0
end
if @velocity_y > 0 && @top - dy + @height < sy
@top = sy - @height - 1
@velocity_x = 0
@velocity_y = 0
end
if @velocity_x < 0 && @left - dx > sx + sw
@left = sx + sw + 1
@velocity_x = 0
@velocity_y = 0
end
if @velocity_x > 0 && @left - dx + @width < sx
@left = sx - @width - 1
@velocity_x = 0
@velocity_y = 0
end
end
end
end
def touching?( sibling, edge )
sx, sy, sw, sh = sibling.left, sibling.top, sibling.width, sibling.height
case edge
when :top
@top == sy - @height - 1 &&
! (@left > sx + sw || @left + @width < sx)
when :bottom
@top == sy + sh + 1 &&
! (@left > sx + sw || @left + @width < sx)
when :left
@left == sx - @width - 1 &&
! (@top > sy + sh || @top + @height < sy)
when :right
@left == sx + sw + 1 &&
! (@top > sy + sh || @top + @height < sy)
end
end
def update_movement( delta )
return if @stuck
if (@gravity_x > 0 && @left + @width < parent.width) ||
(@gravity_x < 0 && @left > 0)
if (@gravity_x > 0 && ! siblings.any? { |s| touching?( s, :left ) }) ||
(@gravity_x < 0 && ! siblings.any? { |s| touching?( s, :right ) })
@velocity_x += @gravity_x * delta
end
end
if (@gravity_y > 0 && @top + @height < parent.height) ||
(@gravity_y < 0 && @top > 0)
if (@gravity_y > 0 && ! siblings.any? { |s| touching?( s, :top ) }) ||
(@gravity_y < 0 && ! siblings.any? { |s| touching?( s, :bottom ) })
@velocity_y += @gravity_y * delta
end
end
return if @velocity_x == 0.0 && @velocity_y == 0.0
@left += (dx = @velocity_x * delta)
@top += (dy = @velocity_y * delta)
update_bounce( dx, dy )
update_stick( dx, dy )
@velocity_x *= 1.0 - (1.0 - @friction_x) * delta
@velocity_y *= 1.0 - (1.0 - @friction_y) * delta
if @gravity_x == 0 && @velocity_x.abs < @min_velocity_x
@velocity_x = 0
end
if @gravity_y == 0 && @velocity_y.abs < @min_velocity_y
@velocity_y = 0
end
self.canvas.move( @left.to_i, @top.to_i )
end
end
Shoes.app :width => 400, :height => 400, :title => "Sprite Demo" do
background white
stack :top => 0, :left => 0, :width => 400, :height => 400, :bouncy => true do
stack :top => 186, :left => 100, :width => 200, :height => 28,
:bouncy => true, :push => 100 do
background "ddd"
@p = para "down", :align => 'center'
end
@s = sprite( :left => 100, :top => 50, :width => 64, :height => 64 ) do
fill blue
oval 1, 1, 62, 62
end
end
keypress do |k|
case k
when :left then @s.push( -90, 0 )
when :right then @s.push( 90, 0 )
when :up then @s.push( 0, -90 )
when :down then @s.push( 0, 90 )
when :shift_left then @s.gravity( -80, 0 ); @p.text = "left"
when :shift_right then @s.gravity( 80, 0 ); @p.text = "right"
when :shift_up then @s.gravity( 0, -80 ); @p.text = "up"
when :shift_down then @s.gravity( 0, 80 ); @p.text = "down"
when :alt_s
@s.relations.each do |r|
r.style[:bouncy] = false
r.style[:sticky] = true
end
@p.text = "sticky"
when :alt_b
@s.relations.each do |r|
r.style[:bouncy] = true
r.style[:sticky] = false
end
@p.text = "bouncy"
end
end
end