Monday, December 7, 2015

Enemy Detection During a Custom MoveTo for Vehicles in Unreal Engine 4

Now that our vehicles can move themselves autonomously fairly well, we can have them check for enemies nearby during their travel.

Unreal 4 includes a new Environment Query System, which will do most of the work for us:

Like FindPathToLocation taking all the fun out of PathFinding...

The EQS Quick Start Guide does a fairly good job of walking through a sample setup, and we'll follow much of that for the first query.

Detecting Enemies

A query needs three objects: a context, a generator and tests.  The context represents "who": the perspective from which actor to perform the query.  The generator provides "what" or "where": the list of actors or locations to choose from.  Tests answer "why" items should be included or excluded from the list.

The default context is the "Querier": the actor performing the query.  We want to find enemies near the vehicle making the query, so no changes there.

Unreal provides several built in generators, and the closest to our needs is "Actors of Class".  However, that could also detect vehicles that are our allies so we need to build our own generator from EnvQueryGenerator_BlueprintBase.  We want to select from all vehicles, then filter out the vehicles that are our allies.

Clipped for readability...

Finally, we'll include the built-in tests: Distance and Trace on Visibility (to make sure it's not behind unreachable terrain).

The final query:

Note that we're providing a parameter (FindEnemyRadius) for the Distance query.  Also, don't be fooled by "prefer greater".  You can configure the test to use Inverse Linear scoring so that nearby objects score higher.

The EQS provides a handy Testing Pawn to check the results of the query during simulation.  It can show the scoring values as well as which tests failed on excluded items.

Calculating Controls to Aim

Assuming that our vehicle has front-mounted weapons, we need to tell it how to aim at a detected enemy.  Let's also assume that various vehicles may have short, medium or long-range weapons and will prefer different optimal ranges.  Our logic for aim is then:

  1. LineTraceByChannel from our vehicle to the enemy to determine if we can see our target (A defeated enemy or even our ally could be in the way.)
  2. If we can't see our target, drive to the LineOfFireLocation (More on that in a moment.)
  3. LineTraceByChannel from our forward direction to FindEnemyRadius to check if we're ready to fire
  4. If we're ready to fire and the target is in the optimal range, stop.  (And fire.)
  5. If the target is greater than our optimal range, speed up.
  6. If the target is less than our optimal range, reverse.
All this code to set Steering and Throttle...

Note that this is not the Pursuit behavior in Fernando Bevilacqua's excellent blog.  This is simply lining up the shot.  We'll build Pursuit behavior later when implementing explicit "attack this target" controls.

Finding LineOfSite Location

What if the enemy is in range, but we can't see it due to an obstacle?  (A defeated enemy or an ally mentioned earlier.)

We'll implement another Environment Query to find a location to fire from.  In this case, we need to create a custom Context because the perspective will be from the target, not our vehicle.  So the context needs to peek at the querier's properties to see who it's target is.

Since we have an optimal range we want to fire from, we'll use the built-in Donut generator to create a list of locations around our target within that range.  Then test to see which ones the target can see (and our vehicle could see from) and the closest of those to our vehicle:

That yields:


I ran into two trouble spots with the EQS.

First, an Environment Query that has no result does not clear its Blackboard entry and set it to None.  It simply fails and moves on.  That inadvertently leaves the last detected enemy even when none are found.  So it was necessary to create a "Clear Found Enemy" task at the start of the Enemy Detection Sequence.

Second, the FindLineOfFire query would not return results when run from a Blueprint.  It worked just find from the Behavior Tree or the EQS Test Pawn.  It really belongs inside the vehicle's Blueprint, as the Behavior Tree should simply be saying "Attack This" then let it do it's thing.  So until that's resolved, it's temporarily in the Behavior Tree.

The Tree looks like this:

Ideally, each sequence would only have two tasks...

Here's what it looks like in action:

And yes it's finally time to apply some post-apocalyptic skins to the vehicles and attach some very loud guns...

No comments:

Post a Comment