Friday, September 18, 2015

Improved MoveTo Behavior For Vehicles In Unreal Engine 4

After the previous post, we now have rudimentary MoveTo functionality for WheeledVehicle pawns.  However, locking the throttle axis at 1.0 causes some obvious problems steering around corners.

Before we tackle that, let's at least stop the vehicle when it reaches its destination.

Close Enough

I added a CloseEnough float variable to the unit, then compared it to the distance to the last NavPoint[] inside a CalculateThrottle() function.  If it's close enough, we'll set the vehicle's state to "Parking".

Yes, state machines are out of fashion and behavior trees have obvious advantages.  At first, I did build quite a bit of control logic inside the behavior tree.  Yet as I continuously added variables to the Blackboard I realized I would then need multiple behavior trees: one for each type of unit.  A helicopter would need vastly different controls.  I would have duplicate trees (or extensive Switches) despite having very similar behaviors.

I think there's still room for both, and a mix of the two is needed here.  The Behavior tree will generally define what the unit should be doing (navigating, following, attacking...) while the unit itself implements how to do it -- including how to stop doing it.

So in this case, the Behavior Tree asks "Am I Driving?" (Driving is the state set when MoveTo is called.)  If so, find a path.  The vehicle itself determines how it gets there.  When the answer is no, the Behavior Tree will "idle" until it's given a new direction.  When Parking, the vehicle sets the throttle to 0 and enables the handbrake.

Improved Throttling

At first, I approached cornering as a math problem and tried physics calculations to determine control settings around a corner.  After numerous failures, it struck me that I don't calculate any of that in my head when I drive my own car.

So I enabled possession of a unit and had it drop pylons every time I changed a control.

Also recording steering: Not as useful

While I always steer toward my next immediate goal, throttle is determined by a future guess as to where the vehicle be and what direction it will be facing.  Despite steering straight for the corner, I stop accelerating to prepare for the turn.  Once in the turn, if I'm going too fast and not facing my next goal then I apply the handbrake and throttle to force a tighter turn.

I chose to predict the position of the car after one second, purely because it was easy to use GetForwardSpeed() and apply that to the forward vector.

Armed with those few rules, the logic is actually quite simple:

If the next NavPoint past the predicted distance is NavPoint[1], throttle = 1

If steering is straight but the angle to the predicted NavPoint is increasing, throttle = 0

If steering is not straight and angle is greater than 90, apply handbrake and throttle = 1

Navigation has been recalculated, it has a straight line to the first point NavPoint[1],
throttle = 1

Will it beat the player in a speed race?  Probably not.  Is it sufficient to simulate fearless post-apocalyptic survivors?  Surprisingly so:

The logic now looks like:

Spline Paths

You'll notice that the rendered path (the green lines) expresses curves because I'm using a SplineComponent to connect the NavPoints.  Let me save you some trouble lest you attempt to guide the vehicle with a spline or use it to predict the vehicle's position.

You can't specify the rotation of individual SplineComponent points.  If you watch carefully in the videos or screenshots, you'll sometimes see exaggerated curves between some points as their rotations do not necessarily aim toward each other.

While the SplineComponent provides a very handy GetLocationAtDistanceAlongSpline() function, there is a significant amount of bookkeeping required to calculate that effectively as well as the curves mentioned above distorting the results.  I found it much simpler to sum up the NavPoint segment lengths and check if my predicted distance is greater.

Next, we'll improve parking so that the vehicle stops at its destination facing a specified direction.

1 comment:

  1. I would love to see how this blueprint was done, your awesome.