How to convert large function into state machine

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I wanna create a state machine because ive heard its more preformant
  2. What is the issue? Include screenshots / videos if possible!
    I dont know how to start converting my current code into a state machine or if i should even
  3. What solutions have you tried so far? Did you look for solutions on the Creator Hub?
    I’ve watched videos on state machines and know what they are but I dont know how to start.
    After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

This is my current enemy ai script

function SmartEnemyAI3:FollowCharacter(dt)
	
	local Ragdoll = self.character:GetAttribute("Ragdolled")
	
	if not Ragdoll then
		self.HumanoidRootPart:SetNetworkOwner(nil)
	end

	local nearestEnemy, Enemydistance, Enemydirection = self:FindNearestCharacter()

	local head = self.character:FindFirstChild("Head")

	-- Stop NPC if ragdolled or dead
	if Ragdoll or self.Humanoid.Health <= 0 then
		self.Humanoid.WalkSpeed = 0
		self.Humanoid:Move(Vector3.new(0,0,0))
		return
	end
	
	local function Dash()
		if not self.Dashing then
			self.Dashing = true
			self.DashingCooldown = true
				NPCBLOCK:Fire(self.character, false)
				AnimateEvent:FireAllClients(self.character, self.Broll, true, true)
				NPCROLL:Fire(self.character)
				local lv = Instance.new("LinearVelocity")
				lv.Parent = self.character.HumanoidRootPart
				lv.Attachment0 = self.character.HumanoidRootPart.RootAttachment
				lv.ForceLimitMode = Enum.ForceLimitMode.PerAxis
				lv.RelativeTo = Enum.ActuatorRelativeTo.World
				lv.VectorVelocity = self.character.HumanoidRootPart.CFrame.LookVector * -40
				lv.MaxAxesForce = Vector3.new(25000,25000,25000)
				task.wait(0.2)
				lv:Destroy()
			self.Dashing = false
			task.wait(3)
			self.DashingCooldown = false
		end
	end
	
	local function Block()
		if not self.isBlocking then
			self.isBlocking = true
			if self.WillParry then
				NPCBLOCK:Fire(self.character, true)
			else
				task.wait(0.1)
				if not SanityModule:StunCheck(self.character) and not SanityModule:AttackStunCheck(self.character) and self.Humanoid.Health > 0 and not Ragdoll and self.equipped and not self.character:GetAttribute("IsBlocking") and not self.Dashing then
					NPCBLOCK:Fire(self.character, true)
				end
			end
			self.isBlocking = false
		end
	end

	if head ~= nil and nearestEnemy ~= nil and nearestEnemy.Humanoid.Health > 0 then
		local vector1 = (nearestEnemy.Head.Position - head.Position).Unit
		vector1 = Vector3.new(vector1.X, 0, vector1.Z).Unit
		local elook = self.character.HumanoidRootPart.CFrame.LookVector
		elook = Vector3.new(elook.X, 0, elook.Z).Unit
		local dot = elook:Dot(vector1)
		
		local diff = nearestEnemy.HumanoidRootPart.Position - self.HumanoidRootPart.Position
		local angle = math.atan2(-diff.X, -diff.Z)
		self.alignorientation.CFrame = CFrame.Angles(0, angle, 0)
		
		if nearestEnemy:GetAttribute("Ragdolled") then
			self.DistanceForAttack = WUP[self.Weapon.Name].WeaponStandingRange + 3
		else
			self.DistanceForAttack = WUP[self.Weapon.Name].WeaponStandingRange
		end
		
		if nearestEnemy then
			
			if self.config.Strafe == true then
				self.Counter += dt
				self.timer -= dt

				if self.Counter >= self.Limit then
					self.linearVelocity.VectorVelocity = Vector3.new(math.random(-7.5,7.5), 0, 0)
					self.Counter = 0
				end
			end


			local Hrp = nearestEnemy.HumanoidRootPart
			
			if Enemydistance <= (self.DistanceForAttack - (self.DistanceForAttack / 4)) and not SanityModule:StunCheck(self.character) and not self.Dashing and SanityModule:AttackStunCheck(self.character) then
				self.Humanoid.WalkSpeed = 15
				self.Humanoid:Move(-Enemydirection)
			elseif Enemydistance <= self.DistanceForAttack + 3 and not SanityModule:StunCheck(self.character) and not self.Dashing and (SanityModule:AttackStunCheck(nearestEnemy) and not SanityModule:StunCheck(nearestEnemy)) then
				self.alignorientation.Enabled = true
				self.linearVelocity.Enabled = true

				if self.WillBlock == nil then
					if math.random() < self.config.blockChance then
						self.WillBlock = true
						if math.random() < self.config.parryChance then
							self.WillParry = true
						else
							self.WillParry = false
						end
					else
						if math.random() < self.config.RollChance then
							self.WillRoll = true
						else
							self.WillRoll = false
						end
						self.WillBlock = false
					end
				end
				
				if not SanityModule:StunCheck(self.character) and not SanityModule:AttackStunCheck(self.character) and self.Humanoid.Health > 0 and not Ragdoll and self.equipped and self.WillBlock and not self.WillRoll and not self.Dashing then
					Block()
				elseif not SanityModule:StunCheck(self.character) and not SanityModule:AttackStunCheck(self.character) and self.Humanoid.Health > 0 and not Ragdoll and self.equipped and not self.WillBlock and self.WillRoll and not self.character:GetAttribute("RollSafeCD") and not self.Dashing and not self.DashingCooldown then
					Dash()
				end
					
				self.Humanoid.WalkSpeed = 5
				self.Humanoid:Move(Enemydirection)
			elseif Enemydistance <= self.DistanceForAttack and not SanityModule:StunCheck(self.character) and not self.Dashing then
				
				self.alignorientation.Enabled = true
				if self.config.Strafe == true then
					self.linearVelocity.Enabled = true
				end
				
				local Ragdoll = self.character:GetAttribute("Ragdolled")
				local ComboCD  = self.character:GetAttribute("ComboCD")
				local Blocking = self.character:GetAttribute("IsBlocking")

				if SanityModule:StunCheck(nearestEnemy) or self.AttackWindup <= 0 then
					
					self.WillBlock = nil
					if self.WillHeavy == nil then
						if math.random() > self.config.heavyChance then
							self.WillHeavy = false
						else
							self.WillHeavy = true
						end
					end
					
					if not SanityModule:StunCheck(self.character) and not SanityModule:AttackStunCheck(self.character) and not SanityModule:ComboLag(self.character) and self.Humanoid.Health > 0 and not Ragdoll and self.equipped and not Blocking and not self.WillHeavy and not self.Dashing then
						if not nearestEnemy:GetAttribute("Ragdolled") then
							NPCATTACK:Fire(self.character, 1)
						end
					elseif not SanityModule:StunCheck(self.character) and not SanityModule:AttackStunCheck(self.character) and self.Humanoid.Health > 0 and not Ragdoll and self.equipped and not Blocking and self.WillHeavy and not self.Dashing then
						if not nearestEnemy:GetAttribute("Ragdolled") then
							NPCATTACK:Fire(self.character, 2)
						end
					else
						NPCBLOCK:Fire(self.character, false)
					end
					self.WillHeavy = nil
				else
					if self.AttackWindup > 0 then
						self.AttackWindup -= dt
					end
				end
				
				self.Humanoid.WalkSpeed = 0
				self.Humanoid:Move(Enemydirection)
			elseif Enemydistance <= self.DistanceForAttack + 10 and not SanityModule:StunCheck(self.character) and not self.Dashing then
				self.Humanoid.WalkSpeed = 15
				self.Humanoid:Move(Enemydirection)
				NPCBLOCK:Fire(self.character, false)
				if self.AttackWindup <= 0 then
					self.AttackWindup = math.random(1, (self.config.attackDelay * 10)) / 10
				end
			elseif Enemydistance <= self.DistanceForAttack + 40 and not SanityModule:StunCheck(self.character) and not self.Dashing then
				self.Humanoid.WalkSpeed = 20
				self.Humanoid:Move(Enemydirection)
				NPCBLOCK:Fire(self.character, false)
			else
				self.alignorientation.Enabled = false
				self.linearVelocity.Enabled = false
				self.Humanoid:Move(Vector3.new(0,0,0))
			end
		else
			self.alignorientation.Enabled = false
			self.linearVelocity.Enabled = false
		end
	end
end

Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

So to start off, a state machine will not be more performant probably (haven’t read all of your code). State machines are usually for organizing the different pieces of state so that they are more self contained and less coupled making it easier to build larger systems or just overall easier to reason about.

As for how to do so, you basically need to pick what “states” your npc will have. This will be a set of actions that they take that are organized in some logical group. It can be as simple as moving, or something more complex like a patrol state. The exact level of complexity the states should have depends on your goals and the complexity of your AI (you can nest state machines if needed). Then you need to pick a way to transition between states.

The way I normally do it is I create state components that I add to a state machine that are named (I use the name to transition to states) and each state has an :Enter() :Leave() and an :Update() and the state machine handles the calling of those functions in each state and manages what states are allowed to transition to what other states.

So for you I recommend getting a piece of paper and writing all the self contained states your AI can be in and then group then into logical pieces of code where each gets to manage the NPC when it’s their turn. From there it’s just writing out the parts of code that will run in each state and the way to switch which code is running. For now I would pretend you don’t already have code and just write out what states you need, then copy pieces of your function in as you go.

Thanks i managed to complete the state machine i wanted with the completed separate states

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.