lib/minigl/movement.rb in minigl-2.2.2 vs lib/minigl/movement.rb in minigl-2.2.3
- old
+ new
@@ -1,585 +1,585 @@
-require_relative 'global'
-
-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.
- attr_reader :x
-
- # The y-coordinate of the top left corner of the bounding box.
- attr_reader :y
-
- # The width of the bounding box.
- attr_reader :w
-
- # The height of the bounding box.
- attr_reader :h
-
- # Whether a moving object can pass through this block when coming from
- # below. This is a common feature of platforms in platform games.
- attr_reader :passable
-
- # Creates a new block.
- #
- # Parameters:
- # [x] The x-coordinate of the top left corner of the bounding box.
- # [y] The y-coordinate of the top left corner of the bounding box.
- # [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. Default is +false+.
- def initialize(x, y, w, h, passable = false)
- @x = x; @y = y; @w = w; @h = h
- @passable = passable
- end
-
- # Returns the bounding box of this block as a Rectangle.
- def bounds
- Rectangle.new @x, @y, @w, @h
- end
- end
-
- # Represents a ramp, i.e., an inclined structure which allows walking over
- # it while automatically going up or down. It can be imagined as a right
- # triangle, with a side parallel to the x axis and another one parallel to
- # the y axis. You must provide instances of this class (or derived classes)
- # to the +ramps+ array parameter of the +move+ method.
- class Ramp
- # The x-coordinate of the top left corner of a rectangle that completely
- # (and precisely) encloses the ramp (thought of as a right triangle).
- attr_reader :x
-
- # The y-coordinate of the top left corner of the rectangle described in
- # the +x+ attribute.
- attr_reader :y
-
- # The width of the ramp.
- attr_reader :w
-
- # The height of the ramp.
- attr_reader :h
-
- # 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:
- attr_reader :factor # :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
- # triangle).
- # [y] The y-coordinate of the top left corner of the rectangle described
- # above.
- # [w] The width of the ramp (which corresponds to the width of the
- # rectangle described above).
- # [h] The height of the ramp (which corresponds to the height of the
- # 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)
- @x = x
- @y = y
- @w = w
- @h = h
- @left = left
- @ratio = @h.to_f / @w
- @factor = @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 + 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 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 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 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.bottom != self
- obj.speed.x *= @factor
- end
- obj.speed.y = 0
- end
- end
-
- 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)
- 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
- end
-
- # This module provides objects with physical properties and methods for
- # moving. It allows moving with or without collision checking (based on
- # rectangular bounding boxes), including a method to behave as an elevator,
- # affecting other objects' positions as it moves.
- module Movement
- # The mass of the object, in arbitrary units. The default value for
- # GameObject instances, for example, is 1. The larger the mass (i.e., the
- # heavier the object), the more intense the forces applied to the object
- # have to be in order to move it.
- attr_reader :mass
-
- # 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
-
- # The object that is making contact with this from above. If there's no
- # contact, returns +nil+.
- attr_reader :top
-
- # The object that is making contact with this from below. If there's no
- # contact, returns +nil+.
- attr_reader :bottom
-
- # The object that is making contact with this from the left. If there's no
- # contact, returns +nil+.
- attr_reader :left
-
- # The object that is making contact with this from the right. If there's
- # no contact, returns +nil+.
- attr_reader :right
-
- # The x-coordinate of the top left corner of the bounding box.
- attr_accessor :x
-
- # The y-coordinate of the top left corner of the bounding box.
- attr_accessor :y
-
- # Whether a moving object can pass through this block when coming from
- # below. This is a common feature of platforms in platform games.
- attr_accessor :passable
-
- # 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
-
- # A Vector containing the speed of the object in the previous frame.
- attr_reader :prev_speed
-
- # Returns the bounding box as a Rectangle.
- def bounds
- Rectangle.new @x, @y, @w, @h
- end
-
- # Moves this object, based on the forces being applied to it, and
- # performing collision checking.
- #
- # Parameters:
- # [forces] A Vector where x is the horizontal component of the resulting
- # force and y is the vertical component.
- # [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).
- # [set_speed] Set this flag to +true+ to cause the +forces+ vector to be
- # treated as a speed vector, i.e., the object's speed will be
- # directly set to the given values. The force of gravity will
- # also be ignored in this case.
- def move(forces, obst, ramps, set_speed = false)
- if set_speed
- @speed.x = forces.x
- @speed.y = forces.y
- else
- 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
-
- 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
- if @bottom.ratio > G.ramp_slip_threshold
- forces.x += (@bottom.left ? -1 : 1) * (@bottom.ratio - G.ramp_slip_threshold) * G.ramp_slip_force / G.ramp_slip_threshold
- elsif forces.x > 0 && @bottom.left || forces.x < 0 && !@bottom.left
- forces.x *= @bottom.factor
- end
- end
-
- @speed.x += forces.x / @mass; @speed.y += forces.y / @mass
- end
-
- @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
- @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 o != self && 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
- 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
- x_aim = @x + @speed.x + (rt ? @w : 0); x_lim_def = x_aim
- y_aim = @y + @speed.y + (dn ? @h : 0); y_lim_def = y_aim
- coll_list.each do |c|
- if c.passable; x_lim = x_aim
- elsif rt; x_lim = c.x
- else; x_lim = c.x + c.w
- end
- if dn; y_lim = c.y
- elsif c.passable; y_lim = y_aim
- else; y_lim = c.y + c.h
- end
-
- if c.passable
- y_lim_def = y_lim if dn && @y + @h <= y_lim && y_lim < y_lim_def
- elsif (rt && @x + @w > x_lim) || (lf && @x < x_lim)
- # Can't limit by x, will limit by y
- y_lim_def = y_lim if (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
- elsif (dn && @y + @h > y_lim) || (up && @y < y_lim)
- # Can't limit by y, will limit by x
- x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
- else
- x_time = 1.0 * (x_lim - @x - (@speed.x < 0 ? 0 : @w)) / @speed.x
- y_time = 1.0 * (y_lim - @y - (@speed.y < 0 ? 0 : @h)) / @speed.y
- if x_time > y_time
- # Will limit by x
- x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
- elsif (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
- y_lim_def = y_lim
- end
- end
- end
- if x_lim_def != x_aim
- @speed.x = 0
- if lf; @x = x_lim_def
- else; @x = x_lim_def - @w
- end
- end
- if y_lim_def != y_aim
- @speed.y = 0
- if up; @y = y_lim_def
- else; @y = y_lim_def - @h
- end
- end
- 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
-
- # Moves this object as an elevator (i.e., potentially carrying other
- # objects) with the specified forces or towards a given point.
- #
- # Parameters:
- # [arg] A Vector specifying either the forces acting on this object or a
- # point towards the object should move.
- # [speed] If the first argument is a forces vector, then this should be
- # +nil+. If it is a point, then this is the constant speed at which
- # the object will move (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>.
- # [obst_obstacles] Obstacles that should be considered when moving objects
- # from the +obstacles+ array, i.e., these obstacles won't
- # interfere in the elevator's movement, but in the movement
- # of the objects being carried.
- # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
- # array, as described for +obst_obstacles+.
- def move_carrying(arg, speed, obstacles, obst_obstacles, obst_ramps)
- if speed
- x_d = arg.x - @x; y_d = arg.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
- else
- arg += G.gravity
- @speed.x += arg.x / @mass; @speed.y += arg.y / @mass
- @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
- end
-
- x_aim = @x + @speed.x; y_aim = @y + @speed.y
- passengers = []
- obstacles.each do |o|
- if @x + @w > o.x && o.x + o.w > @x
- foot = o.y + o.h
- if foot.round(6) == @y.round(6) || @speed.y < 0 && foot < @y && foot > y_aim
- passengers << o
- end
- end
- end
-
- prev_x = @x; prev_y = @y
- if speed
- if @speed.x > 0 && x_aim >= arg.x || @speed.x < 0 && x_aim <= arg.x
- @x = arg.x; @speed.x = 0
- else
- @x = x_aim
- end
- if @speed.y > 0 && y_aim >= arg.y || @speed.y < 0 && y_aim <= arg.y
- @y = arg.y; @speed.y = 0
- else
- @y = y_aim
- end
- else
- @x = x_aim; @y = y_aim
- end
-
- forces = Vector.new @x - prev_x, @y - prev_y
- prev_g = G.gravity.clone
- G.gravity.x = G.gravity.y = 0
- passengers.each do |p|
- prev_speed = p.speed.clone
- prev_forces = p.stored_forces.clone
- prev_bottom = p.bottom
- p.speed.x = p.speed.y = 0
- p.stored_forces.x = p.stored_forces.y = 0
- p.instance_exec { @bottom = nil }
- p.move forces * p.mass, obst_obstacles, obst_ramps
- p.speed.x = prev_speed.x
- p.speed.y = prev_speed.y
- p.stored_forces.x = prev_forces.x
- p.stored_forces.y = prev_forces.y
- p.instance_exec(prev_bottom) { |b| @bottom = b }
- end
- G.gravity = prev_g
- end
-
- # Moves this object, without performing any collision checking, towards
- # a specified point or in a specified direction.
- #
- # Parameters:
- # [aim] A +Vector+ specifying where the object will move to or an angle (in
- # degrees) indicating the direction of the movement. Angles are
- # measured starting from the right (i.e., to move to the right, the
- # angle must be 0) and raising clockwise.
- # [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)
- if aim.is_a? Vector
- 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
- @speed.x = 0
- else
- @x += @speed.x
- end
-
- if (@speed.y < 0 and @y + @speed.y <= aim.y) or (@speed.y >= 0 and @y + @speed.y >= aim.y)
- @y = aim.y
- @speed.y = 0
- else
- @y += @speed.y
- end
- else
- rads = aim * Math::PI / 180
- @speed.x = speed * Math.cos(rads)
- @speed.y = speed * Math.sin(rads)
- @x += @speed.x
- @y += @speed.y
- end
- end
-
- # Causes the object to move in cycles across multiple given points (the
- # 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.
- # [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>.
- # [obst_obstacles] Obstacles that should be considered when moving objects
- # from the +obstacles+ array, i.e., these obstacles won't
- # interfere in the elevator's movement, but in the movement
- # of the objects being carried.
- # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
- # array, as described for +obst_obstacles+.
- def cycle(points, speed, obstacles = nil, obst_obstacles = nil, obst_ramps = nil)
- @cur_point = 0 if @cur_point.nil?
- if obstacles
- move_carrying points[@cur_point], speed, obstacles, obst_obstacles, obst_ramps
- else
- 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
- end
- end
-
- private
-
- 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
- @bottom = o if y2.round(6) == o.y.round(6) && x2 > o.x && @x < x2o
- @top = o if !o.passable && @y.round(6) == y2o.round(6) && x2 > o.x && @x < x2o
- end
- if @bottom.nil?
- ramps.each do |r|
- 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)
- 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)
- 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)
- 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)
- limit = @y + @speed.y
- coll_list.each do |c|
- limit = c.y + c.h if !c.passable && c.y + c.h > limit
- end
- limit
- end
- end
-end
+require_relative 'global'
+
+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.
+ attr_reader :x
+
+ # The y-coordinate of the top left corner of the bounding box.
+ attr_reader :y
+
+ # The width of the bounding box.
+ attr_reader :w
+
+ # The height of the bounding box.
+ attr_reader :h
+
+ # Whether a moving object can pass through this block when coming from
+ # below. This is a common feature of platforms in platform games.
+ attr_reader :passable
+
+ # Creates a new block.
+ #
+ # Parameters:
+ # [x] The x-coordinate of the top left corner of the bounding box.
+ # [y] The y-coordinate of the top left corner of the bounding box.
+ # [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. Default is +false+.
+ def initialize(x, y, w, h, passable = false)
+ @x = x; @y = y; @w = w; @h = h
+ @passable = passable
+ end
+
+ # Returns the bounding box of this block as a Rectangle.
+ def bounds
+ Rectangle.new @x, @y, @w, @h
+ end
+ end
+
+ # Represents a ramp, i.e., an inclined structure which allows walking over
+ # it while automatically going up or down. It can be imagined as a right
+ # triangle, with a side parallel to the x axis and another one parallel to
+ # the y axis. You must provide instances of this class (or derived classes)
+ # to the +ramps+ array parameter of the +move+ method.
+ class Ramp
+ # The x-coordinate of the top left corner of a rectangle that completely
+ # (and precisely) encloses the ramp (thought of as a right triangle).
+ attr_reader :x
+
+ # The y-coordinate of the top left corner of the rectangle described in
+ # the +x+ attribute.
+ attr_reader :y
+
+ # The width of the ramp.
+ attr_reader :w
+
+ # The height of the ramp.
+ attr_reader :h
+
+ # 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:
+ attr_reader :factor # :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
+ # triangle).
+ # [y] The y-coordinate of the top left corner of the rectangle described
+ # above.
+ # [w] The width of the ramp (which corresponds to the width of the
+ # rectangle described above).
+ # [h] The height of the ramp (which corresponds to the height of the
+ # 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)
+ @x = x
+ @y = y
+ @w = w
+ @h = h
+ @left = left
+ @ratio = @h.to_f / @w
+ @factor = @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 + 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 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 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 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.bottom != self
+ obj.speed.x *= @factor
+ end
+ obj.speed.y = 0
+ end
+ end
+
+ 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)
+ 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
+ end
+
+ # This module provides objects with physical properties and methods for
+ # moving. It allows moving with or without collision checking (based on
+ # rectangular bounding boxes), including a method to behave as an elevator,
+ # affecting other objects' positions as it moves.
+ module Movement
+ # The mass of the object, in arbitrary units. The default value for
+ # GameObject instances, for example, is 1. The larger the mass (i.e., the
+ # heavier the object), the more intense the forces applied to the object
+ # have to be in order to move it.
+ attr_reader :mass
+
+ # 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
+
+ # The object that is making contact with this from above. If there's no
+ # contact, returns +nil+.
+ attr_reader :top
+
+ # The object that is making contact with this from below. If there's no
+ # contact, returns +nil+.
+ attr_reader :bottom
+
+ # The object that is making contact with this from the left. If there's no
+ # contact, returns +nil+.
+ attr_reader :left
+
+ # The object that is making contact with this from the right. If there's
+ # no contact, returns +nil+.
+ attr_reader :right
+
+ # The x-coordinate of the top left corner of the bounding box.
+ attr_accessor :x
+
+ # The y-coordinate of the top left corner of the bounding box.
+ attr_accessor :y
+
+ # Whether a moving object can pass through this block when coming from
+ # below. This is a common feature of platforms in platform games.
+ attr_accessor :passable
+
+ # 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
+
+ # A Vector containing the speed of the object in the previous frame.
+ attr_reader :prev_speed
+
+ # Returns the bounding box as a Rectangle.
+ def bounds
+ Rectangle.new @x, @y, @w, @h
+ end
+
+ # Moves this object, based on the forces being applied to it, and
+ # performing collision checking.
+ #
+ # Parameters:
+ # [forces] A Vector where x is the horizontal component of the resulting
+ # force and y is the vertical component.
+ # [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).
+ # [set_speed] Set this flag to +true+ to cause the +forces+ vector to be
+ # treated as a speed vector, i.e., the object's speed will be
+ # directly set to the given values. The force of gravity will
+ # also be ignored in this case.
+ def move(forces, obst, ramps, set_speed = false)
+ if set_speed
+ @speed.x = forces.x
+ @speed.y = forces.y
+ else
+ 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
+
+ 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
+ if @bottom.ratio > G.ramp_slip_threshold
+ forces.x += (@bottom.left ? -1 : 1) * (@bottom.ratio - G.ramp_slip_threshold) * G.ramp_slip_force / G.ramp_slip_threshold
+ elsif forces.x > 0 && @bottom.left || forces.x < 0 && !@bottom.left
+ forces.x *= @bottom.factor
+ end
+ end
+
+ @speed.x += forces.x / @mass; @speed.y += forces.y / @mass
+ end
+
+ @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
+ @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 o != self && 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
+ 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
+ x_aim = @x + @speed.x + (rt ? @w : 0); x_lim_def = x_aim
+ y_aim = @y + @speed.y + (dn ? @h : 0); y_lim_def = y_aim
+ coll_list.each do |c|
+ if c.passable; x_lim = x_aim
+ elsif rt; x_lim = c.x
+ else; x_lim = c.x + c.w
+ end
+ if dn; y_lim = c.y
+ elsif c.passable; y_lim = y_aim
+ else; y_lim = c.y + c.h
+ end
+
+ if c.passable
+ y_lim_def = y_lim if dn && @y + @h <= y_lim && y_lim < y_lim_def
+ elsif (rt && @x + @w > x_lim) || (lf && @x < x_lim)
+ # Can't limit by x, will limit by y
+ y_lim_def = y_lim if (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
+ elsif (dn && @y + @h > y_lim) || (up && @y < y_lim)
+ # Can't limit by y, will limit by x
+ x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
+ else
+ x_time = 1.0 * (x_lim - @x - (@speed.x < 0 ? 0 : @w)) / @speed.x
+ y_time = 1.0 * (y_lim - @y - (@speed.y < 0 ? 0 : @h)) / @speed.y
+ if x_time > y_time
+ # Will limit by x
+ x_lim_def = x_lim if (rt && x_lim < x_lim_def) || (lf && x_lim > x_lim_def)
+ elsif (dn && y_lim < y_lim_def) || (up && y_lim > y_lim_def)
+ y_lim_def = y_lim
+ end
+ end
+ end
+ if x_lim_def != x_aim
+ @speed.x = 0
+ if lf; @x = x_lim_def
+ else; @x = x_lim_def - @w
+ end
+ end
+ if y_lim_def != y_aim
+ @speed.y = 0
+ if up; @y = y_lim_def
+ else; @y = y_lim_def - @h
+ end
+ end
+ 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
+
+ # Moves this object as an elevator (i.e., potentially carrying other
+ # objects) with the specified forces or towards a given point.
+ #
+ # Parameters:
+ # [arg] A Vector specifying either the forces acting on this object or a
+ # point towards the object should move.
+ # [speed] If the first argument is a forces vector, then this should be
+ # +nil+. If it is a point, then this is the constant speed at which
+ # the object will move (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>.
+ # [obst_obstacles] Obstacles that should be considered when moving objects
+ # from the +obstacles+ array, i.e., these obstacles won't
+ # interfere in the elevator's movement, but in the movement
+ # of the objects being carried.
+ # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
+ # array, as described for +obst_obstacles+.
+ def move_carrying(arg, speed, obstacles, obst_obstacles, obst_ramps)
+ if speed
+ x_d = arg.x - @x; y_d = arg.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
+ else
+ arg += G.gravity
+ @speed.x += arg.x / @mass; @speed.y += arg.y / @mass
+ @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
+ end
+
+ x_aim = @x + @speed.x; y_aim = @y + @speed.y
+ passengers = []
+ obstacles.each do |o|
+ if @x + @w > o.x && o.x + o.w > @x
+ foot = o.y + o.h
+ if foot.round(6) == @y.round(6) || @speed.y < 0 && foot < @y && foot > y_aim
+ passengers << o
+ end
+ end
+ end
+
+ prev_x = @x; prev_y = @y
+ if speed
+ if @speed.x > 0 && x_aim >= arg.x || @speed.x < 0 && x_aim <= arg.x
+ @x = arg.x; @speed.x = 0
+ else
+ @x = x_aim
+ end
+ if @speed.y > 0 && y_aim >= arg.y || @speed.y < 0 && y_aim <= arg.y
+ @y = arg.y; @speed.y = 0
+ else
+ @y = y_aim
+ end
+ else
+ @x = x_aim; @y = y_aim
+ end
+
+ forces = Vector.new @x - prev_x, @y - prev_y
+ prev_g = G.gravity.clone
+ G.gravity.x = G.gravity.y = 0
+ passengers.each do |p|
+ prev_speed = p.speed.clone
+ prev_forces = p.stored_forces.clone
+ prev_bottom = p.bottom
+ p.speed.x = p.speed.y = 0
+ p.stored_forces.x = p.stored_forces.y = 0
+ p.instance_exec { @bottom = nil }
+ p.move forces * p.mass, obst_obstacles, obst_ramps
+ p.speed.x = prev_speed.x
+ p.speed.y = prev_speed.y
+ p.stored_forces.x = prev_forces.x
+ p.stored_forces.y = prev_forces.y
+ p.instance_exec(prev_bottom) { |b| @bottom = b }
+ end
+ G.gravity = prev_g
+ end
+
+ # Moves this object, without performing any collision checking, towards
+ # a specified point or in a specified direction.
+ #
+ # Parameters:
+ # [aim] A +Vector+ specifying where the object will move to or an angle (in
+ # degrees) indicating the direction of the movement. Angles are
+ # measured starting from the right (i.e., to move to the right, the
+ # angle must be 0) and raising clockwise.
+ # [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)
+ if aim.is_a? Vector
+ 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
+ @speed.x = 0
+ else
+ @x += @speed.x
+ end
+
+ if (@speed.y < 0 and @y + @speed.y <= aim.y) or (@speed.y >= 0 and @y + @speed.y >= aim.y)
+ @y = aim.y
+ @speed.y = 0
+ else
+ @y += @speed.y
+ end
+ else
+ rads = aim * Math::PI / 180
+ @speed.x = speed * Math.cos(rads)
+ @speed.y = speed * Math.sin(rads)
+ @x += @speed.x
+ @y += @speed.y
+ end
+ end
+
+ # Causes the object to move in cycles across multiple given points (the
+ # 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.
+ # [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>.
+ # [obst_obstacles] Obstacles that should be considered when moving objects
+ # from the +obstacles+ array, i.e., these obstacles won't
+ # interfere in the elevator's movement, but in the movement
+ # of the objects being carried.
+ # [obst_ramps] Ramps to consider when moving objects from the +obstacles+
+ # array, as described for +obst_obstacles+.
+ def cycle(points, speed, obstacles = nil, obst_obstacles = nil, obst_ramps = nil)
+ @cur_point = 0 if @cur_point.nil?
+ if obstacles
+ move_carrying points[@cur_point], speed, obstacles, obst_obstacles, obst_ramps
+ else
+ 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
+ end
+ end
+
+ private
+
+ 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
+ @bottom = o if y2.round(6) == o.y.round(6) && x2 > o.x && @x < x2o
+ @top = o if !o.passable && @y.round(6) == y2o.round(6) && x2 > o.x && @x < x2o
+ end
+ if @bottom.nil?
+ ramps.each do |r|
+ 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)
+ 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)
+ 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)
+ 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)
+ limit = @y + @speed.y
+ coll_list.each do |c|
+ limit = c.y + c.h if !c.passable && c.y + c.h > limit
+ end
+ limit
+ end
+ end
+end