Optimizing AI of enemy unit; problem found

What do you want to achieve?

In my upcoming Scrapyard game, one of the enemies I plan to implement into the game would be based on the Junkbots from mrflimflam’s Junkbot raids of November 2020. As such, these enemies would be always hostile to players until given oil.

“But there are players who ARE Junkbots!” That is true, so I intend to implement a character creator that replaces the player’s avatar with a custom character, and in the future, add an ability that temporarily not only transforms the player’s character into their avatar, but also makes them semi-OP.

While randomly searching in the Studio’s Toolbox for “Junkbot”, I came across this 8-month-old model of a somewhat working enemy Junkbot model. After adding it onto a test baseplate, I checked and refined its scripts.

Changes:
  • made many variables camelCase
  • made constant variables LOUD_SNAKE_CASE
  • replaced instances of if a == true then and if a ~= nil then with if a then
  • replaced instances of if a == nil then with if not a then
  • replaced instances similar to a = a + 1 with a += 1
  • uncommented commented-out lines of code that seemed relevant
  • added new lines for it to cry out aside from “GIVE OIL”
  • many more changes on other scripts

What is the issue?

I did as much modifications as possible, but there is a problem. The enemy AI works fine, but the output constantly keeps writing this error:
attempt to index nil with 'Name’
Even worse, when I kill it during play-testing, it LAGS the testing area very badly.

What solutions have you tried so far?

In the original code, there was this LONG list of conditions for a particular if-then block.

if p:IsA'Part' and not p.Name == bodypartnames and phit and phit.Name ~= bodypartnames and phit:IsA'Part' and rootr:Distance(phit.Position) < 5 then

What I did is that I placed all of these conditions into a table, and then did this:

local c = {
	p:IsA"Part",
	p.Name ~= bodyPartNames,
	pHit,
	pHit.Name ~= bodyPartNames,
	pHit:IsA"Part",
	rootR:Distance(pHit.Position) < 5
}
if c[1] and c[2] and c[3] and c[4] and c[5] and c[6] then
	connection = human.Changed:connect(function()
		human.Jump = true
	end)
else
	human.Jump = false
end
If that does not seem enough, here is the full "AI Chase" script:
-- 22/5/2018: So turns out, Baldi without a humanoid body is too skinny to move --
--DuruTeru
--[[

____________________________________________________________________________________________________________________

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-	
____________________________________________________________________________________________________________________
                      ___       ___                                            
                     (   )     (   )      .-.                                  
    .--.      .--.    | |_      | |_     ( __)  ___ .-.     .--.       .--.    
  /  _  \    /    \  (   __)   (   __)   (''") (   )   \   /    \    /  _  \   
 . .' `. ;  |  .-. ;  | |       | |       | |   |  .-. .  ;  ,-. '  . .' `. ;  
 | '   | |  |  | | |  | | ___   | | ___   | |   | |  | |  | |  | |  | '   | |  
 _\_`.(___) |  |/  |  | |(   )  | |(   )  | |   | |  | |  | |  | |  _\_`.(___) 
(   ). '.   |  ' _.'  | | | |   | | | |   | |   | |  | |  | |  | | (   ). '.   
 | |  `\ |  |  .'.-.  | ' | |   | ' | |   | |   | |  | |  | '  | |  | |  `\ |  
 ; '._,' '  '  `-' /  ' `-' ;   ' `-' ;   | |   | |  | |  '  `-' |  ; '._,' '  
  '.___.'    `.__.'    `.__.     `.__.   (___) (___)(___)  `.__. |   '.___.'   
                                                           ( `-' ;             
                                                            `.__.              
____________________________________________________________________________________________________________________
 
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-	
____________________________________________________________________________________________________________________

--]]	
	
	
SEARCH_DISTANCE = 64 	-- How far a player can be before it detects you

ENEMY_DAMAGE = 5		-- How much damage the enemy inficts towards the player

--[[
	
____________________________________________________________________________________________________________________
 
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-	
____________________________________________________________________________________________________________________

-- OPTIONAL --]]

CAN_WANDER = false
WANDER_X,WANDER_Z = 30, 30
-- 	How many studs the enemy can wander on the x and z axis in studs

--]]


function getHumanoid(model)
	for _, v in pairs(model:GetChildren()) do
		if v:IsA'Humanoid' then
			return v
		end
	end
end


enemy = script.Parent
human = getHumanoid(enemy)
hRoot = enemy.HumanoidRootPart
eSpeed = hRoot.Velocity.magnitude


PFS = game:GetService("PathfindingService")

function GetPlayerNames()
	local players = game:GetService('Players'):GetChildren()
	local name
	for _, v in pairs(players) do
		if v:IsA'Player' then
			name = tostring(v.Name)
		end
	end
	return name
end

spawn(function()
	while wait() do

	end
end)

function GetPlayersBodyParts(t)
	local torso = t
	if torso then
		local figure = torso.Parent
		for _, v in pairs(figure:GetChildren()) do
			if v:IsA'Part' then
				return v.Name
			end
		end
	else
		return "HumanoidRootPart"
	end
end

function GetTorso(part)
	local chars = game.Workspace:GetChildren()
	local torso
	for _, v in pairs(chars) do
		if v:IsA'Model' and v ~= script.Parent and v.Name == GetPlayerNames() then
			local charRoot = v:FindFirstChild'HumanoidRootPart'
			if (charRoot.Position - part).magnitude < SEARCH_DISTANCE then
				torso = charRoot
			end
		end
	end
	return torso
end

for _, enemyPart in pairs(enemy:GetChildren()) do
	if enemyPart:IsA'Part' then
		enemyPart.Touched:connect(function(p)
			if p.Parent.Name == GetPlayerNames() and p.Parent.Name ~= enemy.Name then -- damage
				local victim = p.Parent
				getHumanoid(enemy):TakeDamage(ENEMY_DAMAGE)
			end
		end)
	end
end

-- no touchy
local path
local waypoint
local oldpoints
local isWandering = 0

if CAN_WANDER then
	spawn(function()
		while isWandering == 0 do
			isWandering = 1
			local desgx, desgz = hRoot.Position.x + math.random(-WANDER_X, WANDER_X), hRoot.Position.z + math.random(-WANDER_Z, WANDER_Z)
			human:MoveTo( Vector3.new(desgx, 0, desgz) )
			wait(math.random(4, 6))
			isWandering = 0
		end
	end)
end

while wait() do
	local enemytorso = GetTorso(hRoot.Position)	
	if enemytorso then -- if player detected
		isWandering = 1
		local function checkw(t)
			local ci = 3
			if ci > #t then
				ci = 3
			end
			if not t[ci] and ci < #t then
				repeat
					ci += 1
					wait()
				until t[ci]
				return Vector3.new(1, 0, 0) + t[ci]
			else
				ci = 3
				return t[ci]
			end
		end
		
		path = PFS:FindPathAsync(hRoot.Position, enemytorso.Position)
		waypoint = path:GetWaypoints()
		oldpoints = waypoint
		local connection;
		
		local direct = Vector3.FromNormalId(Enum.NormalId.Front)
		local NCF = hRoot.CFrame * CFrame.new(direct)
		direct = NCF.p.unit
		local rootR = Ray.new(hRoot.Position, direct)
		local pHit, pPos = game.Workspace:FindPartOnRay(rootR, hRoot)
		
		if path and waypoint or checkw(waypoint) then
			if checkw(waypoint) and checkw(waypoint).Action == Enum.PathWaypointAction.Walk then
				human:MoveTo( checkw(waypoint).Position )
				human.Jump = false
			end
			
			if checkw(waypoint) and checkw(waypoint).Action == Enum.PathWaypointAction.Jump then
				human.Jump = true
				connection = human.Changed:connect(function()
					human.Jump = true
				end)
				human:MoveTo( checkw(waypoint).Position )
			else
				human.Jump = false
			end
			
			hRoot.Touched:connect(function(p)
				local bodyPartNames = GetPlayersBodyParts(enemytorso)
				local c = {
					p:IsA"Part",
					p.Name ~= bodyPartNames,
					pHit,
					pHit.Name ~= bodyPartNames,
					pHit:IsA"Part",
					rootR:Distance(pHit.Position) < 5
				}
				if c[1] and c[2] and c[3] and c[4] and c[5] and c[6] then
					connection = human.Changed:connect(function()
						human.Jump = true
					end)
				else
					human.Jump = false
				end
			end)
			
			if connection then
				connection:Disconnect()
			end
			
		else
			for i = 3, #oldpoints do
				human:MoveTo( oldpoints[i].Position )	
			end
		end
	elseif enemytorso and CAN_WANDER then -- if player not detected
		isWandering = 0
		path = nil
		waypoint = nil
		human.MoveToFinished:Wait()
	end
end
-- Edited from @m1sterfufu's "flamingo junkbot" model by TJZ2021playz for game "The Scrapyard"

You could check the original model by @m1sterfufu for reference. If you have any more optimization ideas and suggestions (@m1sterfufu modeled this after supposedly a “Baldi” enemy; this script is 3 years old), please do reply.

1 Like