This is a script that handles all of the pathfinding an AI for a monster
The monster has 2 states: Walking and Chasing
When the monster is walking, there is no player nearby, and they are walking to random waypoints.
When the monster is chasing, they just chase the player.
However, the problem is that the overall AI is sometimes buggy
These are the 2 Main Problems:
-
When the monster finds a player through a wall, they start bugging out
-
When the monster is chasing a player, they sometimes turn around for a second
EXTRA INFO: The blue means the AI is using a MoveTo() to get to the player, the red means the AI is using Pathfinding to get to the player
code:
local npc = script.Parent
local human = npc.Humanoid
local hrp = npc:WaitForChild("HumanoidRootPart")
local PFS = game:GetService("PathfindingService")
local RUNSERVICE = game:GetService("RunService")
local RaycastHitbox = require(game:GetService("ReplicatedStorage").RaycastHitboxV4)
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Blacklist
Params.FilterDescendantsInstances = {npc}
local newHitbox = RaycastHitbox.new(script.Parent.Knife)
newHitbox.RaycastParams = Params
RaycastHitbox.DetectionMode = 1
--task.wait()
local AttackCooldown = .75
local HitDebounce = false
local AttackDebounce = false
local MaxAttackDistance = 3
local SwingSounds = npc.AttackZone.SwingSounds:GetChildren()
local HitSounds = npc.AttackZone.HitSounds:GetChildren()
local destination = workspace.CampingPath:GetChildren()
local IsAttacking = npc.IsAttacking
local foundtarget = nil
local SpawnIdle = human.Animator:LoadAnimation(script.Parent.SpawnIdle)
local SpawnAnim = human.Animator:LoadAnimation(script.Parent.Spawn)
--SpawnIdle:Play()
workspace.Values.EnableCamping.Changed:Wait()
print("CAMPING MONSTER ENABLED")
--SpawnIdle:Stop()
--SpawnAnim:Play()
--SpawnAnim.Stopped:Wait()
--[[
newHitbox.OnHit:Connect(function(hit, humanoid, hum)
if HitDebounce == false then
HitDebounce = true
local RandomHitSound = HitSounds[math.random(1, #HitSounds)]
print(humanoid)
RandomHitSound:Play()
humanoid:TakeDamage(10)
print("hit")
newHitbox:HitStop()
task.wait(AttackCooldown)
HitDebounce = false
end
end)
--]]
local Attack1Anim = human.Animator:LoadAnimation(script.Parent.Attack)
local Attack2Anim = human.Animator:LoadAnimation(script.Parent.Attack2)
local WalkAnim = human.Animator:LoadAnimation(script.Parent.Walk)
local RunAnim = human.Animator:LoadAnimation(script.Parent.Run)
local SpeedTween = game:GetService("TweenService"):Create(human, TweenInfo.new(2.5), {WalkSpeed = 20})
human.Running:Connect(function(speed)
if speed > 15 then
RunAnim:Play()
WalkAnim:Stop()
elseif speed < 10 then
WalkAnim:Play()
RunAnim:Stop()
end
end)
Attack1Anim.Stopped:Connect(function()
newHitbox:HitStop()
end)
Attack2Anim.Stopped:Connect(function()
newHitbox:HitStop()
end)
npc.PrimaryPart:SetNetworkOwner(nil)
local points = {}
for _,part in ipairs(destination) do
table.insert(points,part)
task.wait()
end
local walkDebounce = false
function walkRandomly()
if walkDebounce == false and foundtarget == nil then
walkDebounce = true
print(walkDebounce)
--IsAttacking.Value = false
local point = points[math.random(1,#points)]
--local target = FindTarget()
local path = game:GetService("PathfindingService"):CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
Costs = {
PathFindingModifier = math.huge
}
})
path.Blocked:Connect(function()
path:Destroy()
print("DEBUG: PATH BLOCKED (WALKRANDOMLY)")
walkRandomly()
end)
path:ComputeAsync(hrp.Position, point.Position)
local waypoints = path:GetWaypoints()
for _, waypoint in ipairs(waypoints) do
if foundtarget == nil then
--print("DEBUG: WALKING RANDOMLY TO WAYPOINT")
--IsAttacking.Value = false
human:MoveTo(waypoint.Position)
IsAttacking.Value = false
foundtarget = nil
human.WalkSpeed = 8
path.Blocked:Connect(function()
path:Destroy()
print("DEBUG: PATH BLOCKED (WALKRANDOMLY)")
walkRandomly()
end)
if waypoint.Action == Enum.PathWaypointAction.Jump then
human:ChangeState(Enum.HumanoidStateType.Jumping)
end
end
if IsAttacking.Value or foundtarget == 1 then path:Destroy() break end
human.MoveToFinished:Wait()
--FindTarget()
end
if path.Status == Enum.PathStatus.Success and foundtarget == nil then
print('Success!')
walkDebounce = false
end
end
end
local Swings = 0
local function findTarget()
--print('should run every frame 2')
local players = game:GetService("Players"):GetPlayers()
local nearesttarget
local maxDistance = 25 -- distance
for i,player in pairs(players) do
if player.Character then
local target = player.Character
local distance = (npc.HumanoidRootPart.Position - target:WaitForChild("HumanoidRootPart").Position).Magnitude
if distance < maxDistance then
nearesttarget = target
maxDistance = distance
--print(target)
--print("Found target, nearesttarget = target")
end
task.spawn(function()
local humanoid = target.Humanoid
if distance <= MaxAttackDistance and not AttackDebounce then
--
AttackDebounce = true
if Swings == 0 then
Attack1Anim:Play()
Swings += 1
else
Attack2Anim:Play()
Swings = 0
end
--newHitbox:HitStart()
local RandomSwingSound = SwingSounds[math.random(1, #SwingSounds)]
RandomSwingSound:Play()
if HitDebounce == false then
HitDebounce = true
local RandomHitSound = HitSounds[math.random(1, #HitSounds)]
print(humanoid)
RandomHitSound:Play()
--humanoid:TakeDamage(10)
print("hit")
--newHitbox:HitStop()
task.wait(AttackCooldown)
HitDebounce = false
end
task.wait(AttackCooldown)
AttackDebounce = false--
end
end)
end
end
return nearesttarget
end
local function getPath(destination)
local path = PFS:CreatePath()
path:ComputeAsync(npc.HumanoidRootPart.Position, destination)
return path
end
local function CheckRaycast()
local target = findTarget()
if target then
local ray = Ray.new(npc.HumanoidRootPart.Position,(target.HumanoidRootPart.Position - npc.HumanoidRootPart.Position).unit * 100) -- create the ray
local hit,pos = workspace:FindPartOnRay(ray,npc) -- find parts that the ray hit
if hit then -- if the ray hit a part then
--[[
local distance = (npc.HumanoidRootPart.Position - pos).Magnitude
local p = Instance.new("Part")
p.Anchored = true
p.CanCollide = false
p.Size = Vector3.new(1.1, 1.1, distance)
p.CFrame = CFrame.lookAt(pos, npc.HumanoidRootPart.Position)*CFrame.new(0, 0, -distance/2)
p.Parent = workspace
--]]
if hit:IsDescendantOf(target) then -- if the part is part of the target's character then
--foundtarget = 1
return true -- return true
end
end
--IsAttacking.Value = false
--foundtarget = nil
return false -- if no target was found, return false
end
end
local function pathFindTo(destination)
local path = getPath(destination)
local target = findTarget()
path.Blocked:Connect(function()
path:Destroy()
end)
if target and target.Humanoid.Health > 0 then
for i,waypoint in pairs(path:GetWaypoints()) do
if waypoint.Action == Enum.PathWaypointAction.Jump then
human.Jump = true
end
IsAttacking.Value = true
foundtarget = 1
human.WalkSpeed = 18
if CheckRaycast() == false then
npc.Highlight.FillColor = Color3.fromRGB(255, 0, 0)
--print("Using Pathfinding, Should just be this printing")
human:MoveTo(waypoint.Position)
human.MoveToFinished:Wait()
else
npc.Highlight.FillColor = Color3.fromRGB(0, 0, 255)
human:MoveTo(target.PrimaryPart.Position)
task.wait()
--print("Using Basic MoveTo(), Should just be this printing")
end
--[[
if CheckRaycast() == true then -- you're going to have to make your own checkraycast function
repeat
human:MoveTo(target.PrimaryPart.Position)
task.wait()
print("Using Basic MoveTo(), Should just be this printing")
until CheckRaycast() == false
break
end
--]]
end
end
end
while true do
task.wait()
--print('every frame bruh')
local target = findTarget()
task.spawn(function()
if target then
--print("Attacking"..target.Name)
--print('attackin')
pathFindTo(target:WaitForChild("HumanoidRootPart").Position)
else
--print('walking')
walkRandomly()
end
end)
CheckRaycast()
end