Hello, I’m LuaGirlDeveloper and I’m trying to turn my NPC AI system into a module so I can just call the module and setup the NPC, so I don’t have to repeat my scripts. I’ve tried making it into a Module but I’m not the best of doing self. (The AI isn’t optimized.)
I don’t want the script I just need ideas and how should I complete this.
--@Ai
repeat
task.wait(0.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)