Easiest way to strafe NPC's

While developing a single-player FPS, and therefore focusing on AI enemies with Pathfindingservice, I’ve encountered a major issue: ranged enemies would eventually get on one single path, creating something that looks like a herd of sheep, hence easy to kill. With explosives existing in the game, the aspect takes away all fun, so I’ve started looking for a solution and found a very easy one.

The solution consists of 2 pointers:

  • Ranged enemies only using Pathfinding if not close enough to target
  • Strafing

In this short tutorial I’ll focus on the latter topic, as it is up to you to tweak the first behaviour.
So, assuming you have implemented some conditions which stop the NPC’s from following if they’re close enough, you’d notice that now they just stand still, which doesn’t help the cause. A real player would never stand still in a face-to-face battle, so strafing it is.

Strafing is essentially movement to left or right, but what is left or right on something with always changing position and orientation? At this question, it sparked for me: attachments.
Attachments are relative to their parent part, and are by default used in regular Roblox characters.

Therefore, we can call MoveTo on an NPC’s humanoid with the sole parameter being the HumanoidRootPart’s RootRigAttachment + a vector3 with a random X value (i.e. multiplying an integer by -1, 0, or 1: left, stand still, or right?

Here’s a real-life example:

-- variables for reading convenience
local hrp = char.HumanoidRootPart
local rootAtt = hrp.RootRigAttachment
local random = Random.new() -- better than math.random since you can choose whether you want numbers or integers
local distanceToStrafe = 20
if sight(enemy, target) == true or distance > minDist then -- can the enemy see us, or is it too far?
    path:ComputeAsync(enemy, target) -- compute the path
    spread = defaultSpread
    ... -- handle movement according to computed path
elseif ... then --[[ the enemy does not need to move to us anymore, but is it already strafing? 
up to you to choose a condition for this, I personally went with checking the humanoid's MoveDirection magnitude: if it is around 1 then the hum is moving, 0 if standing still,
but I don't recommend it because magnitude is expensive in terms of performance when used frequently]]
    spread = defaultSpread/2 -- improve enemy's "aim" while strafing
    local strafeTarget = rootAtt.WorldPosition + Vector3.new(distanceToStrafe*random:NextInteger(-1,1),0,0)
    -- multiply strafe distance arbitrarily by -1, 0, or 1 for the element of surprise
    -- if no movement is possible I also recommend conditioning the line below with it for performance
    hum:MoveTo(strafeTarget) -- fireworks
end

Edit: you should also set AutoRotate on Humanoids to false if you do not want the NPC’s to rotate to the direction they strafe at, for them to also look at the player I recommend using the AlignOrientation constraint.

If you’re new to Pathfinding, check out this article to get started: Character Pathfinding | Documentation - Roblox Creator Hub

I hope you’ve learned something from this short tutorial, feel free to share your thoughts.

Lastly, here are the before and afters, room to improve but still a major difference:
Before - Watch strafing_before | Streamable
After - Watch strafing_after | Streamable

6 Likes

Hi,
This would be interesting to try out. Do you have a .rbxl or an open source EDIT game that can show some of the examples of the NPCs using this?

Thanks

1 Like

Hello
As this is a tutorial, I suggest trying it out yourself. The pathfinding intro I’ve put in the post is somewhat lengthy as its purpose is to introduce you to all the service’s options, such as handling blocked paths, but for a simplest of demo’s it boils down to this:
a script inside a character, create a path instance, make a while loop and add a condition within which checks the distance between the character and player (in the case of testing purposes simply get the sole player’s character), and if the distance is greater than a constant of choice, compute the path and iterate through the given waypoints and move the character. If the character is within range, give it the instruction to strafe.
Optionally, you may also condition sight, for which I recommend this beginner-friendly tutorial: How to Create a Realistic NPC Eyesight System

Which in our case of something simple boils down to this:
local function sightRay(pfc, origin, target, targetChar)
	local params = RaycastParams.new()

	params.FilterDescendantsInstances = {pfc.Parent:GetChildren(), targetChar} -- pathf. characters and our target
	params.FilterType = Enum.RaycastFilterType.Exclude

	local raycast = workspace:Raycast(origin, (target - origin), params)

	if raycast then
		return true
	else
		return false
	end
end
local function checkSight(npc, target)
	local object = target.HumanoidRootPart

	local headPosition = npc.Head.Position

	local objectPosition = object.Position
        if sightRay(npc, headPosition, objectPosition, target) then
		return false
	end

	return true

end

From this you can go further into whatever direction you desire, only limiting factor is your imagination. Good luck.

3 Likes