Works but it kinda spins out in a way when patrolling
and following the player also errors guess i didnt code it good
I forgot one of the things to change from Ai to self, it should work fine here
--@Ai
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local Ai = {}
Ai.__index = Ai
function Ai.new(Model : Model)
local self = setmetatable({}, Ai)
self.Model = Model
self.Connections = {
Waypoints = nil,
NextWaypointIndex = nil,
ReachedConnection = nil,
BlockedConnection = nil
}
self.Path = PathfindingService:CreatePath({
["AgentHeight"] = 7,
["AgentRadius"] = 4,
["AgentCanJump"] = false,
["AgentCanClimb"] = false
})
return self
end
function Ai:GetTarget(MaxSearchDistance : number)
local NearestTarget = nil
local SearchDistance =MaxSearchDistance
for Index, Player in pairs(Players:GetPlayers()) do
local Character = Player.Character
if not Character then return end
local Humanoid = Character:FindFirstChild("Humanoid")
if not Humanoid then return end
if Humanoid.Health < 0 then return end
local Distance = (self.Model.PrimaryPart.Position - Character.PrimaryPart.Position).Magnitude
if SearchDistance > Distance then
NearestTarget = Character
SearchDistance = Distance
end
end
return NearestTarget
end
function Ai:Capture(Target)
local Distance = (self.Model.PrimaryPart.Position - Target.PrimaryPart.Position).Magnitude
if Distance > 3 then
self:FollowPath(Target.PrimaryPart.Position)
else
Target.Humanoid:TakeDamage(100)
end
end
function Ai:FollowPath(Destination : Vector3)
local Success, ErrorMessage = pcall(function()
self.Path:ComputeAsync(self.Model.PrimaryPart.Position, Destination)
end)
if Success and self.Path.Status == Enum.PathStatus.Success then
self.Connections.Waypoints = self.Path:GetWaypoints()
for Index, Waypoint in pairs(self.Connections.Waypoints) do
local Target = self:GetTarget(200)
if Target then
self:Capture(Target)
self.Model.Humanoid.WalkSpeed = 25
break
else
self.Model.Humanoid.WalkSpeed = 10
self.Model.Humanoid:MoveTo(Waypoint.Position)
self.Model.Humanoid.MoveToFinished:Wait()
end
end
else
warn(ErrorMessage)
end
end
function Ai:Patrol()
local WayPoints = Workspace.Waypoints:GetChildren()
local RandomWaypoint = math.random(1, #WayPoints)
self:FollowPath(Workspace.Waypoints[RandomWaypoint].Position)
end
return Ai
What do you mean? It shouldn’t do that because it’s using MoveTo properly.
Okay so it trys to move to Part1, then it gets confused then goes to part2, then part 1 and I think I may just recode it bettter
If this will help here’s the original script that isn’t a module:
--// Game Loaded
repeat
task.wait(2.5)
until game.Loaded
--// Services
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
--// Pathfinding Setup
local Path = PathfindingService:CreatePath({
["AgentHeight"] = 7,
["AgentRadius"] = 4,
["AgentCanJump"] = false,
["AgentCanClimb"] = false
})
--// Enemy Variables
local EnemyCharacter = Workspace.Figure2
local EnemyHumanoid = EnemyCharacter:WaitForChild("Humanoid")
local EnemyPrimaryPart = EnemyCharacter.PrimaryPart
local EnemyAnimator = EnemyHumanoid:WaitForChild("Animator")
--// Initialize Enemy
EnemyPrimaryPart:SetNetworkOwner(nil)
--// Pathfinding Variables
local Waypoints
local NextWaypointIndex
local ReachedConnection
local BlockedConnection
--// Enemy Properties
local WalkSpeed = 10
local SprintSpeed = 25
local MaxSearchDistance = 200
--// Animations
local IdleAnimation = script.IdleAnimation
local WalkAnimation = script.WalkAnimation
local RunAnimation = script.RunAnimation
local CurrentAnimationTrack = nil
local FigureStatus = script.Status
--// Stats
local TimeSurvived = 0
--// Functions
local function PlayAnimation(Animation)
if CurrentAnimationTrack then
CurrentAnimationTrack:Stop()
CurrentAnimationTrack = nil
end
CurrentAnimationTrack = EnemyAnimator:LoadAnimation(Animation)
CurrentAnimationTrack:Play()
end
local function CanSeeTarget(Target)
local EnemyToCharacter = (Target.Head.Position - EnemyCharacter.Head.Position).Unit
local EnemyLook = EnemyCharacter.Head.CFrame.LookVector
local DotProduct = EnemyToCharacter:Dot(EnemyLook)
if DotProduct > 0.5 then
return true
else
return false
end
end
local function Attack(Target)
local Humanoid = Target:FindFirstChild("Humanoid")
if Humanoid then
Humanoid:TakeDamage(100)
end
end
local function FindTarget()
local NearestTarget = nil
local SearchDistance = MaxSearchDistance
for Index, Player in pairs(Players:GetPlayers()) do
local Character = Player.Character
local Humanoid = Character:FindFirstChild("Humanoid")
if Character and Humanoid and Humanoid.Health > 0 then
local Distance = (EnemyPrimaryPart.Position - Character.PrimaryPart.Position).Magnitude
if Distance < SearchDistance and CanSeeTarget(Character) then
NearestTarget = Character
SearchDistance = Distance
end
if Distance < 8 then
Attack(Character)
end
end
end
return NearestTarget
end
local function FollowPath(Destination)
local Success, ErrorMessage = pcall(function()
Path:ComputeAsync(EnemyPrimaryPart.Position, Destination)
end)
if Success and Path.Status == Enum.PathStatus.Success then
Waypoints = Path:GetWaypoints()
if #Waypoints < 2 then
EnemyHumanoid:MoveTo(Destination)
if ReachedConnection then ReachedConnection:Disconnect() end
if BlockedConnection then BlockedConnection:Disconnect() end
return
end
BlockedConnection = Path.Blocked:Connect(function(BlockedWaypointIndex)
if BlockedWaypointIndex >= NextWaypointIndex then
BlockedConnection:Disconnect()
FollowPath(Destination)
end
end)
if not ReachedConnection then
ReachedConnection = EnemyHumanoid.MoveToFinished:Connect(function(Reached)
if Reached and NextWaypointIndex < #Waypoints then
NextWaypointIndex += 1
EnemyHumanoid:MoveTo(Waypoints[NextWaypointIndex].Position)
else
ReachedConnection:Disconnect()
BlockedConnection:Disconnect()
end
end)
end
NextWaypointIndex = 2
EnemyHumanoid:MoveTo(Waypoints[NextWaypointIndex].Position)
FigureStatus.Value = "Run" -- walk or run here
else
--warn(ErrorMessage)
end
end
RunService.Heartbeat:Connect(function()
local Target = FindTarget()
if Target then
FollowPath(Target.PrimaryPart.Position)
else
FigureStatus.Value = "Idle"
end
end)
local LastStatus = ""
FigureStatus.Changed:Connect(function()
if FigureStatus.Value == "Idle" and LastStatus ~= "Idle" then
PlayAnimation(IdleAnimation)
EnemyHumanoid.WalkSpeed = 0
elseif FigureStatus.Value == "Walk" and LastStatus ~= "Walk" then
PlayAnimation(WalkAnimation)
EnemyHumanoid.WalkSpeed = WalkSpeed
elseif FigureStatus.Value == "Run" and LastStatus ~= "Run" then
PlayAnimation(RunAnimation)
EnemyHumanoid.WalkSpeed = SprintSpeed
end
LastStatus = FigureStatus.Value
end)
@SubtotalAnt8185 I fixed the issue of walking randomly:
--// Game Loaded
repeat
task.wait(2.5)
until game.Loaded
--// Services
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
--// Pathfinding Setup
local Path = PathfindingService:CreatePath({
["AgentHeight"] = 7,
["AgentRadius"] = 4,
["AgentCanJump"] = false,
["AgentCanClimb"] = false
})
--// Enemy Variables
local EnemyCharacter = Workspace.Figure
local EnemyHumanoid = EnemyCharacter:WaitForChild("Humanoid")
local EnemyPrimaryPart = EnemyCharacter.PrimaryPart
local EnemyAnimator = EnemyHumanoid:WaitForChild("Animator")
--// Initialize Enemy
EnemyPrimaryPart:SetNetworkOwner(nil)
--// Pathfinding Variables
local Waypoints
local NextWaypointIndex
local ReachedConnection
local BlockedConnection
local CurrentTarget = nil
local PathfindingActive = false
--// Enemy Properties
local WalkSpeed = 10
local SprintSpeed = 25
local MaxSearchDistance = 200
--// Animations
local IdleAnimation = script.IdleAnimation
local WalkAnimation = script.WalkAnimation
local RunAnimation = script.RunAnimation
local CurrentAnimationTrack = nil
local FigureStatus = script.Status
--// Patrol Variables (New/Adjusted)
local PatrolPoints = {
workspace.PatrolPoint1.Position,
workspace.PatrolPoint2.Position,
workspace.PatrolPoint3.Position
}
local CurrentPatrolIndex = 1
local IsPatrolling = false
local PatrolWaitTime = 2
--// Functions
local function PlayAnimation(Animation)
if CurrentAnimationTrack then
CurrentAnimationTrack:Stop()
CurrentAnimationTrack = nil
end
CurrentAnimationTrack = EnemyAnimator:LoadAnimation(Animation)
CurrentAnimationTrack:Play()
end
local function CanSeeTarget(Target)
local EnemyToCharacter = (Target.Head.Position - EnemyCharacter.Head.Position).Unit
local EnemyLook = EnemyCharacter.Head.CFrame.LookVector
local DotProduct = EnemyToCharacter:Dot(EnemyLook)
if DotProduct > 0.5 then
return true
else
return false
end
end
local function Attack(Target)
local Humanoid = Target:FindFirstChild("Humanoid")
if Humanoid then
Humanoid:TakeDamage(100)
end
end
local function FindTarget()
local NearestTarget = nil
local SearchDistance = MaxSearchDistance
for Index, Player in pairs(Players:GetPlayers()) do
local Character = Player.Character
local Humanoid = Character:FindFirstChild("Humanoid")
if Character and Humanoid and Humanoid.Health > 0 then
local Distance = (EnemyPrimaryPart.Position - Character.PrimaryPart.Position).Magnitude
if Distance < SearchDistance and CanSeeTarget(Character) then
NearestTarget = Character
SearchDistance = Distance
end
end
end
return NearestTarget
end
local function StopPathfinding()
if ReachedConnection then
ReachedConnection:Disconnect()
ReachedConnection = nil
end
if BlockedConnection then
BlockedConnection:Disconnect()
BlockedConnection = nil
end
PathfindingActive = false
EnemyHumanoid:MoveTo(EnemyPrimaryPart.Position)
end
local function FollowPath(Destination)
if PathfindingActive and CurrentTarget and (CurrentTarget.PrimaryPart.Position - Destination).Magnitude < 5 then
-- Already pathfinding to essentially the same location, no need to recompute
return
end
StopPathfinding()
PathfindingActive = true
CurrentTarget = {PrimaryPart = {Position = Destination}}
local Success, ErrorMessage = pcall(function()
Path:ComputeAsync(EnemyPrimaryPart.Position, Destination)
end)
if Success and Path.Status == Enum.PathStatus.Success then
Waypoints = Path:GetWaypoints()
workspace.Folder:ClearAllChildren()
if #Waypoints < 2 then
EnemyHumanoid:MoveTo(Destination)
StopPathfinding()
return
end
for Index, Point in pairs(Waypoints) do
local Part = Instance.new("Part")
Part.Parent = workspace.Folder
Part.Position = Point.Position
Part.Name = "Point"
Part.BrickColor = BrickColor.new("Persimmon")
Part.Anchored = true
Part.Size = Vector3.new(1, 1, 1)
Part.Material = Enum.Material.Neon
Part.CanCollide = false
end
BlockedConnection = Path.Blocked:Connect(function(BlockedWaypointIndex)
if BlockedWaypointIndex >= NextWaypointIndex then
StopPathfinding()
FollowPath(Destination)
end
end)
ReachedConnection = EnemyHumanoid.MoveToFinished:Connect(function(Reached)
if Reached and NextWaypointIndex < #Waypoints then
NextWaypointIndex += 1
EnemyHumanoid:MoveTo(Waypoints[NextWaypointIndex].Position)
else
StopPathfinding()
end
end)
NextWaypointIndex = 2
EnemyHumanoid:MoveTo(Waypoints[NextWaypointIndex].Position)
else
warn("Path computation failed:", ErrorMessage)
StopPathfinding()
end
end
local function StartPatrol()
if IsPatrolling then return end
IsPatrolling = true
FigureStatus.Value = "Walk"
task.spawn(function()
while IsPatrolling do
local Destination = PatrolPoints[CurrentPatrolIndex]
if FigureStatus.Value == "Idle" then
FigureStatus.Value = "Walk"
task.wait()
end
FollowPath(Destination)
repeat
task.wait(0.5)
until (EnemyPrimaryPart.Position - Destination).Magnitude < 3 or FindTarget() ~= nil or not PathfindingActive
if FindTarget() ~= nil then
IsPatrolling = false
break
elseif not PathfindingActive then
FigureStatus.Value = "Idle"
task.wait(1)
else
FigureStatus.Value = "Idle"
task.wait(PatrolWaitTime)
end
if IsPatrolling and FindTarget() == nil then
CurrentPatrolIndex = CurrentPatrolIndex % #PatrolPoints + 1
if CurrentPatrolIndex == 0 then CurrentPatrolIndex = 1 end
else
break
end
end
IsPatrolling = false
FigureStatus.Value = "Idle"
end)
end
RunService.Heartbeat:Connect(function()
local Target = FindTarget()
if Target then
if IsPatrolling then
IsPatrolling = false
StopPathfinding()
end
local DistanceToTarget = (EnemyPrimaryPart.Position - Target.PrimaryPart.Position).Magnitude
if DistanceToTarget < 8 then
Attack(Target)
FigureStatus.Value = "Idle"
return
end
if not CurrentTarget or (Target.PrimaryPart.Position - CurrentTarget.PrimaryPart.Position).Magnitude > 5 then
FollowPath(Target.PrimaryPart.Position)
end
FigureStatus.Value = "Run"
else
if PathfindingActive and not IsPatrolling then
StopPathfinding()
FigureStatus.Value = "Idle"
end
if not IsPatrolling then
print("No target. Starting patrol.")
StartPatrol()
end
end
end)
local LastStatus = ""
FigureStatus.Changed:Connect(function()
if FigureStatus.Value == "Idle" and LastStatus ~= "Idle" then
PlayAnimation(IdleAnimation)
EnemyHumanoid.WalkSpeed = 0
elseif FigureStatus.Value == "Walk" and LastStatus ~= "Walk" then
PlayAnimation(WalkAnimation)
EnemyHumanoid.WalkSpeed = WalkSpeed
elseif FigureStatus.Value == "Run" and LastStatus ~= "Run" then
PlayAnimation(RunAnimation)
EnemyHumanoid.WalkSpeed = SprintSpeed
end
LastStatus = FigureStatus.Value
end)
I know it’s kind of buggy, but it’s a first draft can I have your opinion on the code?
I think that this is pretty good.
This was a good inclusion.
Yea it took me a while but I think I did good on it.
Hey @SubtotalAnt8185 I have a question how can I make a dodge system that the NPC reacts to? Like if the NPCs running at you if you move to the right or left or doge the NPC. The NPC will slide and play an animation?
It’s pretty complicated, but you can get the velocity of the player’s character to the left or right relative to the NPC.
local CharacterPrimaryPart = Character.PrimaryPart
local CharacterVelocity = CharacterPrimaryPart.AssemblyLinearVelocity
local EnemyVelocity = EnemyPrimaryPart.AssemblyLinearVelocity
local Difference = CharacterVelocity - EnemyVelocity
local RelativeVelocity = EnemyPrimaryPart.CFrame.RightVector:Dot(Difference)
if RelativeVelocity > 3 then
--dodge left/right
elseif RelativeVelocity < -3 then
--dodge the other direction
end
Where should I implement this?
I’m not exactly sure, but you would put it in some sort of loop when the enemy is relatively close to the target (the character is the target).
Uhhh okay I’ll try to implement that.
Okay I have the code but how would I make the slide part?
if FigureStatus.Value == "Run" then
local PlayerHumanoid = Target:FindFirstChild("Humanoid")
local PlayerRootPart = Target:FindFirstChild("HumanoidRootPart")
if PlayerHumanoid and PlayerRootPart and PlayerHumanoid.MoveDirection.Magnitude > 0.1 then
local AI_to_Player_Vector = (PlayerRootPart.Position - EnemyPrimaryPart.Position).Unit
local Player_Move_Direction = PlayerHumanoid.MoveDirection.Unit
local Enemy_Forward_Vector = EnemyPrimaryPart.CFrame.LookVector
local PlayerLateralDot = AI_to_Player_Vector:Dot(Player_Move_Direction)
local AIStraightDot = Enemy_Forward_Vector:Dot(AI_to_Player_Vector)
local DodgeDistanceThreshold = 15
if math.abs(PlayerLateralDot) < 0.3 and PlayerRootPart.Velocity.Magnitude > 5 and
AIStraightDot > 0.8 and DistanceToTarget < DodgeDistanceThreshold then
print("SLIDE")
end
end
end
You can do a few things:
- Increase the WalkSpeed temporarily and make the NPC move really quickly to the left or right (easiest and most efficient)
- Use something like a LinearVelocity mover to move the NPC left or right. This will require an attachment, but you could put one in every NPC and just enable/disable the left/right ones as needed.
- Make the NPC teleport to the left/right and make the animation do the inverse, so the work is put onto the animation to animate the left/right movement, not the physics.
I recommend doing the first or last ones.
For teleporting the NPC:
EnemyPrimaryPart.CFrame *= CFrame.new(5, 0, 0)
...
EnemyPrimaryPart.CFrame *= CFrame.new(-5, 0, 0)
You could alternatively tween it, but it won’t move forward.
For increasing the WalkSpeed:
EnemyHumanoid.WalkSpeed = 50 --change this
EnemyHumanoid:Move(EnemyPrimaryPart.CFrame.RightVector)
task.wait(.5)
EnemyHumanoid.WalkSpeed = originalWalkSpeed --change this
...
--other direction is the same except it's negative:
EnemyHumanoid:Move(-EnemyPrimaryPart.CFrame.RightVector)
Thank you I’ll work on this later…