Tuesday, July 16, 2013

How to fly a plane

http://forum.unity3d.com/threads/43984-Unity-testproject-a-little-flightsim

So, I want to make a movement script for unity to control a plane.  I want a nice simple control system, so I'm not going to use physics.  I'm going to use variable to control the required movement.  So, question one, how does a plane move?

How a plane moves

A plane has 4 axis of movement, Thrust, Pitch, Yaw and Roll.  Thrust is the forward velocity of the plane, in unity this would be the local Z position.  Pitch is the vertical angle of the nose, if you increase pitch it moves the nose up, making the plane climb higher, in Unity this would be the local X Rotation.  Yaw controls the horizontal angle of the plane, or the  global Y rotation (we'll get to why this is global).  and roll is local Z rotation.

While in flight, if the player removes all hands from the controls, I want the plane to move back towards a cruising speed Thrust value, the pitch and roll to move back to zero so the plane is level, and the yaw to stay constant.

if the player wants to adjust the thrust they should be able to accelerate to a maximum value, and decelerate to a minimum (not too slow).  Roll, can complicate the yaw and pitch calculations, as it rotates them (if the plane is Rolled 45 degrees (on the Z rotation) then the Pitch (X rotation) is no longer strait up and down but at 45 degrees.  This allows players to make sharp banking turns in some flight simulators, but this does not match the simple, arcade feel I'm going for, so I'm going to simulate the Roll axis by rotating the model When the player adjust the Yaw (So the plane looks like its banking), While applying the rest of my motion to a dummy game object containing my plane model.  This means that no matter how the plane looks like its banking, the X rotation is always perfectly vertical.

The Yaw is set to global rotation both to avoid being altered by the roll and pitch and so that later, its easier to get the global direction the plane is pointing to build a compass into the HUD.

Enough theory, lets code

So, lets start with a fresh scene in Unity.  Create an empty gameobject and place the plane model (or box) and your Main camera underneath it in the hierarchy.  Throw in some terrain and a light so you have some reference to fly over.
.








The other option is to use a follow cam.

Create a New CSharp script on your plane model, and lets get stated.

We'll being with Thrust. We'll need 4 variables. speed, cruiseSpeed, minSpeed and maxSpeed.  Set the min and max to whatever values you like (I used 10 and 60) and the speed and cruise to a value in the middle of the two (30).
float speed = 30.00f;
float cruiseSpeed = 30.00f;
float minSpeed = 10.00f;
float maxSpeed = 60.00f;
Within the update function we need 4 basic calculations.  A calculation to alter the move speed if the button is pressed.  A calculation to move the speed towards cruising if no button is pressed, code to prevent the speed from exceeding the maximum or dropping below the minimum and finally, code to translate the speed value to the objects transform.
Step 1.
//If the button is pressed, alter speed value
if(Input.GetButton("Thrust"))
{
speed += (Input.GetAxis("Thrust") * Time.deltaTime)* 25;
}

Input.GetButton is a boolean value so its false at zero and true at any other value.  Since the axis controls in unity move between values of - 1 to 1 this means that the statement is true as soon as the button is pressed. The 25 in there is the Acceleration value.  This can be changed.  For a different acceleration and deceleration value use two if statements

if (Input.GetAxis("Thrust") > 0)
if (Input.GetAxis("Thrust") < 0)

Step 2.

// Return to cruising speed it button is not pressed
else if(!Input.GetButton("Thrust"))
{
if(speed > cruiseSpeed)
speed -= (Time.deltaTime * 20);

else if (speed < cruiseSpeed)
speed += (Time.deltaTime * 20);
}

If there is no input from Thrust and speed is greater than cruise speed slow down or if its less than, speed up.

Step 3.

//Prevent Speeding or Slowing too much
if(speed > maxSpeed)
speed = maxSpeed;

if (speed < minSpeed)
speed = minSpeed;

This will keep the speed within limits.

Step 4.

//Apply Speed value to object
transform.parent.transform.Translate (0,0,speed * Time.deltaTime);

Transform.parent allows an object to access its parent objects properties.  transform.Translate is the equivalent of a += to each position coord of and object.  So transform.translate (x, y, z) means
transform.position.x += x;
transform.position.y += y;
transform.position.z += z;
in our case, we are leaving the x and y axis where they are and adjusting the z axis according to our speed value and the frame rate.

So Far our script looks like this.  When we test the script the plane should start moving forward, and should speed up and slow down when the "Thrust" Axis is used, and reset its speed otherwise.

But I want to turn!

Let's add some yaw controls, this bits easy

//***********  Yaw  ***********//

transform.parent.transform.Rotate(0, Input.GetAxis("Yaw") *Time.deltaTime*30,0, Space.World);

Just like above we want to affect the PlaneGameObject so we use transform.parent.  transform.Rotate works it the same way as transform.Translate except it effects Rotation rather than position.  We multiply by 30 for the turn speed.

This isn't nested in an if statment because when the button isn't pressed Input.GetAxis("Yaw") = 0.

0 * Time.deltaTime = 0 and
0 * 30 = 0

meaning that the rotation isn't affected at all when the button isn't pressed.

I still can't go up and down....

Next we'll set up the pitch controls.

// Rotate Nose
transform.parent.transform.Rotate(Input.GetAxis("Pitch")*Time.deltaTime*40,0,0);

This is exactly how we set up the yaw, but we also want the pitch to return to zero if the button is not being pressed

//Rotate Nose to Middle
rotX = ((int)transform.parent.transform.eulerAngles.x);

if((!Input.GetButton("Pitch")) && rotX != 0)
{
if((rotX > 0) && (rotX < 180))
transform.parent.transform.Rotate(Time.deltaTime*-50,0,0);
if((rotX > 180) && (rotX < 360))
transform.parent.transform.Rotate(Time.deltaTime*50,0,0);
if((transform.parent.transform.eulerAngles.x > -1) && (transform.parent.transform.eulerAngles.x < 1))
{
rotXfloat = transform.parent.transform.eulerAngles.x;
transform.parent.transform.Rotate(-rotXfloat,0,0);
}
}
we need to declare a new int variable up the top of the script called rotX. every frame we set rotX to equal the rotation value of the PlaneGameObject converted to an integer. Similar to the code used to return the speed to its cruising value, we move the pitch up to zero if the angle is with a range, and down to zero for the other half of the possible circle.  We use an Int as the value because of delta time.  movement based on deltaTime often produces figures with many decimal places.  This can cause the game to 'bounce' the rotation value back and forth over the 0 mark, overshooting each time.  Run a trace on your speed value to see this happening.  The effect isn't noticeable on the plane with Thrust, but for the Pitch this will cause the whole plane to shake up and down.  By setting the value to an int once the value is between -1 and 1 the first two if statements won't be able to tell and will not 'bounce' the value back and forth.

Once the pitch is within this 'deadzone' the third if statement detects the value and sets it too exactly 0, preventing vertical drift.

Now you should have full control of the Speed, Pitch and Yaw of your plane.

Sure I can move, but it looks crap

Adding the roll will greatly improve the appearance of the flight.

rotZ = transform.eulerAngles.z;

if ((Input.GetAxis("Yaw") <= 0) && ((rotZ < 30) || (rotZ > 320)))
{
transform.Rotate (0,0, (-(Input.GetAxis("Yaw")*Time.deltaTime))*100);
}

if ((Input.GetAxis("Yaw") >= 0) && ((rotZ > 330) || (rotZ < 40)))
{
transform.Rotate (0,0, (-(Input.GetAxis("Yaw")*Time.deltaTime))*100);
}


if ((!Input.GetButton("Yaw"))&&(rotZ != 0))
{
rotZint = (int)transform.eulerAngles.z;

if((rotZint > 0) && (rotZint < 179))
transform.Rotate(0,0,Time.deltaTime*-100);
if((rotZint > 180) && (rotZint < 360))
transform.Rotate(0,0,Time.deltaTime*100);
}

The first two if statements define the maximum range for the rotation and rotate based on rotZ (go declare that by the way (: ).  the reason for the slight staggering of the positive and negative ranges is again to deal with deltatime making things imprecise.  if the rotation of both was set to 30 deltaTime would set the rotation too 30.001 meaning that you couldn't rotate in the other direction.

the final if statement rotates the plane back to zero when the key isn't being pressed using an int value for the same reasons as the Pitch.

No comments:

Post a Comment