Aerial Combat in Video Games: A.K.A Dog Fighting

September 27th, 2011 by John Grden

A while back, we produced a Star Wars title for Lucas Film LTD. called “The Trench Run” which did very well on iPhone/iPod sales and later was converted to a web game hosted on StarWars.com.  Thanks to Unity3D’s ability to allow developers to create with one IDE while supporting multiple platforms, we were able to produce these 2 versions seamlessly!  Not only that, but this was one of our first releases that included the now famous Brass Monkey™ technology, which allows you to control the game experience of The Trench Run on StarWars.com with your iPhone/Android device as a remote control.  [Click here to see the video on youtube]

Now, the reason for this article is to make good on a promise I made while speaking at Unite2009 in San Francisco.  I’d said I would go over *how* we did the dog fighting scene in The Trench Run, and I have yet to do so.  So, without further delay…

Problem

The problems surrounding this issue are a few fold:

  1. How do you get the enemy to swarm “around you”?
  2. How do you get the enemy to attack?
  3. What factors into convincing AI?

When faced with a dog fight challenge for the first time, the first question you might have is how do you control the enemy to keep them flying around you (engage you), thus one of the most important ones is how to achieve the dog fight AI and playability.  The issue was more than just simple mechanics of how to deal with dog fighting, it also included issues with having an “endless” scene, performance issues on an iDevice and seamlessly introducing waves of enemies without interrupting game flow and performance.

Solution

In debug mode - way point locations shown as spheres

The solution I came up with, was to use what I would call “way points”.  Way points are just another term for GameObjects in 3D space.  I create around 10-15 or so, randomly place them within a spherical area around the player’s ship and anchor them to the player’s ship so that they’re always relatively placed around the player ( but don’t rotate with the player – position only ).  I use GameObjects and parent them to the a GameObject that follows the player, and this solves my issue of having vectors always positioned relative to the player.  The enemies each get a group of 5 way points and continually fly between them.  This solves the issue of keeping the enemies engaged with the player no matter where they fly and allows the enemy the opportunity to get a “lock” on the player to engage.   Since the way points move with the player’s position, this also creates interesting flight patterns and behavior for attacking craft, and now we’ve officially started solving our AI problem.

Check out the Demo.  Get the files

Check out the demo – the camera changes to the next enemy that gets a target lock on the player ship (orange exhaust).  Green light means it has a firing lock, red means it has a lock to follow the player.

Download the project files and follow along.

Setting up the Enemy Manager

The Enemy manager takes care of creating the original way points and providing an api that allows any object to request a range of way points.   Its functionality is basic and to the point in this area.  But it also takes care of creating the waves of enemies and keeping track of how many are in the scene at a time (this demo does not cover that topic, I leave that to you).

First, we’ll create random way points and scatter them around.  Within a loop,  you simply use Random.insideUnitSphere to place your objects at random locations and distances from you within a sphere.  Just multiply the radius of your sphere (fieldWidth) by the value returned by insideUnitSphere, and there you go – all done.

Now, the method for handing out way points is pretty straight forward.  What we do here is give our enemy craft a random set of way points.  By doing this, we’re trying to avoid enemies having identical sets and order of way points given to each enemy.

NOTE:  You can change the scale of the GameObject that the way points are parented to and create different looking flight patterns.  In the demo files, I’ve scaled the GameController GameObject on the Y axis by setting it to 2 in the IDE.  Now, the flight patterns are more vertical and interesting, rather than flat/horizontal and somewhat boring.  Also, changing the fieldWidth to something larger will create longer flight paths and make it easier to shoot enemies.  A smaller fieldWidth means that they’ll be more evasive and drastic with their moves.  Coupled with raising the actualSensivity, you’ll see that it becomes more difficult to stay behind and get a shot on an enemy.

Setting up the enemy aircraft

The enemy needs to be able to fly one their own from way point to way point.  Once they’re in range of their target, they randomly select the next way point.   To make this look as natural as possible, we continually rotate the enemy until they’re within range of “facing” the next target and this usually looks like a nice arc/turn as if a person were flying the craft.  This is very simple to do thankfully.

First, after selecting your new target, update the rotationVector (Quaternion) property for use with the updates to rotate the ship:

Now, in the updateRotation method, we rotate the ship elegantly toward the new way point, and all you have to do is adjust “actualSensitivity” to achieve whatever aggressiveness you’re after:

As you’re flying, you’ll need to know when to change targets.  If you wait until the enemy hits the way point, it’ll likely never happen since the way point is tied to the player’s location.  So you need to set it up to see if it’s “close enough” to make the change, and you need to do this *after* you update the enemy’s position:

Enemy flying to his next way point



You can also simply change the target for an enemy on a random timer – either way would look natural.

NOTE:  Keep the speed of the player and the enemy the same unless you’re providing acceleration controls to match speeds.  Also, keep in mind, that if your actualSensitivity is low (slow turns), and your speed is fast, you will have to make the bufferDistance larger since there is an excellent chance that the enemy craft will not be able to make a tight enough turn to get to a way point, and will continue to do donuts around it.  This issue is fixed if the player is flying around, and is also remedied by using a timer to switch way point targets.    You can also add code to make the AI more convincing that would suggest that way points are switched very often if the enemy is being targeted by the player (as well as increasing the actualSensitivity to simulate someone who is panicking).

Targeting the Player

Red means target aquired : Green means target lock to fire

The next thing we need to talk about is targeting, and that’s the 2nd part of the AI.  The first part is the enemy’s flight patterns, which we solved with way points.  The other end of it is targeting the player and engaging them.  We do this by checking the angle of the enemy to the player.  If that number falls within the predefined amount, then the currentTarget of the enemy is set to the player.

The nice part about this is that, if the player decides to fly in a straight path, then eventually (very soon actually) all of the baddies will be after him and shooting at him because of the rules above.  So, the nice caveat to all of this is that it encourages the player fly evasively.  If you become lazy, you get shot 0.o

You can also change the property “actualSensitivity” at game time to reflect an easy/medium/hard/jedi selection by the player.  If they choose easy, then you set the sensitivity so that the enemy reacts more slowly to the turns.   If it’s Jedi, then he’s a lot more aggressive and the “actualSensitivity” variable would be set to have them react very quickly to target changes.

Firing

And finally, the 3rd part to the AI problem is solved by having yet another angle variable called “firingAngle”.  “firingAngle” is the angle that has to be achieved in order to fire.  While the angle for changing targets is much wider (50), the ability to fire and hit something is a much tighter angle ( >= 15 ).  So we take the “enemyAngle” and check it against “firingAngle” and if it’s less, we fire the cannons on the player.  You could also adjust the “firingAngle” to be bigger for harder levels so that the player’s ship falls into a radar lock more frequently.

In the sample, I added an ellipsoid particle emitter/particle animator/particle renderer to the enemy ship object set the references to the “leftGun / rightGun” properties and unchecked “Emit” in the inspector. Then, via the Enemy class, I simply set emit to true on both when its time to fire:

Conclusion

So, we’ve answered all 3 questions:

  1. How do you get the enemy to swarm “around you”?
  2. How do you get the enemy to attack?
  3. What factors into convincing AI?

With the way point system, you keep the enemy engaged around you and the game play will be very even and feel like a good simulation of a dog fight.  The way points keep the enemy from getting unfair angles and provide plenty of opportunity for the player to get around on the enemy and take their own shots, as well as provide flying paths that look like someone is piloting the ship.  And adjusting values like “actualSensitivity”, “fieldWidth” and “firingAngle” can give you a great variety of game play from easy to hard.  When you start to put it all together and see it in action, you’ll see plenty of room for adjustments for difficulty as well as getting the reaction and look you want out of your enemy’s AI.

Have a Bandit Day!

Android Graphics and Animation Part III – Handling the Accelerometer

September 19th, 2011 by Keith Peters

It’s been a while, but we finally come to part 3 of this series.

In part 1 we learned the basics of setting up an Android project in Eclipse and drawing to the canvas.

In part 2 we covered animation and threading.

In this episode, we will look at handling the accelerometer, allowing you to control the animation by tilting your Android device.

Set up

We’ll continue on with the same project we created last time, which had a circle moving from left to right across the screen. Since we’ll be allowing the user to tilt the phone in any direction, we want to disable the auto-rotation feature that will change the orientation when the phone is tilted. This is done in the Android manifest xml file. We want to add the following line to the activity tag:

This will force the device to remain in portrait orientation no matter how it is tilted. The whole manifest will now look like this:

Listening for Accelerometer Events

The next thing we want to do is listen for accelerometer events, which will occur whenver the device is tilted. In reality, it’s not like you will only get events when the device is moving. You will get a steady stream of accelerometer events even if the device is sitting by itself on a table. But different types of applications may need to get these events more or less often. For example, a game may need to be very responsive and be very up to date on the events coming in, whereas in another type of application, it may not be so vital and you can opt to get the events less often to save on processing. Android allows you to listen for these events using the following values to control how often you will get them:

These are all static values on the SensorManager class. We’ll see how they are used in a moment.

First we’ll go into our AnimView class which starts out like this right now:

In addition to implementing the SurfaceHolder.Callback interface, we now want to implement the android.hardware.SensorEventListener interface. So import that interface and add it to the class signature:

This, of course, will cause the compiler to complain that you have not implemented the required methods of that interface. Triggering a quick fix will add the following method stubs:

Before we do anything with these, we need to write the code that listens for the sensor events. We’ll do that right in the constructor.

First we get an instance of the SensorManager class. This is done by calling the getSystemService on the context that is passed into the view’s constructor. We tell it which service we want: the Context.SENSOR_SERVICE.

Once we have the SensorManager, we need to check if there is indeed an accelerometer on this device. If so, we get the first available one. I’m not sure if any existing device has more than one accelerometer, but the api leaves that possibility open.

Finally, we register this class as a listener to the accelerometer, passing in the sensor delay you need, as covered earlier. At this point, we should start receiving events in the two methods we just added.

All we are interested in now is the onSensorChanged method, which will give us the data on how the device is currently oriented. As you can see, this method gets passed an instance of SensorEvent. This object has a property called values, which is a simple array of floats. For accelerometer events, the tilt on the x, y and z axes are represented by the first 3 elements of the array. i.e.:

event.values[0] is the degree of tilt on the x axis
event.values[1] is the degree of tilt on the y axis
event.values[2] is the degree of tilt on the z axis

All of these values will be in the range of -9.81 to +9.81. For a thorough explanation of why these values are used, see the SensorEvent class documentation here:

http://developer.android.com/reference/android/hardware/SensorEvent.html

For our purposes, we only care about the x and y axes. We’ll pass those values to the AnimThread class with a method called setTilt. This method doesn’t exist yet, but we’ll create it soon.

Note that the method will probably be called before and/or after the thread is created, so we’ll test to make sure it exists before calling any methods on it. For this simple demo, we won’t do any high or low pass filtering as described in the SensorEvent documentation, but if you wanted to do so, this would be a good place to do it.

Handling the Tilt Values

Now we need to create the setTilt method in AnimThread, but first let’s create a few properties there. We’ll need something to hold the raw tilt values, and some properties to hold the current position and velocity of the ball. The top of that class should now look like this:

Now we can create the setTilt method, which will be pretty simple:

Now all we have to do is make use of those values. The strategy is to add the tilt values (or at least a part of them) to the velocity values, then add the velocity to the position values, and finally draw the circle at the final x, y point. Here’s the run method in full:

We’re now at a point where you can test the app. Hold the phone flat when you start it. The ball should “roll” in the direction you tilt the phone. Of course, it will roll out of sight if you’re not careful, so we need to fix that next.

Handling Screen Edges – Bouncing

The following stuff I’ve covered in a number of books and tutorials on my personal site, www.bit-101.com, so I’m not going to belabor the point. We’re just going to see if the ball has gone past any edge of the screen and if so, place it on the edge and reverse the velocity on that axis. Here’s the final AnimThread class in full:

Now as you tilt the device, the ball will bounce off the edges of the screen, with just a little less force than it hit.

Summary

We now have a working, accelerometer-based interactive animation. This isn’t meant to be a perfect example in terms of best practices. I’d probalby pull out a lot of hard coded values into variables, extract some of the code into separate methods, etc. I’d also get a time delta between updates so that devices running at different speeds would run the animation at the same rate. Perhaps I’ll be able to cover some of that in a future tutorial. But this gives you a good idea of the structure and what happens where and how to get started.