PathFinding with VectorForces

What do you want to achieve? I’m trying to create an Enemy NPC that pathfinds using VectorForces. The moving part of the enemy would be a sphere collider and I’d use the VectorForce to move it around. This allows me to have full control of how the enemy moves.

What is the issue? I’ve tried doing this but I’m not sure how to detect if the enemy has reached a point in the path. Also, since the function is ran every Heartbeat, the movement becomes all jittery.

What solutions have you tried so far? I tried making it repeatedly check if it reached the waypoint but it didn’t work.

This is the current code that I have for the Pathfinding. I know its pretty janky, but I don’t know what else to do here:

-- The "MoveTowardsToLoc" function sets the VectorForce' force to the direction of the Target.

    local function MoveToTarget(targetPosition)
        local ColPos = collider.Position
        
        local path = PathfindingService:CreatePath({
            AgentRadius = 4.742,
            AgentHeight = 4.742,
        })

        path:ComputeAsync(ColPos, targetPosition)

        if path.Status == Enum.PathStatus.Success then
            CurrentPath = path

            local waypoints = path:GetWaypoints()
            for _, waypoint in ipairs(waypoints) do
                if not Enemy.Alive then return end -- Stop movement if dead
                
                MoveTowardsToLoc(waypoint.Position) -- Move towards each waypoint
            end
        else
            MoveTowardsToLoc(targetPosition)
        end
    end
1 Like

Instead of moving directly to each waypoint in a loop, consider checking the distance to the current waypoint in your Heartbeat function. Try this

local currentWaypointIndex = 1

local function MoveToTarget(targetPosition)
    local ColPos = collider.Position
    local path = PathfindingService:CreatePath({
        AgentRadius = 4.742,
        AgentHeight = 4.742,
    })

    path:ComputeAsync(ColPos, targetPosition)

    if path.Status == Enum.PathStatus.Success then
        CurrentPath = path
        local waypoints = path:GetWaypoints()
        currentWaypointIndex = 1 
        MoveTowardsToLoc(waypoints[currentWaypointIndex].Position)
    else
        MoveTowardsToLoc(targetPosition)
    end
end

game:GetService("RunService").Heartbeat:Connect(function()
    if CurrentPath and CurrentPath.Status == Enum.PathStatus.Computing then return end

    local waypoints = CurrentPath:GetWaypoints()
    if currentWaypointIndex <= #waypoints then
        local waypoint = waypoints[currentWaypointIndex]
        local distance = (collider.Position - waypoint.Position).Magnitude

        if distance < 2 then
            currentWaypointIndex = currentWaypointIndex + 1
            if currentWaypointIndex <= #waypoints then
                MoveTowardsToLoc(waypoints[currentWaypointIndex].Position)
            end
        else
            MoveTowardsToLoc(waypoint.Position)
        end
    end
end)
1 Like

I applied this to my code and it it working a bit better. For some reason, the collider is stuck moving to the first waypoint index. I don’t know why though. Here’s my code:

--// Math Functions
local v3 = Vector3.new

--// Variables
local Runservice = game:GetService("RunService")

local Model = script.Parent
local collider = Model.collider

local VectorForce = collider.VectorForce

local Gyro = collider.BodyGyro

local Config = Model.config
local MaxWander = Config.MaxWander.Value

local MaxSearchDist = Config.MaxSearchDist.Value

local Speed = Config.Speed.Value
local Friction = Config.Friction.Value

local function UpdateFriction(dt:number)
    collider.Velocity =  collider.Velocity - v3(collider.Velocity.X,0,collider.Velocity.Z)*Friction*dt
end

local function FindPlayer()
    local col = workspace:FindFirstChild("goal")
    if col and (collider.Position - col.Position).Magnitude < MaxSearchDist then
        return col
    end
end

local pfs = game:GetService("PathfindingService")


local WaypointIndex = 1

local CurrentPath
local LastMovePoint = v3()

local function MoveTowardsToLoc(location:Vector3)
    local direction = (location - collider.Position).Unit
    direction = direction * Speed

    VectorForce.Force = v3(direction.X,0,direction.Z)
    local ColCF = collider.CFrame

    local Look = direction * v3(1,0,1)
    if Look.Magnitude ~= 0 then
        Gyro.CFrame = CFrame.lookAlong(ColCF.Position,direction * v3(1,0,1),ColCF.UpVector).Rotation 
    end
end

local function CreatePath(location : Vector3)
    if CurrentPath == nil then
        local ColPos = collider.Position

        local Path = pfs:CreatePath()
        Path:ComputeAsync(ColPos,location)

        warn("Path Created: "..#Path:GetWaypoints())
        CurrentPath = Path
        CurrentWayPointIndex = 1 
    end
end

local function FollowPath()
    local WayPoints : {PathWaypoint} = CurrentPath:GetWaypoints()
    local CurrentPoint : PathWaypoint = WayPoints[CurrentWayPointIndex]

    local ColPos = collider.Position
    local Distance = (ColPos - CurrentPoint.Position).Magnitude

    local MovePoint

    if Distance < 2 then
        CurrentWayPointIndex += 1

       if CurrentWayPointIndex <= #WayPoints then
            local NewPoint = WayPoints[CurrentWayPointIndex]
            MovePoint = NewPoint.Position
        else
            warn("Path Removed")
            CurrentPath = nil
            CurrentWayPointIndex = nil
            return
        end
    else
        MovePoint = CurrentPoint.Position
    end

    if LastMovePoint ~= MovePoint then
        warn("Moved: "..CurrentWayPointIndex)
        MoveTowardsToLoc(MovePoint)
    end
end

Runservice.PostSimulation:Connect(function(deltaTime)
    local target = FindPlayer()
    if target then
        if CurrentPath then
            if CurrentPath.Status == Enum.PathStatus.Success then
                FollowPath()
            else
                MoveTowardsToLoc(target.Position)
            end
        else
            CreatePath(target.Position)
        end
    else
        print("No Target!")
    end

    if collider.Velocity.Magnitude ~= 0 then
        UpdateFriction(deltaTime)
    end
end)

I figured out a way for it to work. All I had to do is remove the Y axis of the 2 positions:

local function ry(vec:Vector3)
        return vec*v3(1,0,1)
    end


    local function FollowPath()
        local WayPoints : {PathWaypoint} = CurrentPath:GetWaypoints()
        local CurrentPoint : PathWaypoint = WayPoints[CurrentWayPointIndex]

        local ColPos = Enemy.collider.Position
        local Distance = (ry(ColPos) - ry(CurrentPoint.Position)).Magnitude

        local MovePoint

        if Distance < 2 then
            CurrentWayPointIndex += 1

            if not (CurrentWayPointIndex > #WayPoints) then
                local NewPoint = WayPoints[CurrentWayPointIndex]
                MovePoint = NewPoint.Position
            else
                warn("Path Removed")

                CurrentPath = nil
                CurrentWayPointIndex = nil
                return
            end
        else
            MovePoint = CurrentPoint.Position
        end

        if LastMovePoint ~= MovePoint then
            warn("Moved")
            MoveTowardsToLoc(MovePoint)
        end
    end
1 Like