Help With AI Framework

  1. 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

  1. 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.

image

  1. 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
1 Like

Did you ever figure it out? I am having the same issue.