Where should I look to learn how to make a AI?

I’m aware there are many threads on the topic of creating AIs, but many of them seem to either be answered with “watch this youtube tutorial and learn nothing!!!” or “use my pre-made script and learn nothing!!!”.
My question is simply: what Roblox documentation/devforum tutorials would aid me in creating a wandering AI that attacks players upon seeing them?

1 Like

The problem with AI is that it really varies a lot. What do you mean by wanders? Do you want it to go through prescheduled routes? Do you want the AI to merely pick a random point on the map and pathfind to it? What do you mean by seeing the player? Do you mean when the player is near or do you mean when the player enters a vision cone or something? When does an AI quit attacking? Does the AI ever get distracted by other players?

The best way to make the AI you want is to first break it down into exactly what behaviors you want. Right now your idea is a bit vague in terms of programming since a different answer to above questions can actually affect quite a bit on how exactly you plan to program it.

Once you have a way to structure all of the actions or senses your AI has you need to pick a way to string them together. Do you want to just make a simple statemachine? Do you want the AI to weigh it’s options?

Really why this will be hard is it’s actually a lot of problems and design choices rolled up into one.

I don’t know any tutorials about this, but we can start by creating a better definition of your AI.

Let’s let “wander” mean it will pick a random point to go to, walk there, then repeat. For this example we will be assuming the AI has no obstacles. Now that we have a more concrete definition converting this part to code will be rather easy.

local wanderTarget = Vector3.new()
local function updateWander()
	if (wanderTarget - char.HumanoidRootPart.Position).Magnitude < 10 then
		wanderTarget = Vector3.new(math.random(-200, 200), 0, math.random(-200, 200))
	end
end

That’s pretty simple. Just pick a location wait until it reaches it and pick a new one. This is meant to be ran in a while loop

while true do
	updateWander()
	char.Humanoid:MoveTo(wanderTarget)
	task.wait(0.25)
end

Now for the next part. Instead of attacking the player I’m just going to make it chase the player when they are within 30 studs. This is purely for the sake of simplicity.

Alright… so we will need to constantly check to see if a player is near. To do that we will need an update loop. And in that update loop we will need to check the distance between the AI and all active players. I’m not going to explain this part in detail because it’s kind of long, but it loops through all of the players to find the nearest one and returns their character if it’s within the chase distance. Otherwise it return nil.

getNearestCharacter()
local function getNearestCharacter()
	local nearestCharacter = game.Players:GetPlayers()[1]
	local dist = math.huge --This means that the loop will override it on the first go so we get the real distance without having to add extra code here
	for _, player in pairs(game.Players:GetPlayers()) do
		if player.Character and player.Character.Parent ~= nil and player.Character:FindFirstChild("HumanoidRootPart") then --Make sure everything we need is actually in the player
			local curDist = (player.Character.HumanoidRootPart.Position - char.HumanoidRootPart.Position).Magnitude
			if curDist < dist then
				dist = curDist
				nearestCharacter = player.Character
			end
		end
	end
	
	if dist < 30 then
		return nearestCharacter
	end
end

So we can use that getNearestCharacter function in our sensor loop. If it returns a character, we don’t want to wander, we want to just move towards the character. If it doesn’t return anything then we just want to wander.

We can change our while loop to this now to account for the new functionality

while true do
	local chase = getNearestCharacter()
	if chase then
		char.Humanoid:MoveTo(chase.HumanoidRootPart.Position)
	else --Wander if not being chased
		updateWander()
		char.Humanoid:MoveTo(wanderTarget)
	end
	task.wait(0.25)
end

And that’s how I made a very basic AI. As you make more complicated AI you may need to also consider using techniques to make the code easier to manage and switch branches/behaviors. But moral of the story is you need to break it down into it’s parts and solve those, then put them together.

Instead of checking distance you can write code there that checks for line of sight, or something else. Instead of moving straight you can call pathfinding functions. Some of those changes will require changes or updates to the structure of the AI though depending on how they are meant to be integrated. A lot of the tutorials you likely encountered probably focused more on the architecture of the AI meant to make it easily scaleable or something. So looking at how other AI is designed is a good idea. This one can be a bit tricky to add onto since I didn’t bother writing a state machine or any other thing that might be easier to interact with for your code. If I was actually writing one for a game of mine I would likely structure it different to make it easier to add more functionality, but the underlining concept would be similar. Hopefully this gave you a slightly better understanding.

Full code
local char = game.Workspace:WaitForChild("char") --my character that the AI controls

local wanderTarget = Vector3.new()
local function updateWander()
	if (wanderTarget - char.HumanoidRootPart.Position).Magnitude < 10 then
		wanderTarget = Vector3.new(math.random(-200, 200), 0, math.random(-200, 200))
	end
end

local function getNearestCharacter()
	local nearestCharacter = game.Players:GetPlayers()[1]
	local dist = math.huge --This means that the loop will override it on the first go so we get the real distance without having to add extra code here
	for _, player in pairs(game.Players:GetPlayers()) do
		if player.Character and player.Character.Parent ~= nil and player.Character:FindFirstChild("HumanoidRootPart") then --Make sure everything we need is actually in the player
			local curDist = (player.Character.HumanoidRootPart.Position - char.HumanoidRootPart.Position).Magnitude
			if curDist < dist then
				dist = curDist
				nearestCharacter = player.Character
			end
		end
	end
	
	if dist < 30 then
		return nearestCharacter
	end
end

while true do
	local chase = getNearestCharacter()
	if chase then
		char.Humanoid:MoveTo(chase.HumanoidRootPart.Position)
	else
		updateWander()
		char.Humanoid:MoveTo(wanderTarget)
	end
	task.wait(0.25)
end
1 Like

I have one question, what would you recommend using as the local char for the AI? I’ve attempted to simply put the script into a part, but I’ve seen that doesn’t do anything.
Anyways, this is all quite interesting. Thank you for taking your time to provide this example

I’m not quite sure what you mean by your question. If you are asking how my architecture is, I have the script in serverScript service and I have a copy of my charcter in workspace named “char” which is the character the script controls. For this script though it might make more sense to put the script inside the character and change the first line to “local char = script.Parent”.

The AI uses the humanoid to run, so it’s designed for characters, but you could make edits to work without a humanoid. I just put a copy of me in Workspace and named it “char” since that was the easiest.

1 Like

I was moreso asking where to put the script into(I quickly realized my mistake with putting it into a part, as the script put out an error code mentioning a humanoidrootpart). When I had put the script into serverscriptservice, renamed the character model to char, it seemed to have done nothing. The script still does not seem to work, though, even after defining char as script.parent. Perhaps there’s some issue with the structure of things that I am not noticing?
image

Here is an rbxm file that has the working AI you can drag into studio. The script is a child of char and it’s called AI.

basic_ai.rbxm (27.4 KB)

1 Like

It’s actually fine now. I realized I had the character anchored LOL. anyways, thanks for the help with trying to get it to work, despite how dumb I was being

1 Like