Quick explanation: This script was previously made by Theraot who graciously created this script for me a few years a go, Today I attempted to update it and improve it! it work-ish apart from these weird issues I am met with.
Issue 1: A issue I had originally with the script was that each segment was not equal length this was because of the mouse speed, the player could move the mouse very fast and it would create huge segments, I have attempted to fix this issue by first calculating how many segments are needed, and I have tried creating a interpolated point for the new segment but it does not work it glitches out like its lagging behind.
var distance = Points[-2].distance_to(Mouse_Position)
# Check if the current distance is greater then segment distance
if distance > Segment_Distance:
var distanceDifference = distance - Segment_Distance
var segmentsToAdd = int(distanceDifference / Segment_Distance)
# Want to add segments for when the mouse is far away from the second to last point
# but I am not sure what to do, I attempted using a interpolated point
# but it glitches out
for i in range(segmentsToAdd):
var newPoint = Points[-2] + (Mouse_Position - Points[-2]).normalized() * Segment_Distance
Points.append(newPoint)
Create_Segment(Points[-2], newPoint)
Points[-1] = Mouse_Position
Update_Segment(Points[-2], Mouse_Position)
else:
# if the distance is not greater then we simply update the last position of the point to mouse
Points[-1] = Mouse_Position
Update_Segment(Points[-2], Mouse_Position)
Issue 2: When a loop is created and a new initial point is created there is left over segments and also the new initial point also seems to either missing or completely not drawing it, not sure, The process_loop function is over my head so I dont really understand it.
Issue 3: Sometimes when a loop is created one segment point? goes to 0,0 and I dont know why, this happens occasionally.
Full script can be copy and pasted into any scene as long as its attached to a Node2D.
extends Node2D
# Variables
var Line : Line2D # Holds the line created
var Line_Width : int = 10 # We use this for collision thickness
var Segment_Distance : int = 50 # The Distance of each segment also how we create a new one
var Segments : Node2D # Creates a segment when enough distance is made also holds area2Ds
var Loop : Node2D # Holds the first created loop until a new loop is created
var Points : PackedVector2Array = PackedVector2Array() # Holds all the points for the line2D
var Max_Points : int = 50 # To stop having multiple points on the screen
func _ready() -> void:
# Create Nodes to hold each part of the line loop detection
Line = Line2D.new()
Line.name = "Line"
Line.width = Line_Width
add_child(Line)
Segments = Node2D.new()
Segments.name = "Segments"
add_child(Segments)
Loop = Node2D.new()
Loop.name = "Loop"
add_child(Loop)
func _process(delta: float) -> void:
if Input.is_action_pressed("Left_Click"):
Process_Line(get_global_mouse_position())
if Input.is_action_just_released("Left_Click"):
Clear_All()
func Process_Line(Mouse_Position: Vector2) -> void:
# Get the size of points
var Point_Count = Points.size()
if Point_Count == 0: # Checks if its 0, we add 2 points for the start and current position
Points.append(Mouse_Position)
Points.append(Mouse_Position)
Create_Segment(Mouse_Position, Mouse_Position) # Create a segment to connect area2D
elif Point_Count == 1:
Points.append(Mouse_Position)
Create_Segment(Points[-2], Mouse_Position)
elif Point_Count > Max_Points:
Clear(Point_Count - Max_Points)
else:
var distance = Points[-2].distance_to(Mouse_Position)
# Check if the current distance is greater then segment distance
if distance > Segment_Distance:
var distanceDifference = distance - Segment_Distance
var segmentsToAdd = int(distanceDifference / Segment_Distance)
# Want to add segments for when the mouse is far away from the second to last point
# but I am not sure what to do, I attempted using a interpolated point
# but it glitches out
for i in range(segmentsToAdd):
var newPoint = Points[-2] + (Mouse_Position - Points[-2]).normalized() * Segment_Distance
Points.append(newPoint)
Create_Segment(Points[-2], newPoint)
Points[-1] = Mouse_Position
Update_Segment(Points[-2], Mouse_Position)
else:
# if the distance is not greater then we simply update the last position of the point to mouse
Points[-1] = Mouse_Position
Update_Segment(Points[-2], Mouse_Position)
# Apply the points array to the line2D points
Line.points = Points
# What does the loop detection currently buggy.
Process_Loop()
func Create_Segment(start: Vector2, end: Vector2) -> void:
# Get the rotation of the points
var points = rotated_rectangle_points(start, end, Line_Width)
var segment = Area2D.new()
segment.add_to_group("Player_Looper") # Add segment to a group to detect stuff
var collision = create_collision_polygon(points)
segment.add_child(collision)
Segments.add_child(segment)
func Update_Segment(start: Vector2, end: Vector2) -> void:
var points = rotated_rectangle_points(start, end, Line_Width)
var segment = Segments.get_child(Segments.get_child_count() - 1) as Area2D
var collision = segment.get_child(0) as CollisionPolygon2D
collision.set_polygon(points)
static func rotated_rectangle_points(start: Vector2, end: Vector2, width: float) -> Array:
var diff = end - start
var normal = diff.rotated(TAU / 4).normalized()
var offset = normal * width * 0.5
return [start + offset, start - offset, end - offset, end + offset]
static func create_collision_polygon(points: Array) -> CollisionPolygon2D:
var result = CollisionPolygon2D.new()
result.set_polygon(points)
return result
func Process_Loop() -> void:
var segments = Segments.get_children()
for index in range(segments.size() - 1, 0, -1):
var segment = segments[index]
var candidates = segment.get_overlapping_areas()
for candidate in candidates:
var candidate_index = segments.find(candidate)
if candidate_index == -1:
continue
if abs(candidate_index - index) > 1:
push_loop(candidate_index, index)
Clear(index)
return
func push_loop(first_index:int, second_index:int) -> void:
Clear_Loop()
var loop = Area2D.new()
var points = Points
points.resize(second_index)
for point_index in first_index + 1:
points.remove_at(0)
var collision = create_collision_polygon(points)
loop.add_child(collision)
Loop.add_child(loop)
func Clear_All() -> void:
# Clears the points and segments and also clear loops
Clear(Points.size())
Clear_Loop()
func Clear(index) -> void:
# Loop through the points array and remove each one based on index
for _clear in range(index):
if Points.size() > 0:
Points.remove_at(0)
# Get the current segments and also remove each segment
var segments = Segments.get_children()
for i in range(index):
if segments.size() > 0:
Segments.remove_child(segments[0])
segments[0].queue_free()
segments.remove_at(0)
Line.points = Points
func Clear_Loop() -> void:
for loop in Loop.get_children():
if is_instance_valid(loop):
loop.queue_free()
The solution I have found are these.
Issue 1 (Work-around):
When moving fast the segments become difference lengths, the main issue was that the process and physics process dont update quick enough for it to detect that its past a distance intern creating unequal segments, solution disable Vsync, disabling Vsync makes the whole program run super fast, disadvantage cant use Vsync, I also attempted on adding interpolated segments but they are buggy and dont work.
Issue 2:
When creating a loop some of the segments would still show and also some segment points would error out and go to 0,0. Solution was to make it use a duplicate array, it seems from 3.0 to 4.0 assigning an array to a variable does not create a duplicate instead it references it, solution array.duplicate. this fixes these issues.
Help:
I would love it if someone can help me figure out why my attempt at fixing issue 1 did not work with adding interpolated segments, please enable Vsync to see issue and move the mouse fast, else disable it to make it work better.
Code:
extends Node2D
var MouseMotion : Vector2 = Vector2.ZERO
var Looper : Line2D # The line renderer
var LinePoints : PackedVector2Array = PackedVector2Array()
var LineWidth : int = 10 # Line thickness also used for collision
var SegmentNode: Node2D # Holds segments that make a loop
var LoopNode : Node2D # Holds the loop when a detection is made
@export var MaxPoints : int = 45 # How many points that should only render.
@export var MinDistance : int = 30 # Segment length.
func _ready() -> void:
Looper = Line2D.new()
Looper.name = "Line2D"
add_child(Looper)
SegmentNode = Node2D.new()
SegmentNode.name = "SegmentNode"
add_child(SegmentNode)
LoopNode = Node2D.new()
LoopNode.name = "LoopNode"
add_child(LoopNode)
func _input(event):
if event is InputEventMouseMotion:
MouseMotion = event.position
func _process(delta: float) -> void:
if Input.is_action_pressed("LeftClick"):
ProcessLine(MouseMotion)
elif Input.is_action_just_released("LeftClick"):
ClearPoints(LinePoints.size())
func ProcessLine(MousePosition : Vector2) -> void:
if LinePoints.size() == 0:
LinePoints.append(MousePosition)
LinePoints.append(MousePosition)
CreateSegment(MousePosition,MousePosition)
elif LinePoints.size() == 1:
LinePoints.append(MousePosition)
CreateSegment(LinePoints[-2],MousePosition)
elif LinePoints.size() > MaxPoints:
ClearPoints(LinePoints.size() - MaxPoints)
else:
var Distance = LinePoints[-2].distance_to(MousePosition)
var distanceDifference = Distance - MinDistance
var NumSegments = int(distanceDifference / MinDistance)
for i in range(NumSegments):
var newPoint = LinePoints[-2] + (MousePosition - LinePoints[-2]).normalized() * MinDistance
LinePoints.append(newPoint)
CreateSegment(LinePoints[-2], newPoint)
LinePoints[-1] = MousePosition
UpdateSegment(LinePoints[-2], MousePosition)
Looper.points = LinePoints
ProcessLoop()
func ClearPoints(Index : int):
for _clear in range(Index):
if LinePoints.size() > 0:
LinePoints.remove_at(0)
var segments = SegmentNode.get_children()
for i in range(Index):
if segments.size() > 0:
SegmentNode.remove_child(segments[0])
segments[0].queue_free()
segments.remove_at(0)
Looper.points = LinePoints
ProcessLoop()
func CreateSegment(start: Vector2, end: Vector2) -> void:
var points = rotated_rectangle_points(start, end, LineWidth)
var segment = Area2D.new()
segment.add_to_group("BattlePlayer")
var collision = create_collision_polygon(points)
segment.add_child(collision)
SegmentNode.add_child(segment)
func UpdateSegment(start: Vector2, end: Vector2) -> void:
var points = rotated_rectangle_points(start, end, LineWidth)
var segment = SegmentNode.get_child(SegmentNode.get_child_count() - 1) as Area2D
var collision = segment.get_child(0) as CollisionPolygon2D
collision.set_polygon(points)
static func rotated_rectangle_points(start: Vector2, end: Vector2, width: float) -> Array:
var diff = end - start
var normal = diff.rotated(TAU / 4).normalized()
var offset = normal * width * 0.5
return [start + offset, start - offset, end - offset, end + offset]
static func create_collision_polygon(points: Array) -> CollisionPolygon2D:
var result = CollisionPolygon2D.new()
result.set_polygon(points)
return result
func ProcessLoop() -> void:
var segments = SegmentNode.get_children()
for index in range(segments.size() - 1, 0, -1):
var segment = segments[index]
var candidates = segment.get_overlapping_areas()
for candidate in candidates:
var candidate_index = segments.find(candidate)
if candidate_index == -1:
continue
if abs(candidate_index - index) > 1:
CreateLoop(candidate_index,index)
ClearPoints(LinePoints.size())
return
func CreateLoop(FirstIndex:int, SecondIndex:int) -> void:
ClearLoop()
var Loop = Area2D.new()
var Points = LinePoints.duplicate()
Points.resize(SecondIndex)
for PointIndex in FirstIndex + 1:
Points.remove_at(0)
var Collision = create_collision_polygon(Points)
Loop.add_child(Collision)
LoopNode.add_child(Loop)
func ClearLoop() -> void:
for Loop in LoopNode.get_children():
if is_instance_valid(Loop):
Loop.queue_free()