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.

Friday, September 4, 2015

Basic MoveTo Behavior For Vehicles In Unreal Engine 4

Baldur's Gate, MechCommander, Neverwinter Nights, Dragon Age: Origins and Pillars of Eternity: games featuring squad-level real-time with pause tactical gameplay.  Those beloved franchises balance a mix of RPG micromanagement and battlefield strategy, but there's a void. 

As a fan of Car Wars, The Road WarriorSpy Hunter and Death Race, I want to arm and armor a squad of post-apocalyptic muscle cars then send them into the wasteland to challenge gangs of jury-rigged monstrosities-on-wheels fighting over gasoline and spare parts.

Image from
This.  A screen full of this.

Engines like Unreal (and Unity) are available for free and it's easier than ever for inexperienced developers to create games.  With no eagerly anticipated announcements forthcoming, like any smug mature gamer with a programming background I thought, "How hard could it be?"

As we're going to find out, fairly hard.

"Simple" Vehicle Movement

The first feature to tackle is point-and-click movement.  Instead of directly controlling a single vehicle, we want a squad of vehicles to drive themselves to a specified destination.

I assumed it would be as simple as importing a vehicle from one of the examples, build a Behavior Tree Task and send it a MoveTo.

Done.  Next on the list?  Guns!

Except that pawns using the WheeledVehicleMovement component don't respond to MoveTo.  They respond to Throttle, Steering and Handbrake.  So not only do we have to tell it where to go, but how to get there.

The typical approach to racing game AI is to pre-build waypoints into a circular track.  That won't work for this game due to the open-world style maps.  We need to tell a vehicle to move from anywhere to anywhere.  Fortunately, we can still use Unreal's NavMesh intended for character pawns.  The FindPathToLocationSynchronously function simply needs Start and End vectors.  The result is a NavigationPath object, which includes an array of Path Point vectors.

Who says A* Pathfinding is complicated?

Now we can build a short Behavior Tree Service that recalculates a path from the vehicle's location to its destination.  Finally, have the vehicle always steer toward the next waypoint, NavPoints[1], and set the throttle to 1.0 (for now).  Our logic so far looks like:

I thought you said this was hard?

Here's what it looks like in-game:

Next time, we'll make the throttle smarter and make use of the handbrake.