-
-
Notifications
You must be signed in to change notification settings - Fork 102
Riemers3DXNA2flightsim08flightkinematics
Now we have our camera centered on the xwing, it is time to make our xwing fly. This will be done through the Update method.
In a plane, when you move to the left, your flaps will be adjusted so the plane rotates around its forward axis, when you pull the joystick towards you, the nose of the plane will be lifted up resulting in a rotation among the Right axis.
We will start by adding a variable called ‘gameSpeed’, which we will increase the speed of the game when the player is playing well and decrease when the player crashes. We need to define this variable in the Properties section of our code:
private float _gameSpeed = 1.0f;
Next, we will add the ProcessKeyboard method, which will read the keyboard input and change the angles of the flight accordingly:
private void ProcessKeyboard(GameTime gameTime)
{
float leftRightRotation = 0;
float turningSpeed = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
turningSpeed *= 1.6f * _gameSpeed;
KeyboardState keys = Keyboard.GetState();
if (keys.IsKeyDown(Keys.Right))
{
leftRightRotation += turningSpeed;
}
if (keys.IsKeyDown(Keys.Left))
{
leftRightRotation -= turningSpeed;
}
}
All this does is update a value (leftRightRotation) to rotate the plane around its forward axis when you push the left or right button, as discussed above, this amount of rotation will be added to the current rotation of our xwing. First, we will create a quaternion corresponding to this new rotation and then add it to the current rotation, which is then applied to the xwingRotation variable.
Add this code to the end of the ProcessKeyboard method:
Quaternion additionalRotation = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, -1), leftRightRotation);
_xwingRotation *= additionalRotation;
This creates a quaternion that corresponds to the rotation around the (0, 0, -1) forward axis. Next, this rotation is then added to the current rotation of our xwing.
You will notice that this method expects the GameTime object, so that the amount of rotation will depend on the actual amount of time that has passed, this makes sure the rotation is the same for fast and slow computers.
We now need to call this method from the Update method:
ProcessKeyboard(gameTime);
Now when you run the program, when you push the left or right arrow button on your keyboard, the plane will spin around its forward axis, turning left and right.
Next, let us try to pull the nose of our xwing up, so add this code to the middle of our ProcessKeyboard method:
float upDownRotation = 0;
if (keys.IsKeyDown(Keys.Down))
{
upDownRotation += turningSpeed;
}
if (keys.IsKeyDown(Keys.Up))
{
upDownRotation -= turningSpeed;
}
Of course, we now also need to include this in our additional rotation in the Quaternion calculation for the xwings model's rotation (replacing the previous Quaternion line in our ProcessKeyboard method):
Quaternion additionalRotation = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, -1), leftRightRotation) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), upDownRotation);
Pressing the down arrow now will result in pulling up the nose of the airplane, and pressing up will do the reverse, just like normal flight controls. Running this code will also enable you to rotate your xwing at any angle you want!
This is not too much fun at the moment because our xwing is not actually moving, So let us create another method called MoveForward which will update the position of our xwing according to the current xwingRotation:
private void MoveForward(ref Vector3 position, Quaternion rotationQuat, float speed)
{
Vector3 addVector = Vector3.Transform(new Vector3(0, 0, -1), rotationQuat);
position += addVector * speed;
}
First, we calculate the direction of movement, by taking the (0, 0, -1) forward vector and transforming it with the provided rotation and then multiplying it by the given speed. Using this method, we can obtain a vector that is the current forward direction for our xwing.
Since the position variable was passed as a reference (using the "ref" keyword), when we call this method the changes will be stored in the calling code.
We need to call this method from our Update method, passing in the amount of movement:
float moveSpeed = gameTime.ElapsedGameTime.Milliseconds / 500.0f * _gameSpeed;
MoveForward(ref _xwingPosition, _xwingRotation, moveSpeed);
And there you have it! When you run this code, you should be able to fly your xwing through the 3D city, as shown in the image below:
Try flying around in your 3D city, making some loops, and pay attention to the lighting of the buildings and the xwing as you rotate.
Now that you can fly your plane, maybe it is time to do some collision detection, because flying through walls is not exactly a natural thing to happen.
You can try these exercises to practice what you have learned:
- If you prefer to invert your flying controls, try reversing the up/down direction. Or better yet, add an additional boolean variable to switch between flight modes.
- Try adding GamePad controls using the "GamePad" class, which works in the same way as the Keyboard, but uses "Buttons" instead of "Keys"
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Series3D2
{
public class Game1 : Game
{
//Properties
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private GraphicsDevice _device;
private Effect _effect;
private Matrix _viewMatrix;
private Matrix _projectionMatrix;
private Texture2D _sceneryTexture;
private int[,] _floorPlan;
private VertexBuffer _cityVertexBuffer;
private int[] _buildingHeights = new int[] { 0, 2, 2, 6, 5, 4 };
private Model _xwingModel;
private Vector3 _lightDirection = new Vector3(3, -2, 5);
private Vector3 _xwingPosition = new Vector3(8, 1, -3);
private Quaternion _xwingRotation = Quaternion.Identity;
private float _gameSpeed = 1.0f;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
_graphics.PreferredBackBufferWidth = 500;
_graphics.PreferredBackBufferHeight = 500;
_graphics.IsFullScreen = false;
_graphics.ApplyChanges();
Window.Title = "Riemer's MonoGame Tutorials -- 3D Series 2";
LoadFloorPlan();
_lightDirection.Normalize();
base.Initialize();
}
private void SetUpCamera()
{
_viewMatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, -7), new Vector3(0, 1, 0));
_projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, _device.Viewport.AspectRatio, 0.2f, 500.0f);
}
private void SetUpVertices()
{
int differentBuildings = _buildingHeights.Length - 1;
float imagesInTexture = 1 + differentBuildings * 2;
int cityWidth = _floorPlan.GetLength(0);
int cityLength = _floorPlan.GetLength(1);
List<VertexPositionNormalTexture> verticesList = new List<VertexPositionNormalTexture>();
for (int x = 0; x < cityWidth; x++)
{
for (int z = 0; z < cityLength; z++)
{
int currentBuilding = _floorPlan[x, z];
//floor or ceiling
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z), new Vector3(0, 1, 0), new Vector2(currentBuilding * 2 / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z), new Vector3(0, 1, 0), new Vector2((currentBuilding * 2 + 1) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentBuilding * 2 + 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z), new Vector3(0, 1, 0), new Vector2((currentBuilding * 2 + 1) / imagesInTexture, 1)));
if (currentBuilding != 0)
{
//front wall
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentBuilding * 2) / imagesInTexture, 0)));
//back wall
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(0, 0, 1), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z), new Vector3(0, 0, 1), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z), new Vector3(0, 0, 1), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z), new Vector3(0, 0, 1), new Vector2((currentBuilding * 2) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
//left wall
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(-1, 0, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, _buildingHeights[currentBuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(-1, 0, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
//right wall
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentBuilding * 2 - 1) / imagesInTexture, 0)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 1)));
verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, _buildingHeights[currentBuilding], -z), new Vector3(1, 0, 0), new Vector2((currentBuilding * 2) / imagesInTexture, 0)));
}
}
}
_cityVertexBuffer = new VertexBuffer(_device, VertexPositionNormalTexture.VertexDeclaration, verticesList.Count, BufferUsage.WriteOnly);
_cityVertexBuffer.SetData<VertexPositionNormalTexture>(verticesList.ToArray());
}
private void LoadFloorPlan()
{
_floorPlan = new int[,]
{
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,1,1,0,0,0,1,1,0,0,1,0,1},
{1,0,0,1,1,0,0,0,1,0,0,0,1,0,1},
{1,0,0,0,1,1,0,1,1,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,1,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,1,1,0,0,0,1,0,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,1,0,0,0,1,0,0,0,0,1},
{1,0,1,0,0,0,0,0,0,1,0,0,0,0,1},
{1,0,1,1,0,0,0,0,1,1,0,0,0,1,1},
{1,0,0,0,0,0,0,0,1,1,0,0,0,1,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
Random random = new Random();
int differentBuildings = _buildingHeights.Length - 1;
for (int x = 0; x < _floorPlan.GetLength(0); x++)
{
for (int y = 0; y < _floorPlan.GetLength(1); y++)
{
if (_floorPlan[x, y] == 1)
{
_floorPlan[x, y] = random.Next(differentBuildings) + 1;
}
}
}
}
private Model LoadModel(string assetName)
{
Model newModel = Content.Load<Model>(assetName);
foreach (ModelMesh mesh in newModel.Meshes)
{
foreach (ModelMeshPart meshPart in mesh.MeshParts)
{
meshPart.Effect = _effect.Clone();
}
}
return newModel;
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
_device = _graphics.GraphicsDevice;
_effect = Content.Load<Effect>("effects");
_sceneryTexture = Content.Load<Texture2D>("texturemap");
_xwingModel = LoadModel("xwing");
SetUpCamera();
SetUpVertices();
}
private void UpdateCamera()
{
Vector3 cameraPosition = new Vector3(0, 0.1f, 0.6f);
cameraPosition = Vector3.Transform(cameraPosition, Matrix.CreateFromQuaternion(_xwingRotation));
cameraPosition += _xwingPosition;
Vector3 cameraUpDirection = new Vector3(0, 1, 0);
cameraUpDirection = Vector3.Transform(cameraUpDirection, Matrix.CreateFromQuaternion(_xwingRotation));
_viewMatrix = Matrix.CreateLookAt(cameraPosition, _xwingPosition, cameraUpDirection);
_projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, _device.Viewport.AspectRatio, 0.2f, 500.0f);
}
private void ProcessKeyboard(GameTime gameTime)
{
float leftRightRotation = 0;
float upDownRotation = 0;
float turningSpeed = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
turningSpeed *= 1.6f * _gameSpeed;
KeyboardState keys = Keyboard.GetState();
if (keys.IsKeyDown(Keys.Right))
{
leftRightRotation += turningSpeed;
}
if (keys.IsKeyDown(Keys.Left))
{
leftRightRotation -= turningSpeed;
}
if (keys.IsKeyDown(Keys.Down))
{
upDownRotation += turningSpeed;
}
if (keys.IsKeyDown(Keys.Up))
{
upDownRotation -= turningSpeed;
}
Quaternion additionalRotation = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, -1), leftRightRotation) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), upDownRotation);
_xwingRotation *= additionalRotation;
}
private void MoveForward(ref Vector3 position, Quaternion rotationQuat, float speed)
{
Vector3 addVector = Vector3.Transform(new Vector3(0, 0, -1), rotationQuat);
position += addVector * speed;
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
UpdateCamera();
ProcessKeyboard(gameTime);
float moveSpeed = gameTime.ElapsedGameTime.Milliseconds / 500.0f * _gameSpeed;
MoveForward(ref _xwingPosition, _xwingRotation, moveSpeed);
base.Update(gameTime);
}
private void DrawCity()
{
_effect.CurrentTechnique = _effect.Techniques["Textured"];
_effect.Parameters["xWorld"].SetValue(Matrix.Identity);
_effect.Parameters["xView"].SetValue(_viewMatrix);
_effect.Parameters["xProjection"].SetValue(_projectionMatrix);
_effect.Parameters["xTexture"].SetValue(_sceneryTexture);
_effect.Parameters["xEnableLighting"].SetValue(true);
_effect.Parameters["xLightDirection"].SetValue(_lightDirection);
_effect.Parameters["xAmbient"].SetValue(0.5f);
foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
{
pass.Apply();
_device.SetVertexBuffer(_cityVertexBuffer);
_device.DrawPrimitives(PrimitiveType.TriangleList, 0, _cityVertexBuffer.VertexCount / 3);
}
}
private void DrawModel()
{
Matrix worldMatrix = Matrix.CreateScale(0.0005f, 0.0005f, 0.0005f) *
Matrix.CreateRotationY(MathHelper.Pi) *
Matrix.CreateFromQuaternion(_xwingRotation) *
Matrix.CreateTranslation(_xwingPosition);
Matrix[] xwingTransforms = new Matrix[_xwingModel.Bones.Count];
_xwingModel.CopyAbsoluteBoneTransformsTo(xwingTransforms);
foreach (ModelMesh mesh in _xwingModel.Meshes)
{
foreach (Effect currentEffect in mesh.Effects)
{
currentEffect.CurrentTechnique = currentEffect.Techniques["Colored"];
currentEffect.Parameters["xWorld"].SetValue(xwingTransforms[mesh.ParentBone.Index] * worldMatrix);
currentEffect.Parameters["xView"].SetValue(_viewMatrix);
currentEffect.Parameters["xProjection"].SetValue(_projectionMatrix);
currentEffect.Parameters["xEnableLighting"].SetValue(true);
currentEffect.Parameters["xLightDirection"].SetValue(_lightDirection);
currentEffect.Parameters["xAmbient"].SetValue(0.5f);
}
mesh.Draw();
}
}
protected override void Draw(GameTime gameTime)
{
_device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0);
DrawCity();
DrawModel();
base.Draw(gameTime);
}
}
}