Line of Sight Script
#1
Line of Sight Script
by Ryethe
Nov 12 2005

This is a locked, single-post thread from Creation Asylum. Archived here to prevent its loss.
No support is given. If you are the owner of the thread, please contact administration.


VERSION 2.5 Release

!!!NOTE!!! If you have downloaded any other version of this system, please use this new version. A major bug was discovered tonight and has been fixed in this release

What this script does is check if is there is an unimpeded line from one event to another. How it works is that it creates a line that passes through the coordinates of the two events. The system then checks the tiles that this line passes through for passibility. The system returns false if there is no line of sight and true if there is a line of sight. If the target is the player then this will now only function if the event is onscreen.

IMPORTANT NOTE: By default, events set to a graphic of (none) are passable by the player, but NOT other events. To overcome this RMXP issue, set all (none) events to Through.

There are two different scripts you need to add. One contains the line of sight detection and the other some additional Math functions. Add each anywhere above main.

Math
Code:
#======================================
# ■ Additional Math Functions
#------------------------------------------------------------------------------
#  By: Ryethe
#   Date: 11.11.05
#   Version 1
#======================================
module Math


  def rotation_modifier(x,y)
    if (x == 0 and y == 0)
      return 0
    end
    if (x > 0 and y >= 0)
      angle = 0
    elsif (x <= 0 and y > 0)
      angle = 90
    elsif (x < 0 and y <= 0)
      angle = 180
    elsif (x >= 0 and y < 0)
      angle = 270
    end
    return angle
  end
  
  def vector_angle(x,y)
    if (x*y == 0)
      return rotation_modifier(x,y)
    end
    ratio = y.to_f / x.to_f
    #vector is in quadrant 1 or 3
    if (ratio > 0)
      angle = (atan(ratio) / (PI/180)) + rotation_modifier(x,y)
    #vector is in quadrant 2 or 4
    else
      angle = (atan(ratio) / (PI/180)) + 90 + rotation_modifier(x,y)
    end
    return angle
  end
  
  def within_angle(angle1, angle2, angle_check)
    #If the angle range is normal (from small angle to big angle)
    if angle2 > angle1
      if (angle1..angle2) === angle_check or (angle_check == 0 and angle2 == 360)
        return true
      end
    #If the angle range is reverse (from big angle to small)
    else
      if !((angle2+1..angle1-1) === angle_check) or (angle1 == 360 and angle2 == 0)
        return true
      end
    end
    return false
  end
  
  def distance_past_ellipse(e1_x, e1_y, e2_x, e2_y, a_size, b_size)
    a = a_size.to_f
    b = b_size.to_f
    x2 = (e2_x - e1_x).to_f
    y2 = (e2_y - e1_y).to_f
    if (y2 == 0.0) and (x2 == 0.0)
      return 0.0
    end
    temp_squared = (a**2 * b**2) / (a**2 * y2**2 + b**2 * x2**2)
    y_squared = y2**2 * temp_squared
    x_squared = x2**2 * temp_squared
    return (Math.sqrt((e2_x-e1_x) ** 2 + (e2_y - e1_y) ** 2) - Math.sqrt(y_squared + x_squared)).ceil
  end
  
  def distance(x1, y1, x2, y2)
    return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
  end
  
  module_function :distance
  module_function :distance_past_ellipse
  module_function :within_angle
  module_function :rotation_modifier
  module_function :vector_angle
end

Line of Sight
Code:
#======================================
# ■ Line of Sight
#------------------------------------------------------------------------------
#  By: Ryethe
#   Date: 11.12.05
#   Version 2.5
#======================================
class Angle_Set
  attr_accessor :angle1
  attr_accessor :angle2
  def initialize
    angle1 = 0
    angle1 = 0
  end
end

class Game_Character
  attr_accessor :x_shift
  attr_accessor :y_shift
  DOWN = 2
  LEFT = 4
  RIGHT = 6
  UP = 8
  
  alias character_initialize initialize
  def initialize
    character_initialize
    @x_shift = 0
    @y_shift = -16
  end
  
  def modified_angle(angle)
    angle_set = Angle_Set.new
    case self.direction
    when UP
      angle_set.angle1 = 90 - angle
      if angle_set.angle1 < 0
        angle_set.angle1 += 360
      end
      angle_set.angle2 = 90 + angle
      if angle_set.angle2 >= 360
        angle_set.angle2 -= 360
      end
    when LEFT
      angle_set.angle1 = 180 - angle
      if angle_set.angle1 < 0
        angle_set.angle1 += 360
      end
      angle_set.angle2 = 180 + angle
      if angle_set.angle2 >= 360
        angle_set.angle2 -= 360
      end
    when DOWN
      angle_set.angle1 = 270 - angle
      if angle_set.angle1 < 0
        angle_set.angle1 += 360
      end
      angle_set.angle2 = 270 + angle
      if angle_set.angle2 >= 360
        angle_set.angle2 -= 360
      end      
    when RIGHT
      angle_set.angle1 = 360 - angle
      if angle_set.angle1 < 0
        angle_set.angle1 += 360
      end
      angle_set.angle2 = 360 + angle
      if angle_set.angle2 >= 360
        angle_set.angle2 -= 360
      end      
    end
    return angle_set
  end

  def on_screen(s_x,s_y)
    top_left_x = ($game_map.display_x / 128).to_i
    top_left_y = ($game_map.display_y / 128).to_i
    x = (s_x.to_f / 32.0).ceil.to_i
    y = (s_y.to_f / 32.0).ceil.to_i
    if (top_left_x..(19 + top_left_x)).include?(x)
      if (top_left_y..(14 + top_left_y)).include?(y)
        return true
      end
    end
    return false
  end
  
  def within_view_space(target, t_x, t_y, s_x, s_y)
    #checks to see if the player is within the defined angle
    if self.list[2] != nil and self.list[2].code == 108 and self.list[3] != nil and self.list[3].code == 108
      if self.list[2].parameters[0].to_i != self.list[3].parameters[0].to_i
        target_angle = Math.vector_angle(t_x - s_x, (t_y - s_y) * (-1))
        mid_point = (self.list[2].parameters[0].to_i - self.list[3].parameters[0].to_i).abs.to_f / 2.0
        angle_set = modified_angle(mid_point)
        if !Math.within_angle(angle_set.angle1, angle_set.angle2, target_angle)
          return false
        end
      end
    end
    #checks to see if the player is within the view ellipse
    if self.list[4] != nil and self.list[5] != nil and self.list[5].code == 108 and
    self.list[4].code == 108
      a = self.list[4].parameters[0].to_i
      b = self.list[5].parameters[0].to_i
      if a != 0 or b != 0
        # a has value (horizontal line)
        if b == 0
          if (t_y - s_y) == 0
            if a < (Math.distance(s_x,s_y,t_x,t_y) / 32.0)
              return false
            end
          end
        # b has value (vertical line)
        elsif a == 0
          if (t_x - s_x) == 0
            if b < (Math.distance(s_x,s_y,t_x,t_y) / 32.0)
              return false
            end
          end
        # a and b form an ellipse
        else
          if 0 < Math.distance_past_ellipse(s_x / 32.0, s_y / 32.0, t_x / 32.0, t_y / 32.0, a, b)
            return false
          end
        end
      end
    end
    return true
  end
  
  def line_of_sight(target)
    behind_target_flag = false
    #sets the vantage point of both objects.  Currently the middle of the tile
    #on which the object sits.
    target_screen_x = (target.x * 32 + target.x_shift).to_i
    target_screen_y = (target.y * 32 + target.y_shift).to_i
    self_screen_x = (self.x * 32 + self.x_shift).to_i
    self_screen_y = (self.y * 32 + self.y_shift).to_i
    #makes sure the target is within bounds before checking for line of sight.
    if !within_view_space(target, target_screen_x, target_screen_y, self_screen_x, self_screen_y)
      return false
    end
    #makes sure the on-looker is within the target's screen if the target is the player.
    if target.type == Game_Player and
    !on_screen(self_screen_x, self_screen_y)
      return false
    end
    #declares the possible locations to check for a line of sight.
    field_height = (self_screen_y / 32 - target_screen_y / 32).abs
    feild_width = (self_screen_x / 32 - target_screen_x / 32).abs
    #determines the slope and y intercept of the line directly between the two
    #objects vantage points
    line_m = (self_screen_y - target_screen_y).to_f / (self_screen_x - target_screen_x).to_f
    line_b = target_screen_y - target_screen_x * line_m
    #Determines the range of the tiles through which the line passes
    bar_start = (self_screen_x / 32 + 1)
    bar_end = (target_screen_x / 32 + 1)
    #If the target is in front of the object then the range is the vertical line
    #to the right of the object to the vertical line to right of the target.
    #However if the target is behind the object then the lines are taken to the
    #left of each object.
    if self_screen_x / 32 > target_screen_x / 32
      bar_start -= 1
      bar_end -= 1
      behind_target_flag = true
    end
    #The starting poting of the algorithm
    reference_x = self_screen_x / 32
    reference_y = self_screen_y / 32
    if bar_start > bar_end
      bar_start *= -1
      bar_end *= -1
    end
    #The two objects form a veritcal line.
    if (self_screen_x - target_screen_x) == 0
      block_start = self_screen_y / 32
      block_end = target_screen_y / 32
      if block_start > block_end
        block_start *= -1
        block_end *= -1
      end
      #iterates every tile inbetween the two targets checking for passability
      for j in block_start..block_end
        x = reference_x
        y = j.abs
        if !(x == target.x and y == target.y) and !(x == self.x and y == self.y) and
        !$game_map.see_through_terrain(x,y) and !passable?(x, y, 0)
          return false
        end        
      end
    #The two objects form a horizontal line.
    elsif (self_screen_y - target_screen_y) == 0
      block_start = self_screen_x / 32
      block_end = target_screen_x / 32
      if block_start > block_end
        block_start *= -1
        block_end *= -1
      end
      for j in block_start..block_end
        x = j.abs
        y = reference_y
        #print x
        #print y
        #iterates every tile inbetween the two targets checking for passability
        if !(x == target.x and y == target.y) and !(x == self.x and y == self.y) and
        !$game_map.see_through_terrain(x,y) and !passable?(x, y, 0)
          #print "TEST:" + $game_player.x.to_s + " " + j.to_s
          return false
        end        
      end
    #Other
    else
      #print bar_start
      #print bar_end
      #iterates the vertical lines that need to be checked for itercepts
      for i in bar_start..bar_end
        intersect_x = (i.abs * 32)
        intersect_y = (intersect_x * line_m + line_b)
        block_start = (intersect_y / 32).to_i
        block_end = reference_y.to_i
        #prevents out of bounds checking
        if (block_start - self_screen_y / 32) > field_height
          block_start = target.y
        end
        #allows reverse iteration
        if block_start > block_end
          block_start *= -1
          block_end *= -1
        end
        #Alters for the special case in which the interect line passes directly through the corner of a tile.
        if intersect_y % 32 == 0
          if target_screen_y / 32 > self_screen_y / 32
            block_start += 1
          else
            block_end -= 1
          end
        end
        #print "START:" + block_start.abs.to_s
        #print "END:" + block_end.abs.to_s
        #iterates from the tile where the line itersects the veritcal line
        #up to the reference point
        for j in block_start..block_end
          x = reference_x
          y = j.abs
          #print "X:" + x.to_s
          #print "Y:" + y.to_s
          if !(x == target.x and y == target.y) and !(x == self.x and y == self.y) and
          !$game_map.see_through_terrain(x,y) and !passable?(x, y, 0)
          #print "failed"
            return false
          end
        end
        #sets a new reference point based on where the last iteration stopped.
        reference_x = (i + (behind_target_flag ? 1 : 0)).abs
        reference_y = (intersect_y / 32)
      end
    end
    return true
  end
end

class Game_Map
  def see_through_terrain(x,y)
    #defines terrain tag 1 as see through
    return ($game_map.terrain_tag(x,y) == 1)
  end
end

class Interpreter
  def line_of_sight(event1_id = 0, event2_id = @event_id)
    if event1_id == 0
      event1 = $game_player
    else
      event1 = $game_map.events[event2_id]
    end
    $game_map.events[event2_id].line_of_sight(event1)
  end

  def set_shift_x(value, event_id = @event_id)
    $game_map.events[event_id].shift_x = value
  end

  def set_shift_y(value, event_id = @event_id)
    $game_map.events[event_id].shift_y = value
  end
end


Usage
In any of the event script commands type line_of_sight. This will check if there is a line of sight from the event that made the call to the player. To check between a different two events use line_of_sight(event1_id, event2_id). Keep in mind that 0 is used for the player and must ALWAYS be event1_id. The system will crash out if you declare event2_id as player.

IMPORTANT NOTE: Parts of this script are based off of an old version of the shadows script that I had kicking around. Since I personally have no use for the shadow script (mostly due to some of the problematic linear algebra concepts surrounding it; another story for another time) it was morphed into a light sourcing script. In short the comment system is used. What this means it that it will NOT be compatible with the new version of the shadow script. You can, however, edit the script so that it reads the comments the same way as the new shadows script, but that's up to you.

Ignored Tiles
To make a tile ignored by the script, set its terrain tag to 1 in the editor

Detachable Viewpoint
In an event script command type self.set_shift_x(value) or self.set_shift_y(value) to shift the viewpoint to those coordinates. To change the viewpoint of another event type use (value, event_id). The function defaults to the current event's event_id. Each event defaults to (0, -16) as a viewpoint (that's the center of each event).

View Distance and View Angle
In your event the first lines will look something like this:
Comment: n
Comment: 0
Comment: angle1
Comment: angle2
Comment: a
Comment: b

angle1 is the start angle and angle2 is the end angle. The total angle will be divided into two equal parts and be turned into the viewing "wedge" of the event. For example, to get 90 degrees of viewing in from of the event, type 0 in place of angle1 and type 90 in place of angle2.

a is the horizontal radius of the viewpoint and b is the vertical radius of the viewpoint. For example to have a circle field of view with a radius of 4 you would type 4 in place of a and 4 in place of b.

The other two are ignored for this script comments are ignored for this script, but are needed (in my game they're used for determining if something is a light source and how much light it emits).

If someone has a better idea of how to obtain line of sight, please let me know.

Enjoy. Any credit given will be greatly appreciated.

-Ryethe
}


Possibly Related Threads…
Thread Author Replies Views Last Post
  Emotion Script Ánemus 0 2,542 08-29-2008, 01:00 PM
Last Post: Ánemus
  Beran's iPod script Sniper308 0 3,008 08-09-2008, 01:00 PM
Last Post: Sniper308
  NeoABS & NeoSABS ()enemy processes script azrith001 0 2,706 04-04-2008, 01:00 PM
Last Post: azrith001
  Blur Effect Script Hadriel 0 2,953 01-30-2008, 01:00 PM
Last Post: Hadriel
  Warp Script Sheol 0 2,874 12-28-2007, 01:00 PM
Last Post: Sheol
  AIM Script Pack vgvgf 0 3,193 09-13-2007, 01:00 PM
Last Post: vgvgf
  Audio Encryption Script InfiniteSpawn 0 2,579 05-09-2007, 01:00 PM
Last Post: InfiniteSpawn
  Credits Script Remake avatarmonkeykirby 0 2,591 03-10-2007, 01:00 PM
Last Post: avatarmonkeykirby
  Leon Blade's Percent Script Leon Blade 0 2,591 03-05-2007, 01:00 PM
Last Post: Leon Blade
  Cogwheels original pixelmovement script!!! mechacrash 0 2,532 01-14-2007, 01:00 PM
Last Post: mechacrash



Users browsing this thread: 1 Guest(s)