|
|
|
|
Quaternions: Flight kinematics – Keyboard input |
Now we have our camera centered on the xwing, it’s time to make our xwing fly. This will be done through the Update method.
In an airplane, when you move to the left, your flaps will be adjusted so the plane rotates around its Forward axis. Then, when you pull the joystick to you, the nose of the plane will be lifted up, which corresponds to a rotation among the Right axis.
The ProcessKeyboard method below will read keyboard input and change the values of the angles accordingly:
private void ProcessKeyboard(GameTime gameTime) { float leftRightRot = 0; float turningSpeed = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; turningSpeed *= 1.6f * gameSpeed; KeyboardState keys = Keyboard.GetState(); if (keys.IsKeyDown(Keys.Right)) leftRightRot += turningSpeed; if (keys.IsKeyDown(Keys.Left)) leftRightRot -= turningSpeed; }
All this does, is store if we need to rotate the plane around its Forward axis when you push the left or right button, as discussed above. Now, this amount of rotation has to be added to the current rotation of our xwing. First we’ll create a quaternion corresponding to this new rotation, and add it to the current rotation, which is then stored in the xwingRotation variable. Add this code to the end of the method:
Quaternion additionalRot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, -1), leftRightRot); xwingRotation *= additionalRot;
We create a quaternion that corresponds to a rotation around the (0, 0, -1) Forward axis. Next, this rotation is added to the actual rotation of our xwing.
You can notice that this method expects the GameTime object, so the amount of rotation will depend on the amount of time that has passed. This makes sure the rotation is the same for fast and slow computers.
Call this method from your Update method:
ProcessKeyboard(gameTime);
You see we also use a variable ‘gameSpeed’, which we will increase when the player is playing well, and decrease when the player crashes. We still need to define this variable at the top of our code:
float gameSpeed = 1.0f;
Now run the program! When you push the left or right arrow button on your keyboard, the plane will spin around its Forward axis!
Let’s try to pull up the nose of our xwing. So add this code to the middle of our ProcessKeyboard method:
float upDownRot = 0; if (keys.IsKeyDown(Keys.Down)) upDownRot += turningSpeed; if (keys.IsKeyDown(Keys.Up)) upDownRot -= turningSpeed;
Of course, we’ll also need to include this in our additional rotation:
Quaternion additionalRot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, -1), leftRightRot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), upDownRot);
Pressing the down arrow will result in pulling up the nose of the airplane. Running this code should enable you to rotate your xwing in any angle you want!
Not too much fun, because our xwing isn’t actually moving. So let’s create another method, 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 from the angles. We do this by taking the (0,0,-1) Forward vector, and transform it with the rotation of our xwing. This way, we obtain a vector that is the current Forward direction for our xwing.
Then we multiply it by a speed variable, and add it to the current position. Since the position variable was passed as a reference, these changes will be stored in the calling code.
We need to call this method from our Update methode. We need to pass 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:

Click here to go to the forum on this chapter!
Or click on one of the topics on this chapter to go there: Scaling to large positions Hi Riemer,
I have been experimenting with your ...Turning the plane Hi riemer
I have a problem in that the plane mo...Make the plane fly horizontally Hello,
First, congratulations for your great tu...Movement Hi riemer, after completing this chapter i run thi...Its ailerions not flaps Where did you go skiing?...What about Joypad Control Hi
Is there any one who can give me some code f...Why are you using ref? Why are you using reference in method UpdatePositi...Gimbal Lock w/ Quaternions... help? I imported/modified the quaternion example code he...Rotation Origin It seems like the center of the rotation is slight...ProcessKeyboard Question I was trying to understand the argument passed int...Can we save some memory? Hi,Riemer. I have some questions about the coding ...left-handed Isn't the coordinate system for the figure at the...Adding boost Hey, i've just gone back to series two, and have ...UpdatePosition Code In the lines:
addvector.X += (float)(Math...The additional maths needed... Hi,
Is it possible to maybe have a 'second par...Changing the controls.. First, I need to salute you for this excellent tut...
Try flying around in your 3D city, making some loops, and pay attention to the lighting of the buildings and of the xwing as you rotate.
Now you can fly your plane, maybe it’s time to do some collision detection, because flying through walls really isn’t a natural thing to happen.
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; namespace Series3D2 { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; GraphicsDevice device; Effect effect; Texture2D sceneryTexture; Model xwingModel; VertexBuffer cityVertexBuffer; int[,] floorPlan; int[] buildingHeights = new int[] { 0, 2, 2, 6, 5, 4 }; Vector3 lightDirection = new Vector3(3, -2, 5); Vector3 xwingPosition = new Vector3(8, 1, -3); Quaternion xwingRotation = Quaternion.Identity; float gameSpeed = 1.0f; Matrix viewMatrix; Matrix projectionMatrix; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { graphics.PreferredBackBufferWidth = 500; graphics.PreferredBackBufferHeight = 500; graphics.IsFullScreen = false; graphics.ApplyChanges(); Window.Title = "Riemer's XNA Tutorials -- 3D Series 2"; lightDirection.Normalize(); base.Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); device = graphics.GraphicsDevice;
effect = Content.Load<Effect>
("effects"); sceneryTexture = Content.Load<Texture2D>
("texturemap"); xwingModel = LoadModel("xwing");
LoadFloorPlan(); SetUpVertices(); }
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 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 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 UnloadContent() { }
protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
ProcessKeyboard(gameTime); float moveSpeed = gameTime.ElapsedGameTime.Milliseconds / 500.0f * gameSpeed; MoveForward(ref xwingPosition, xwingRotation, moveSpeed);
UpdateCamera(); base.Update(gameTime); } private void UpdateCamera() { Vector3 campos = new Vector3(0, 0.1f, 0.6f); campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation)); campos += xwingPosition; Vector3 camup = new Vector3(0, 1, 0); camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation)); viewMatrix = Matrix.CreateLookAt(campos, xwingPosition, camup); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.2f, 500.0f); }
private void ProcessKeyboard(GameTime gameTime) { float leftRightRot = 0; float turningSpeed = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; turningSpeed *= 1.6f * gameSpeed; KeyboardState keys = Keyboard.GetState(); if (keys.IsKeyDown(Keys.Right)) leftRightRot += turningSpeed; if (keys.IsKeyDown(Keys.Left)) leftRightRot -= turningSpeed; float upDownRot = 0; if (keys.IsKeyDown(Keys.Down)) upDownRot += turningSpeed; if (keys.IsKeyDown(Keys.Up)) upDownRot -= turningSpeed; Quaternion additionalRot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, -1), leftRightRot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), upDownRot); xwingRotation *= additionalRot; } 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 Draw(GameTime gameTime) { device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); DrawCity(); DrawModel(); base.Draw(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(); } } } }
- Website design & XNA + DirectX code : Riemer Grootjans - ©2003 - 2011 Riemer Grootjans
|
|
|
|
|