Search code examples
godotgodot4

Attempt to update Line2D loop detector to 4.0, I am met with issues


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.

enter image description here

Issue 3: Sometimes when a loop is created one segment point? goes to 0,0 and I dont know why, this happens occasionally. enter image description here

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

Solution

  • 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()