lib/minigl/movement.rb in minigl-1.3.10 vs lib/minigl/movement.rb in minigl-2.0.0
- old
+ new
@@ -1,8 +1,8 @@
require_relative 'global'
-module AGL
+module MiniGL
# Represents an object with a rectangular bounding box and the +passable+
# property. It is the simplest structure that can be passed as an element of
# the +obst+ array parameter of the +move+ method.
class Block
# The x-coordinate of the top left corner of the bounding box.
@@ -29,11 +29,11 @@
# [w] The width of the bounding box.
# [h] The height of the bounding box.
# [passable] Whether a moving object can pass through this block when
# coming from below. This is a common feature of platforms in platform
# games.
- def initialize x, y, w, h, passable
+ def initialize(x, y, w, h, passable)
@x = x; @y = y; @w = w; @h = h
@passable = passable
end
# Returns the bounding box of this block as a Rectangle.
@@ -64,10 +64,12 @@
# Whether the height of the ramp increases from left to right (decreases
# from left to right when +false+).
attr_reader :left
+ attr_reader :ratio # :nodoc:
+
# Creates a new ramp.
#
# Parameters:
# [x] The x-coordinate of the top left corner of a rectangle that
# completely (and precisely) encloses the ramp (thought of as a right
@@ -80,67 +82,70 @@
# rectangle described above, and to the difference between the lowest
# point of the ramp, where it usually meets the floor, and the
# highest).
# [left] Whether the height of the ramp increases from left to right. Use
# +false+ for a ramp that goes down from left to right.
- def initialize x, y, w, h, left
+ def initialize(x, y, w, h, left)
@x = x
@y = y
@w = w
@h = h
@left = left
+ @ratio = @h.to_f / @w
+ @factor = Math.cos(@w / Math.sqrt(@w**2 + @h**2))
end
# Checks if an object is in contact with this ramp (standing over it).
#
# Parameters:
# [obj] The object to check contact with. It must have the +x+, +y+, +w+
# and +h+ accessible attributes determining its bounding box.
- def contact? obj
- obj.x.round(6) == get_x(obj).round(6) && obj.y.round(6) == get_y(obj).round(6)
+ def contact?(obj)
+ obj.x + obj.w > @x && obj.x < @x + @w && obj.x.round(6) == get_x(obj).round(6) && obj.y.round(6) == get_y(obj).round(6)
end
# Checks if an object is intersecting this ramp (inside the corresponding
# right triangle and at the floor level or above).
#
# Parameters:
# [obj] The object to check intersection with. It must have the +x+, +y+,
# +w+ and +h+ accessible attributes determining its bounding box.
- def intersects obj
+ def intersect?(obj)
obj.x + obj.w > @x && obj.x < @x + @w && obj.y > get_y(obj) && obj.y <= @y + @h - obj.h
end
# :nodoc:
- def can_collide? obj
- @can_collide = (obj.speed.y >= 0 and not intersects(obj))
+ def check_can_collide(m)
+ y = get_y(m) + m.h
+ @can_collide = m.x + m.w > @x && @x + @w > m.x && m.y < y && m.y + m.h > y
end
- def check_intersection obj
- if @can_collide and intersects obj
+ def check_intersection(obj)
+ if @can_collide and intersect? obj
+ counter = @left && obj.prev_speed.x > 0 || !@left && obj.prev_speed.x < 0
+ if obj.prev_speed.y > 0 && counter
+ dx = get_x(obj) - obj.x
+ s = (obj.prev_speed.y.to_f / obj.prev_speed.x).abs
+ dx /= s + @ratio
+ obj.x += dx
+ end
obj.y = get_y obj
+ if counter
+ obj.speed.x *= @factor
+ end
obj.speed.y = 0
- # a = @w / @h
- # x = get_x(obj)
- # y = get_y(obj)
- # w = obj.x - x
- # h = obj.y - y
- # dx = w * h / (w * a + h)
- # dy = dx * a
- #
- # obj.x -= dx
- # obj.y -= dy
- # obj.speed.x *= (@w / (@w + @h))
- # obj.speed.y = 0
end
end
- def get_x obj
+ def get_x(obj)
+ return obj.x if @left && obj.x + obj.w > @x + @w
return @x + (1.0 * (@y + @h - obj.y - obj.h) * @w / @h) - obj.w if @left
+ return obj.x if obj.x < @x
@x + (1.0 * (obj.y + obj.h - @y) * @w / @h)
end
- def get_y obj
+ def get_y(obj)
return @y - obj.h if @left && obj.x + obj.w > @x + @w
return @y + (1.0 * (@x + @w - obj.x - obj.w) * @h / @w) - obj.h if @left
return @y - obj.h if obj.x < @x
@y + (1.0 * (obj.x - @x) * @h / @w) - obj.h
end
@@ -159,10 +164,14 @@
# A Vector with the current speed of the object (x: horizontal component,
# y: vertical component).
attr_reader :speed
+ # A Vector with the speed limits for the object (x: horizontal component,
+ # y: vertical component).
+ attr_reader :max_speed
+
# Width of the bounding box.
attr_reader :w
# Height of the bounding box.
attr_reader :h
@@ -195,10 +204,12 @@
# A Vector with the horizontal and vertical components of a force that
# be applied in the next time +move+ is called.
attr_accessor :stored_forces
+ attr_reader :prev_speed # :nodoc:
+
# Returns the bounding box as a Rectangle.
def bounds
Rectangle.new @x, @y, @w, @h
end
@@ -211,50 +222,58 @@
# [obst] An array of obstacles to be considered in the collision checking.
# Obstacles must be instances of Block (or derived classes), or
# objects that <code>include Movement</code>.
# [ramps] An array of ramps to be considered in the collision checking.
# Ramps must be instances of Ramp (or derived classes).
- def move forces, obst, ramps
- forces.x += Game.gravity.x; forces.y += Game.gravity.y
+ def move(forces, obst, ramps)
+ forces.x += G.gravity.x; forces.y += G.gravity.y
forces.x += @stored_forces.x; forces.y += @stored_forces.y
@stored_forces.x = @stored_forces.y = 0
- # check_contact obst, ramps
forces.x = 0 if (forces.x < 0 and @left) or (forces.x > 0 and @right)
forces.y = 0 if (forces.y < 0 and @top) or (forces.y > 0 and @bottom)
+ if @bottom.is_a? Ramp and @bottom.ratio >= G.ramp_slip_threshold
+ forces.x = (@bottom.left ? -1 : 1) * 0.1
+ end
+
@speed.x += forces.x / @mass; @speed.y += forces.y / @mass
- @speed.x = 0 if @speed.x.abs < @min_speed.x
- @speed.y = 0 if @speed.y.abs < @min_speed.y
+ @speed.x = 0 if @speed.x.abs < G.min_speed.x
+ @speed.y = 0 if @speed.y.abs < G.min_speed.y
@speed.x = (@speed.x <=> 0) * @max_speed.x if @speed.x.abs > @max_speed.x
@speed.y = (@speed.y <=> 0) * @max_speed.y if @speed.y.abs > @max_speed.y
- ramps.each do |r|
- r.can_collide? self
- end
+ @prev_speed = @speed.clone
x = @speed.x < 0 ? @x + @speed.x : @x
y = @speed.y < 0 ? @y + @speed.y : @y
w = @w + (@speed.x < 0 ? -@speed.x : @speed.x)
h = @h + (@speed.y < 0 ? -@speed.y : @speed.y)
move_bounds = Rectangle.new x, y, w, h
coll_list = []
obst.each do |o|
- coll_list << o if move_bounds.intersects o.bounds
+ coll_list << o if move_bounds.intersect? o.bounds
end
+ ramps.each do |r|
+ r.check_can_collide move_bounds
+ end
if coll_list.length > 0
up = @speed.y < 0; rt = @speed.x > 0; dn = @speed.y > 0; lf = @speed.x < 0
if @speed.x == 0 || @speed.y == 0
# Ortogonal
if rt; x_lim = find_right_limit coll_list
elsif lf; x_lim = find_left_limit coll_list
elsif dn; y_lim = find_down_limit coll_list
elsif up; y_lim = find_up_limit coll_list
end
- if rt && @x + @w + @speed.x > x_lim; @x = x_lim - @w; @speed.x = 0
- elsif lf && @x + @speed.x < x_lim; @x = x_lim; @speed.x = 0
+ if rt && @x + @w + @speed.x > x_lim
+ @x = x_lim - @w
+ @speed.x = 0
+ elsif lf && @x + @speed.x < x_lim
+ @x = x_lim
+ @speed.x = 0
elsif dn && @y + @h + @speed.y > y_lim; @y = y_lim - @h; @speed.y = 0
elsif up && @y + @speed.y < y_lim; @y = y_lim; @speed.y = 0
end
else
# Diagonal
@@ -304,10 +323,15 @@
end
end
@x += @speed.x
@y += @speed.y
+ # Keeping contact with ramp
+ # if @speed.y == 0 and @speed.x.abs <= G.ramp_contact_threshold and @bottom.is_a? Ramp
+ # @y = @bottom.get_y(self)
+ # puts 'aqui'
+ # end
ramps.each do |r|
r.check_intersection self
end
check_contact obst, ramps
end
@@ -321,13 +345,19 @@
# provided as a scalar, not a vector.
# [obstacles] An array of obstacles to be considered in the collision
# checking, and carried along when colliding from above.
# Obstacles must be instances of Block (or derived classes),
# or objects that <code>include Movement</code>.
- def move_carrying aim, speed, obstacles
+ def move_carrying(aim, speed, obstacles)
x_d = aim.x - @x; y_d = aim.y - @y
distance = Math.sqrt(x_d**2 + y_d**2)
+
+ if distance == 0
+ @speed.x = @speed.y = 0
+ return
+ end
+
@speed.x = 1.0 * x_d * speed / distance
@speed.y = 1.0 * y_d * speed / distance
x_aim = @x + @speed.x; y_aim = @y + @speed.y
passengers = []
@@ -361,13 +391,19 @@
#
# Parameters:
# [aim] A Vector specifying where the object will move to.
# [speed] The constant speed at which the object will move. This must be
# provided as a scalar, not a vector.
- def move_free aim, speed
+ def move_free(aim, speed)
x_d = aim.x - @x; y_d = aim.y - @y
distance = Math.sqrt(x_d**2 + y_d**2)
+
+ if distance == 0
+ @speed.x = @speed.y = 0
+ return
+ end
+
@speed.x = 1.0 * x_d * speed / distance
@speed.y = 1.0 * y_d * speed / distance
if (@speed.x < 0 and @x + @speed.x <= aim.x) or (@speed.x >= 0 and @x + @speed.x >= aim.x)
@x = aim.x
@@ -383,43 +419,41 @@
@y += @speed.y
end
end
# Causes the object to move in cycles across multiple given points (the
- # method must be called repeatedly, and it returns the value that must be
- # provided to +cur_point+ after the first call). If obstacles are
- # provided, it will behave as an elevator (as in +move_carrying+).
+ # first point in the array is the first point the object will move towards,
+ # so it doesn't need to be equal to the current/initial position). If
+ # obstacles are provided, it will behave as an elevator (as in
+ # +move_carrying+).
#
# Parameters:
# [points] An array of Vectors representing the path that the object will
# perform.
- # [cur_point] The index of the point in the path that the object is
- # currently moving to. In the first call, it is a good idea to
- # provide 0, while in the subsequent calls, you must provide
- # the return value of this method.
# [speed] The constant speed at which the object will move. This must be
# provided as a scalar, not a vector.
# [obstacles] An array of obstacles to be considered in the collision
# checking, and carried along when colliding from above.
# Obstacles must be instances of Block (or derived classes),
# or objects that <code>include Movement</code>.
- def cycle points, cur_point, speed, obstacles = nil
+ def cycle(points, speed, obstacles = nil)
+ @cur_point = 0 if @cur_point.nil?
if obstacles
- move_carrying points[cur_point], speed, obstacles
+ move_carrying points[@cur_point], speed, obstacles
else
- move_free points[cur_point], speed
+ move_free points[@cur_point], speed
end
if @speed.x == 0 and @speed.y == 0
- if cur_point == points.length - 1; cur_point = 0
- else; cur_point += 1; end
+ if @cur_point == points.length - 1; @cur_point = 0
+ else; @cur_point += 1; end
end
- cur_point
end
private
- def check_contact obst, ramps
+ def check_contact(obst, ramps)
+ prev_bottom = @bottom
@top = @bottom = @left = @right = nil
obst.each do |o|
x2 = @x + @w; y2 = @y + @h; x2o = o.x + o.w; y2o = o.y + o.h
@right = o if !o.passable && x2.round(6) == o.x.round(6) && y2 > o.y && @y < y2o
@left = o if !o.passable && @x.round(6) == x2o.round(6) && y2 > o.y && @y < y2o
@@ -431,37 +465,48 @@
if r.contact? self
@bottom = r
break
end
end
+ if @bottom.nil?
+ ramps.each do |r|
+ if r == prev_bottom && @x + @w > r.x && r.x + r.w > @x &&
+ @prev_speed.x.abs <= G.ramp_contact_threshold &&
+ @prev_speed.y >= 0
+ @y = r.get_y self
+ @bottom = r
+ break
+ end
+ end
+ end
end
end
- def find_right_limit coll_list
+ def find_right_limit(coll_list)
limit = @x + @w + @speed.x
coll_list.each do |c|
limit = c.x if !c.passable && c.x < limit
end
limit
end
- def find_left_limit coll_list
+ def find_left_limit(coll_list)
limit = @x + @speed.x
coll_list.each do |c|
limit = c.x + c.w if !c.passable && c.x + c.w > limit
end
limit
end
- def find_down_limit coll_list
+ def find_down_limit(coll_list)
limit = @y + @h + @speed.y
coll_list.each do |c|
limit = c.y if c.y < limit && c.y >= @y + @h
end
limit
end
- def find_up_limit coll_list
+ def find_up_limit(coll_list)
limit = @y + @speed.y
coll_list.each do |c|
limit = c.y + c.h if !c.passable && c.y + c.h > limit
end
limit