11-12-2005, 01:00 PM
Line of Sight Script
by Ryethe
Nov 12 2005
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
Line of Sight
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
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.
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