Hello, I recently had a problem with a script about an npc pathfinding/direct movement, here is the script with the problem:
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local zombie = script.Parent
local humanoid = zombie:WaitForChild("Humanoid")
local rootPart = zombie:WaitForChild("HumanoidRootPart")
local ATTACK_DISTANCE = 5
local UPDATE_INTERVAL = 0.5
local RAYCAST_DISTANCE = 20
local JUMP_COOLDOWN = 1
local JUMP_HEIGHT_THRESHOLD = 5
local lastJumpTime = 0
local STUCK_THRESHOLD = 2
local lastPosition = rootPart.Position
local stuckTime = 0
local DAMAGE_AMOUNT = 10
local DAMAGE_COOLDOWN = 2
local lastDamageTime = 0
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true
})
local attackAnimation = script:WaitForChild("AttackAnimation")
local attackTrack = humanoid:LoadAnimation(attackAnimation)
-- Función para verificar si el zombie está en el suelo
local function isOnGround()
local rayStart = rootPart.Position
local rayDirection = Vector3.new(0, -3.1, 0)
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {zombie}
local raycastResult = workspace:Raycast(rayStart, rayDirection, raycastParams)
return raycastResult ~= nil
end
-- Intentar saltar si es necesario
local function tryJump()
local currentTime = tick()
if currentTime - lastJumpTime >= JUMP_COOLDOWN and isOnGround() then
humanoid.Jump = true
lastJumpTime = currentTime
return true
end
return false
end
-- Buscar al jugador más cercano
local function findNearestPlayer()
local nearestPlayer = nil
local shortestDistance = math.huge
for _, player in ipairs(Players:GetPlayers()) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") and player.Character:FindFirstChild("Humanoid").Health > 0 then
local distance = (player.Character.HumanoidRootPart.Position - rootPart.Position).Magnitude
if distance < shortestDistance then
shortestDistance = distance
nearestPlayer = player
end
end
end
return nearestPlayer
end
local function getIgnoreList()
local ignoreList = {zombie}
for _, player in ipairs(Players:GetPlayers()) do
if player.Character then
table.insert(ignoreList, player.Character)
end
end
for _, npc in ipairs(CollectionService:GetTagged("NPC")) do
table.insert(ignoreList, npc)
end
return ignoreList
end
-- Verificar obstáculos
local function checkObstacles(target)
local direction = (target.Position - rootPart.Position).Unit
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = getIgnoreList()
-- Raycast horizontal para obstáculos
local raycastResult = workspace:Raycast(rootPart.Position, direction * RAYCAST_DISTANCE, raycastParams)
-- Raycast hacia arriba para detectar obstáculos que podamos saltar
local upRayStart = rootPart.Position + direction * 2
local upRaycastResult = workspace:Raycast(upRayStart, Vector3.new(0, 4, 0), raycastParams)
if raycastResult then
local hitPart = raycastResult.Instance
if hitPart.CanCollide then
-- Verificar si podemos saltar sobre el obstáculo
local obstacleHeight = (raycastResult.Position.Y - rootPart.Position.Y)
if obstacleHeight > 0 and obstacleHeight < JUMP_HEIGHT_THRESHOLD and not upRaycastResult then
-- Intentar saltar si el obstáculo no es muy alto
if tryJump() then
return false
end
end
return true
end
end
return false
end
-- Función para infligir daño al jugador
local function attackPlayer(player)
local currentTime = tick()
if currentTime - lastDamageTime >= DAMAGE_COOLDOWN then
local humanoidTarget = player.Character:FindFirstChild("Humanoid")
if humanoidTarget and humanoidTarget.Health > 0 then
attackTrack:Play() -- Reproducir animación de ataque
humanoidTarget:TakeDamage(DAMAGE_AMOUNT)
lastDamageTime = currentTime
end
end
end
-- Verificar si el zombie está atascado
local function checkStuck()
local currentPosition = rootPart.Position
if (currentPosition - lastPosition).Magnitude < 1 then
stuckTime = stuckTime + UPDATE_INTERVAL
if stuckTime >= STUCK_THRESHOLD then
return true
end
else
stuckTime = 0
end
lastPosition = currentPosition
return false
end
-- Movimiento directo hacia el jugador
local function moveDirectlyToTarget(target)
local targetPosition = target.Position
humanoid:MoveTo(targetPosition)
end
-- Movimiento con recalculo de camino si está atascado u obstaculizado
local function followPathToTarget(target)
local success, errorMessage = pcall(function()
path:ComputeAsync(rootPart.Position, target.Position)
end)
if success and path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
for _, waypoint in ipairs(waypoints) do
if humanoid.Health <= 0 then
break
end
if waypoint.Action == Enum.PathWaypointAction.Jump then
tryJump()
end
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
end
-- Función principal para el movimiento del zombie
local function updateZombieMovement()
while humanoid.Health > 0 do
local nearestPlayer = findNearestPlayer()
if nearestPlayer and nearestPlayer.Character then
local targetRoot = nearestPlayer.Character:FindFirstChild("HumanoidRootPart")
if targetRoot then
local distance = (targetRoot.Position - rootPart.Position).Magnitude
if distance <= ATTACK_DISTANCE then
humanoid:MoveTo(rootPart.Position) -- Detenerse
attackPlayer(nearestPlayer)
else
local heightDifference = math.abs(targetRoot.Position.Y - rootPart.Position.Y)
local hasObstacles = checkObstacles(targetRoot)
local isStuck = checkStuck()
if isStuck or heightDifference > JUMP_HEIGHT_THRESHOLD or hasObstacles then
followPathToTarget(targetRoot) -- Recalcular si hay obstáculos
else
moveDirectlyToTarget(targetRoot) -- Seguir directamente si no hay obstáculos
end
end
end
end
wait(UPDATE_INTERVAL)
end
humanoid:MoveTo(rootPart.Position)
end
updateZombieMovement()
Heres a video of the Problem: