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