2
\$\begingroup\$

I've been studying steering and flocking behaviors, if not directly from Craig Reynolds work on Steering Behaviors For Autonomous Characters (http://www.red3d.com/cwr/steer/). I understand the basic concepts and how to apply forces but the main issue with this is performance. Using Unity, ECS and the job system, I have been able to implement a multi-threaded implementation of some simple behaviors (seek, arrival, evade etc).

The problem is simply that all of the implementations of steering behaviors that I find are all based on this same principle and they all seem to suffer from the same problem - framerate independence. For example, a simplified example of seeking:

// get a vector pointing directly at our target, at the maximum speed.
desired_velocity = normalize(target - position) * max_velocity

// get the steering force direction
steering = desired_velocity - velocity

// ensure the steering force never exceeds our maximum steering force
steering = truncate(steering, max_steering_force)

// ensure the velocity never exceeds the max speed
velocity = truncate (velocity + steering , max_speed)

// apply our velocity
position += velocity * delta_time

When dealing with the final velocity, we are utilizing delta_time to ensure that the velocity is applied in a framerate independent manner. But the steering force is not framerate independent at all. If this was running at 120fps, the steering would reach it's maximum velocity much faster than if the game was running at 30fps.

Is there something more I should be doing here. I've experimented with using delta_time with the steering, but that seems to make little difference.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Yes, we can do better. We need a maximun steering per unit of time.

// get the steering direction
steering = desired_velocity - velocity

// ensure the steering never exceeds our maximum steering per unit of time
steering = truncate(steering, max_steering * delta_time)

// ensure the velocity never exceeds the max speed
velocity = truncate (velocity + steering, max_speed)

That is essentially a cap on acceleration.

Of course, you would have to tweak max_steering for this to reflect the behaviour you want.


I had a look at OpenSteer, it does something similar to what I put above. Actual source (not sure if I was reading in the worng place, but that is the result that came up):

// Euler integrate (per frame) acceleration into velocity
newVelocity += _smoothedAcceleration * elapsedTime;

// enforce speed limit
newVelocity = newVelocity.truncateLength (maxSpeed ());

So, you see, it is similar to:

newVelocity = newVelocity + _smoothedAcceleration * elapsedTime;

newVelocity = truncate(newVelocity, maxSpeed())

Which is the same as:

newVelocity = truncate(newVelocity + _smoothedAcceleration * elapsedTime, maxSpeed())

Alternatively, if you prefer:

steering = _smoothedAcceleration * elapsedTime;

newVelocity = truncate(newVelocity + steering, maxSpeed())

Which is basically what I am suggesting. Evidently _smoothedAcceleration is doing some of the work. In fact, given that this steering "force" does not deal with mass, I suggest to start thinking of it in terms of acceleration.

Addendum: Looking a bit more on the context of the code, they take the force, they truncate it, then divide by mass to get acceleration, then they dampen the acceleration (they seem to be aplying it over time) to get the _smoothedAcceleration.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .