- What do you want to achieve? Keep it simple and clear!
I just want to know the best way to go about making an AI. I see a lot of AI’s with tons of module scripts and such which seems a little unnecessary
- What is the issue? Include screenshots / videos if possible!
this is my current ai it dosen’t look good and the pathfinding is terrible, i currently have one module script to handle vision and hearing and then one script to handle everything else.
- What solutions have you tried so far? Did you look for solutions on the Developer Hub?
ive tried some youtube tutorials and some posts on the dev forum none seem to be what im looking for as well as some free models but they aren’t too good.
– StateController
local PathFindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local CURRENTSTATE = script.STATE
local NPC = script.Parent
local KillAnim = script.Anims.KillAnim
local Humanoid = NPC:WaitForChild("Humanoid")
local HumanoidRootPart = NPC.PrimaryPart
local Animator = Humanoid:WaitForChild("Animator")
local Head = NPC:WaitForChild("Head")
local UpperTorso = NPC:WaitForChild("UpperTorso")
local WanderPoints = workspace.WanderPoints:GetChildren()
-- MODULES
local VisionHandler = require(script.VisionHandler)
-- SOUNDS
local KillSound = HumanoidRootPart.KillSound
local BreakableWalls = workspace.BreakableWalls:GetChildren()
local AttackRange = 5
local SearchTime = 10
local WanderTime = 20
local TARGET = nil
local isWandering = false
local isChasing = false
local function PathFinding(Destination)
local Path = PathFindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = false,
AgentCanClimb = false,
AgentWalkSpeed = Humanoid.WalkSpeed,
Costs = {
BreakWall = 1
}
})
Path:ComputeAsync(HumanoidRootPart.Position, Destination)
local Waypoints = Path:GetWaypoints()
if Path.Status == Enum.PathStatus.Success then
for _, Waypoint in ipairs(Waypoints) do
Humanoid:MoveTo(Waypoint.Position)
Humanoid.MoveToFinished:Wait()
end
else
warn("Pathfinding failed")
end
end
local function Wander()
if isWandering then return end
isWandering = true
Humanoid.WalkSpeed = 8
local RandomIndex = math.random(1, #WanderPoints)
local RandomPoint = WanderPoints[RandomIndex]
PathFinding(RandomPoint.Position)
isWandering = false
end
local function Kill(Target)
if TARGET == Target then return end
TARGET = Target
local targetHumanoidRootPart = Target:FindFirstChild("HumanoidRootPart")
targetHumanoidRootPart.CFrame = HumanoidRootPart.CFrame * CFrame.Angles(0, math.rad(180), 0) * CFrame.new(0, .5, 2.5)
Target:FindFirstChild("UpperTorso").Anchored = true
local TargetAnimator = Target:FindFirstChild("Humanoid").Animator
local Animation = TargetAnimator:LoadAnimation(Target:FindFirstChild("Struggle"))
local killAnimationTrack = Animator:LoadAnimation(KillAnim)
killAnimationTrack:Play()
wait(0.3)
Animation:Play()
KillSound:Play()
wait(1.1)
local targetHumanoid = Target:FindFirstChild("Humanoid")
if targetHumanoid then
Target:FindFirstChild("UpperTorso").Anchored = false
Target:FindFirstChild("Head").Neck:Destroy()
targetHumanoid:TakeDamage(101)
end
end
local function BreakWallFunc(Wall)
local attachment0Pos = Wall.PrimaryPart.Attachment0.WorldPosition
local attachment1Pos = Wall.PrimaryPart.Attachment1.WorldPosition
local targetPos = (HumanoidRootPart.Position - attachment0Pos).Magnitude < (HumanoidRootPart.Position - attachment1Pos).Magnitude and attachment0Pos or attachment1Pos
local EndPos = targetPos == attachment0Pos and attachment1Pos or attachment0Pos
Humanoid:MoveTo(targetPos)
Humanoid.MoveToFinished:Wait()
Humanoid.WalkSpeed = 8
wait(1)
Humanoid:MoveTo(EndPos)
wait(0.2)
for _, part in pairs(Wall:GetChildren()) do
if part:IsA("Part") then
if part.Name == "MAIN" then
part.Transparency = 1
part.CollisionGroup = "WallParts"
else
part.Anchored = false
part.CollisionGroup = "WallParts"
end
elseif part.Name == "PathFindingLink" then
part:Destroy()
end
end
local Explode = Instance.new("Explosion")
Explode.BlastRadius = 2.2
Explode.BlastPressure = 600000
Explode.Position = Wall.MAIN.Position
Explode.DestroyJointRadiusPercent = 0
Explode.Visible = false
Explode.Parent = workspace
Humanoid.MoveToFinished:Wait()
end
local function PathChase(Target)
if isChasing then return end
isChasing = true
Humanoid.WalkSpeed = 8
CURRENTSTATE.Value = "Chase"
while CURRENTSTATE.Value == "Chase" do
local Path = PathFindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = false,
AgentCanClimb = false,
AgentWalkSpeed = Humanoid.WalkSpeed,
--WayPointSpacing = 0.1,
Costs = {
BreakWall = 1,
BreakWall1 = 1
}
})
Path:ComputeAsync(HumanoidRootPart.Position, Target.PrimaryPart.Position)
local Waypoints = Path:GetWaypoints()
if Path.Status == Enum.PathStatus.Success then
Humanoid.WalkSpeed = 8
for _, Waypoint in ipairs(Waypoints) do
Humanoid:MoveTo(Waypoint.Position)
if Waypoint.Label then
local wall = workspace.BreakableWalls:FindFirstChild(Waypoint.Label)
if wall then
BreakWallFunc(wall)
break
end
end
--Humanoid.MoveToFinished:Wait()
LASTKNOWNPOSITION = Target.PrimaryPart.Position
if (Target.PrimaryPart.Position - HumanoidRootPart.Position).Magnitude <= 6 then
Kill(Target)
CURRENTSTATE.Value = "Wander"
isChasing = false
break
end
end
else
warn("Pathfinding failed")
end
end
isChasing = false
return LASTKNOWNPOSITION
end
RunService.Stepped:Connect(function()
if CURRENTSTATE.Value == "Chase" then return end
local Target = VisionHandler.Run()
if Target then
CURRENTSTATE.Value = "Chase"
local chase = PathChase(Target)
if chase then
PathFinding(LASTKNOWNPOSITION)
else
Wander()
end
else
Wander()
end
wait(0.01)
end)
– VisionHandler
local PathFindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local NPC = script.Parent.Parent
local Humanoid = NPC:WaitForChild("Humanoid")
local HumanoidRootPart = NPC.PrimaryPart
local Head = NPC:WaitForChild("Head")
local UpperTorso = NPC:WaitForChild("UpperTorso")
local WanderPoints = workspace.WanderPoints:GetChildren()
local HearingRange = 20
local ViewRange = 80
local FOV = 80
local VisionHandler = {}
function VisionHandler.GetCharacters()
local Characters = {}
local players = Players:GetPlayers()
for _, player in ipairs(players) do
if player.Character then
table.insert(Characters, player.Character)
else
player.CharacterAdded:Wait()
table.insert(Characters, player.Character)
end
end
return Characters
end
function VisionHandler.RayCast(npc, start, finish)
local parameters = RaycastParams.new()
parameters.FilterDescendantsInstances = {npc, unpack(VisionHandler.GetCharacters())}
parameters.FilterType = Enum.RaycastFilterType.Exclude
local raycast = workspace:Raycast(start, (finish - start), parameters)
return raycast ~= nil
end
function VisionHandler.Check()
local Detected = {}
local characters = VisionHandler.GetCharacters()
for _, character in ipairs(characters) do
local NpcToObject = (character.PrimaryPart.Position - Head.Position).Unit
local npcLookVector = Head.CFrame.LookVector
local dotProduct = NpcToObject:Dot(npcLookVector)
local Angle = math.deg(math.acos(dotProduct))
local distance = (Head.Position - character.PrimaryPart.Position).Magnitude
if distance <= HearingRange then
table.insert(Detected, {character = character, state = "HEARD"})
continue
end
if Angle > FOV or distance > ViewRange or VisionHandler.RayCast(NPC, Head.Position, character.PrimaryPart.Position) then
continue
else
table.insert(Detected, {character = character, state = "SEEN"})
continue
end
end
return #Detected > 0 and Detected or nil
end
function VisionHandler.GetClosestTarget(detected)
local closestTarget = nil
local closestDistance = math.huge
for _, data in ipairs(detected) do
local character = data.character
local distance = (NPC.PrimaryPart.Position - character.PrimaryPart.Position).Magnitude
if distance < closestDistance then
closestDistance = distance
closestTarget = character
end
end
return closestTarget
end
function VisionHandler.Run()
local Sight = VisionHandler.Check()
if Sight then
return VisionHandler.GetClosestTarget(Sight)
else
return nil
end
end
return VisionHandler