Options

# Line of Sight Script

• #### Index

• Line of Sight Script
• 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.

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
Thanks given by: