module MiniGL::Movement

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.

Attributes

bottom[R]

The object that is making contact with this from below. If there’s no contact, returns nil.

h[R]

Height of the bounding box.

left[R]

The object that is making contact with this from the left. If there’s no contact, returns nil.

mass[R]

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.

max_speed[R]

A Vector with the speed limits for the object (x: horizontal component, y: vertical component).

passable[RW]

Whether a moving object can pass through this block when coming from below. This is a common feature of platforms in platform games.

prev_speed[R]

A Vector containing the speed of the object in the previous frame.

right[R]

The object that is making contact with this from the right. If there’s no contact, returns nil.

speed[R]

A Vector with the current speed of the object (x: horizontal component, y: vertical component).

stored_forces[RW]

A Vector with the horizontal and vertical components of a force that be applied in the next time move is called.

top[R]

The object that is making contact with this from above. If there’s no contact, returns nil.

w[R]

Width of the bounding box.

x[RW]

The x-coordinate of the top left corner of the bounding box.

y[RW]

The y-coordinate of the top left corner of the bounding box.

Public Instance Methods

bounds() click to toggle source

Returns the bounding box as a Rectangle.

# File lib/minigl/movement.rb, line 227
def bounds
  Rectangle.new @x, @y, @w, @h
end
cycle(points, speed, obstacles = nil, obst_obstacles = nil, obst_ramps = nil, stop_time = 0) click to toggle source

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 include Movement.

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.

stop_time

Optional stop time (in frames) when the object reaches each of the points.

# File lib/minigl/movement.rb, line 500
def cycle(points, speed, obstacles = nil, obst_obstacles = nil, obst_ramps = nil, stop_time = 0)
  unless @cycle_setup
    @cur_point = 0 if @cur_point.nil?
    if obstacles
      obst_obstacles = [] if obst_obstacles.nil?
      obst_ramps = [] if obst_ramps.nil?
      move_carrying points[@cur_point], speed, obstacles, obst_obstacles, obst_ramps
    else
      move_free points[@cur_point], speed
    end
  end
  if @speed.x == 0 and @speed.y == 0
    unless @cycle_setup
      @cycle_timer = 0
      @cycle_setup = true
    end
    if @cycle_timer >= stop_time
      if @cur_point == points.length - 1
        @cur_point = 0
      else
        @cur_point += 1
      end
      @cycle_setup = false
    else
      @cycle_timer += 1
    end
  end
end
move(forces, obst, ramps, set_speed = false) click to toggle source

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 include Movement.

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.

# File lib/minigl/movement.rb, line 246
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, nil]
      y_aim = @y + @speed.y + (dn ? @h : 0); y_lim_def = [y_aim, nil]
      coll_list.each do |c|
        find_limits(c, x_aim, y_aim, x_lim_def, y_lim_def, up, rt, dn, lf)
      end

      if x_lim_def[0] != x_aim && y_lim_def[0] != y_aim
        x_time = (x_lim_def[0] - @x - (lf ? 0 : @w)).to_f / @speed.x
        y_time = (y_lim_def[0] - @y - (up ? 0 : @h)).to_f / @speed.y
        if x_time < y_time
          stop_at_x(x_lim_def[0], lf)
          move_bounds = Rectangle.new(@x, up ? @y + @speed.y : @y, @w, @h + @speed.y.abs)
          stop_at_y(y_lim_def[0], up) if move_bounds.intersect?(y_lim_def[1].bounds)
        else
          stop_at_y(y_lim_def[0], up)
          move_bounds = Rectangle.new(lf ? @x + @speed.x : @x, @y, @w + @speed.x.abs, @h)
          stop_at_x(x_lim_def[0], lf) if move_bounds.intersect?(x_lim_def[1].bounds)
        end
      elsif x_lim_def[0] != x_aim
        stop_at_x(x_lim_def[0], lf)
      elsif y_lim_def[0] != y_aim
        stop_at_y(y_lim_def[0], up)
      end
    end
  end
  @x += @speed.x
  @y += @speed.y

  ramps.each do |r|
    r.check_intersection self
  end
  check_contact obst, ramps
end
move_carrying(arg, speed, carried_objs, obstacles, ramps, ignore_collision = false) click to toggle source

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).

carried_objs

An array of objects that can potentially be carried by this object while it moves. The objects must respond to x, y, w and h.

obstacles

Obstacles that should be considered for collision checking with the carried objects, if they include the Movement module, and with this object too, if moving with forces and the ignore_collision flag is false.

ramps

Ramps that should be considered for the carried objects, if they include the Movement module, and for this object too, if moving with forces and ignore_collision is false.

ignore_collision

Set to true to make this object ignore collision even when moving with forces.

# File lib/minigl/movement.rb, line 363
def move_carrying(arg, speed, carried_objs, obstacles, ramps, ignore_collision = false)
  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
    x_aim = @x + @speed.x; y_aim = @y + @speed.y
  else
    x_aim = @x + @speed.x + G.gravity.x + arg.x
    y_aim = @y + @speed.y + G.gravity.y + arg.y
  end

  passengers = []
  carried_objs.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
    move(arg, ignore_collision ? [] : obstacles, ignore_collision ? [] : ramps)
  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|
    if p.class.included_modules.include?(Movement)
      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, obstacles, 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 }
    else
      p.x += forces.x
      p.y += forces.y
    end
  end
  G.gravity = prev_g
end
move_free(aim, speed) click to toggle source

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.

# File lib/minigl/movement.rb, line 442
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

Private Instance Methods

check_contact(obst, ramps) click to toggle source
# File lib/minigl/movement.rb, line 531
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
find_down_limit(coll_list) click to toggle source
# File lib/minigl/movement.rb, line 578
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
find_left_limit(coll_list) click to toggle source
# File lib/minigl/movement.rb, line 570
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
find_limits(obj, x_aim, y_aim, x_lim_def, y_lim_def, up, rt, dn, lf) click to toggle source
# File lib/minigl/movement.rb, line 594
def find_limits(obj, x_aim, y_aim, x_lim_def, y_lim_def, up, rt, dn, lf)
  x_lim =
    if obj.passable
      x_aim
    elsif rt
      obj.x
    else
      obj.x + obj.w
    end

  y_lim =
    if dn
      obj.y
    elsif obj.passable
      y_aim
    else
      obj.y + obj.h
    end

  x_v = x_lim_def[0]; y_v = y_lim_def[0]
  if obj.passable
    if dn && @y + @h <= y_lim && y_lim < y_v
      y_lim_def[0] = y_lim
      y_lim_def[1] = obj
    end
  elsif (rt && @x + @w > x_lim) || (lf && @x < x_lim)
    # Can't limit by x, will limit by y
    if (dn && y_lim < y_v) || (up && y_lim > y_v)
      y_lim_def[0] = y_lim
      y_lim_def[1] = obj
    end
  elsif (dn && @y + @h > y_lim) || (up && @y < y_lim)
    # Can't limit by y, will limit by x
    if (rt && x_lim < x_v) || (lf && x_lim > x_v)
      x_lim_def[0] = x_lim
      x_lim_def[1] = obj
    end
  else
    x_time = (x_lim - @x - (lf ? 0 : @w)).to_f / @speed.x
    y_time = (y_lim - @y - (up ? 0 : @h)).to_f / @speed.y
    if x_time > y_time
      # Will limit by x
      if (rt && x_lim < x_v) || (lf && x_lim > x_v)
        x_lim_def[0] = x_lim
        x_lim_def[1] = obj
      end
    elsif (dn && y_lim < y_v) || (up && y_lim > y_v)
      y_lim_def[0] = y_lim
      y_lim_def[1] = obj
    end
  end
end
find_right_limit(coll_list) click to toggle source
# File lib/minigl/movement.rb, line 562
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
find_up_limit(coll_list) click to toggle source
# File lib/minigl/movement.rb, line 586
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
stop_at_x(x, moving_left) click to toggle source
# File lib/minigl/movement.rb, line 647
def stop_at_x(x, moving_left)
  @speed.x = 0
  @x = moving_left ? x : x - @w
end
stop_at_y(y, moving_up) click to toggle source
# File lib/minigl/movement.rb, line 652
def stop_at_y(y, moving_up)
  @speed.y = 0
  @y = moving_up ? y : y - @h
end