Creating a stable movement controller for NPCs

I’m struggling to get a movement controller for NPCs that will work well. the NPCs don’t have humanoids in them, but I want to get behavior similar to Humanoid:MoveTo where they walk at a constant velocity. I am using a VectorForce because as far as I know LinearVelocity doesn’t allow other VectorForces to act on the thing it’s applying force to.

so, I went for a velocity PID controller wrapped with a positional one. not great. I’ve spent a good portion of a day and they oscillate like crazy no matter what constants I put in. the rig is massless.

local pve
local ive = 0
self._CheckPos = RunService.Heartbeat:Connect(function(dt)
	local curPos = self.Model.PrimaryPart.Position
	local curVel = self.Model.PrimaryPart.AssemblyLinearVelocity
	local dir = target - curPos

	local ve = targetSpeed - dir.Unit:Dot(curVel)
	ive += ve * dt
	local dve = (ve - (pve or ve)) * dt
	local pe = dir.Magnitude

	self._VecForce.Force = self.K * pe * (self.P * ve + self.I * ive + self.D * dve) * dir.Unit

	if dir.Magnitude <= 2.0 then
		self._CheckPos:Disconnect()
	end

	pve = ve
end)

I have been testing at around 80 studs at 24 studs/s. I tried setting minimum and maximum outputs but that was no dice either.

the strangest part is that it might work perfectly for a minute going back and forth, then suddenly explode using the same path the next time. and when it restabilizes itself, the problem continues to happen until I restart the simulation.

I also tried dropping the PID and just applying a counter force when it reaches the target. same issue.

is it doable? any alternatives can I use? thanks.

Yep using PID will just end up as LinearVelocity or Body velocity with enough tuning.

Personally I just use a flat vector force and add a drag force to maintain constant speed which has created a humanoid like effect.

1 Like

thank god, you just saved my wall.

two questions:

  • how can I derive the magnitude of the pushing force that gets it to a certain velocity? using a magnitude of 1000 went somewhere like 29 studs/s, and I’m not sure how that’s related.
  • is F=mass*(-velocity)/t for t seconds the best way to push it to a stop? I tried that earlier and it was dodgy, but it might have something to do with the previous movement implementation and not the stopping.

edit: unfortunately, though less frequent, ran into the issue again. the rig flew off in a random direction, although I didn’t get the outputs for the force, so I’ll monitor for that again.

No ideally you want to add friction force which is in the direction of movement but a constant value

F = uR

Reaction force, and friction coefficient pretty much a flat value.

However you will want this force to be 0 small at low velocities or else the character will jerk back and off, so I chose a exponential function

F = -velocity.Unit * constantStaticFrictionForce * the function below

I think that’s due to a NAN error it should be this I think cannot open my laptop ATM.

(velocity* XZVector).Unit*(speed^2)

Solve for terminal velocity where drag force is equal to the pushing force.

That’s a bit too much effort for me when I got other gameplay work to do atm.

1 Like

to save someone five minutes, the driving force should be targetSpeed^2 + mass * workspace.Gravity * friction. you also might want to apply a limit to the drag force to always be <= the driving force, otherwise you might run into some oscillation issues like I did.

edit: do NOT limit the driving force, that was actually stupid. a better idea is to just multiply both the drag and the driving force before accounting for gravity with a constant k. the controller will fail to run at high speeds if you don’t tone them down a little bit.

1 Like