Bombardo-Project2

=SSE 643: Advanced Graphic= =Project 2: Advanced Graphic Interfaces=

(FIRST PERSON SHOOTER FOR XBOX 360)
=By Chris Bombardo= =For: Dr. Paul E MacNeil=

Report (same as Wiki) in Word 2007-

Table of Content
Resources Overview Setup First Person Camera Adding 3D Object Moving Camera Creating Weapon Terrain Crouch

Hard Copy Resources:
"Game Design " (1st ed.) by Thompson et al., Wiley, ISBN 0471968943

XNA 3.0 Game Programming Recipes: A Problem-Solution Approach (Expert's Voice in XNA) by Riemer Grootjans - I would recommend this book for XNA.

Web Resources
Getting Started with XNA Game http://msdn.microsoft.com/en-us/library/bb203894.aspx

3D Models http://www.turbosquid.com/

XNA FUSION http://www.xnafusion.com

DPOWARE http://www.dhpoware.com/

OVERVIEW – FIRST PERSON SHOOTER FOR XBOX 360

 * 3-D First Person Shooter**

Project two will be a continuation of project one, but with a big jump. There is one thing I like to play on game systems and that is first person shooters. Starting to read about first person shooter (“FPS”) game creation I understand one of the most difficult processes was going to be creating and understand the first person camera. It is not just creating the camera, for there are a lot of demos with FPS camera, but to create the camera to act as I expected it to with other objects and controls. For the most part I was right about the camera, except when it came to adding a weapon, is where it really got confusing for a while.

I broke down the project into subparts, so I could work on one subpart at a time. On completing a subpart I could test and move forward. Using subparts turned out to work well, assisting in several areas in creating my first FPS game. First, it broke down the entire FPS game into subparts or mini-goals which is much more manageable. Second, created a good breakdown of how I would separate the code into classes. Third, it helps keep me moving through the project, as I feel a since of completion as each mini-goal is each completed.

The initial break down: This paper for the most part is going to follow the break down as I did coding. Though the entire project is not complete, not even close, the project will be complete up to the main controls of the FPS camera, display, controls, and terrain. Where project 2 ends project 3 will pick-up with this same break down.

1. Create the first person camera 2. Add a 3D object 3. Move the camera with the Xbox controller 4. Add a weapon 5. Add additional controls (crouch, jump, run) 6. Work with terrain a. Play with both terrain and rooms b. Create unlevel terrain 7. Add additional objects 8. Detect object collisions - stop walking through objects 9. Explore lighting and shadows 10. Add enemy 11. Add ability to shoot 12. Detect collision with shots 13. Add enemy movement a. Not walk through objects b. Enemy chase towards change shooter 14. Add additional enemy – or generate enemy 15. Quad board to manage enemy 16. Add enemy’s ability to shoot 17. Detect if enemy shot is a hit 18. Redefine theme 19. Add score, lives, and power (multiple hits before death)

I know this is not an all inclusive list and may be longer than one semester assignment, but it gives a good initial break down of the project. Since I am not sure how much I will complete before this project is complete I will mark the end of project two and the next item will be the start of project three, if I get a partner willing to work on it. I do understand the project will not likely be completed by the end of the semester and completion will depend on available time after.


 * Setup**

The setup is identical to the setup in project one. The project will use Visual Studios 2008 C# with XNA Studios Version 3.1. It will be written to be run on the Xbox 360.


 * Create a First Person Camera**

The first person camera was a collection of three different demos. Each of the demos had something different that fit what was going to be the camera for this project. Two of the demos had the camera in the game class. The third demo did have the camera in a separate class, but was coded differently than I wanted. Using pieces of each of the demos with some modification developed the first person camera used in this project.

The first step in creating the camera was creating a class to manage most aspects of the camera. In this project the class will be called ShootersCamera.

First setup the initial variables to manage the position of the camera: We will need some variables to store the camera’s position and target which are used to create a projectionMatrix, a LookAt, and a viewMatrix (the LookAt and viewMatrix will come later). We will also need a reference Vector3 to give us the direction Vector3 - representing the direction the camera is looking.

code format="csharp" public Vector3 shooterCameraPosition; public Vector3 shooterCameraTarget; public Vector3 shooterCameraReference; code

Next, add some variables to store the view settings like the screen’s aspect ratio, near and far clipping distances, and the Field of View.

code format="csharp" public float aspectRatio; public float shooterNearPlane; public float shooterFarPlane; public float shooterFieldOfView; code

The plan is to use the right thumb stick to look around and the left thumb stick to move. Add variables to store the information. Below are the variables to store the “looking”.

code format="csharp" public float shooterCameraYaw; public float shooterCameraPitch; code

The camera created is essentially a set of stored parameters used to determine the viewable area or what should be drawn on the screen. Much of the drawing code has to be done on a per-object basis (because it is this process which applies translation, rotation and scale to the objects as well as shadow effects) but there is a part of this code that only needs to be performed once. This is the Projection Matrix, so add a variable for this too:

code format="csharp" public Matrix shooterProjection; code

Now all the main variables for the initial camera are created in the ShootersCamera class it is time to initialize the variables when the class is initialized:

code format="csharp" public ShootersCamera { code

Initialize the viewable near and far distances (see image below).

code format="csharp" shooterNearPlane = 1.0f; shooterFarPlane = 1000.0f; shooterFieldOfView = 45.0f; code

Next set the starting position for the camera and the cameraReference:

code format="csharp" shooterCameraPosition = new Vector3(0.0f, 0.0f, 0.0f); shooterCameraReference = new Vector3(0.0f, 0.0f, 1.0f); code

Create a target Vector3 for the camera to look at by adding the current cameraPosition to the cameraReference, giving a target which rests 1 unit in front of the camera’s position.

code format="csharp" shooterCameraTarget = cameraReference + cameraPosition; code

Set the Camera’s Pitch and Yaw to zero - start looking straight:

code format="csharp" shooterCameraYaw = 0.0f; shooterCameraPitch = 0.0f; code

Calculate the game viewports aspect ratio, to do this retrieve the current graphics device width and divide it by its height. This will be used later in the drawing code to make sure nothing drawn gets distorted (imagine viewing a 4:3 TV signal on a 16:9 widescreen TV, the image would stretch), so make sure the correct aspect ratio:

code format="csharp" aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width (float)graphics.GraphicsDevice.Viewport.Height; code

Since all the camera’s view variables have been initialized create the shooterProjection Matrix, this will give the viewing frustum:

code format="csharp" shooterProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(fieldOfView), aspectRatio, nearPlane, farPlane); } code

Image 1 (image thanks to xnaFusion)



Now to initialize the camera in the Initialize method in the main class. First declare the variable:

code format="csharp" ShootersCamera shootersCamera; code

Next initialize the camera.

code format="csharp" protected override void Initialize {   shootersCamera = new ShootersCamera(this, graphics); base.Initialize; } code


 * ADDING A 3D OBJECT**

The camera is fairly complete, however, unable to verify it is working unless something in drawn in front of the camera. Add a simple crate to check if the camera is displaying the correct matrix view.

Planning ahead, in case more crates are used later, make the create in its own class. Since this will start out very simple, since the crate is initially just to test the class will be:

code format="csharp" namespace Assignment2_FirstPerson { class Crate { public Model model; public Vector3 modelPosition; } } code

The final step to test the camera is to draw the object. Initally, we will add the code to the draw method, however, as more objects are added it will moved into a separate method called from the draw method.

code format="csharp" protected override void Draw(GameTime gameTime) {           GraphicsDevice.Clear(Color.CornflowerBlue);

Matrix[] transforms = new Matrix[crate.Model.Bones.Count]; crate.Model.CopyAbsoluteBoneTransformsTo(transforms); foreach (ModelMesh mesh in crate.Model.Meshes) {               foreach (BasicEffect effect in mesh.Effects) {                   effect.Projection = shootersCamera.shooterProjection; effect.View = Matrix.CreateLookAt(shootersCamera.shooterCameraPosition, shootersCamera.shooterCameraTarget, Vector3.Up); effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateTranslation(crate.ModelPosition); }               mesh.Draw; }

base.Draw(gameTime); }

code

Now an object is front of the camera:



**MOVING THE CAMERA WITH THE XBOX CONTROLLER**
Now to add movement. Adding movement will cause substanial changes in the code and will help clean up some of the previous code. Since a good portion of the ShootersCamera class is changed, it is a good idea to step through the entire class.

code format="csharp" public class ShootersCamera : GameComponent { code

One of the first changes is the variables from public to private, since only the ShooterCamera should change the variables private will work. This also included setting the intial values at time of declare the values.

code format="csharp" private Vector3 shooterCameraPosition = new Vector3(0.0f, 0.0f, 0.0f); private Vector3 shooterCameraTarget = Vector3.Zero; private Vector3 shooterCameraReference = new Vector3(0.0f, 0.0f, 1.0f); code

Four variables have been added to manage the speed of the camera’s movement. The first variable shooterCameraMove is the speed to change the position of the camera. The second variable shooterCameraRotationSpeed is the speed of the rotation of the camera. The third is the rate at which the position movement is going to accelerate. The fourth variable is the deacceleration or the rate at which the movement is going to deacceleration.

code format="csharp" private Vector3 shooterCameraMove = Vector3.Zero; private float shooterCameraRotationSpeed = 2f / 60f; private float acceleration = 0.05f; private float deacceleration = 0.02f; private float aspectRatio; private float shooterNearPlane = 1.0f; private float shooterFarPlane = 1000.0f; private float shooterFieldOfView = 45.0f; private float shooterCameraYaw = 0.0f; private float shooterCameraPitch = 0.0f; private Matrix shooterProjection; public ShootersCamera(Game game, GraphicsDeviceManager graphics)
 * base(game)

{ code

Now only the shotterCameraTarget, aspectRation and shooterProjection is set at the time of initialization.

code format="csharp" shooterCameraTarget = shooterCameraReference + shooterCameraPosition; aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width (float)graphics.GraphicsDevice.Viewport.Height;

shooterProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(shooterFieldOfView), aspectRatio, shooterNearPlane, shooterFarPlane); } code

Currently, the GameControl (main class) only uses three variables from the ShooterCamera class. Thus, create access to get the values of the variables using get accessors.

code format="csharp" public Vector3 ShooterCameraPosition {           get { return shooterCameraPosition; } }

public Vector3 ShooterCameraTarget {           get { return shooterCameraTarget; } }

public Matrix ShooterProjection {           get { return shooterProjection; } }

code

Next, add the actual movement control to the camera. The plan is to use the same controls used in most FPS games. Movement is going to be controlled by the thumb sticks. The left stick is to be used for changing of position of the camera, i.e. walking and striding. The right stick is to be used for the rotation of the camera, i.e. moving the head to look. The state of the GamePad and the gameTime is passed into the UpdateCamera method of the shootersCamera class to control the camera view. The controls will be anaylized and adjustments made with the gameTime as a scaler.

code format="csharp" public void UpdateCamera(GamePadState currentState, GameTime gameTime) { code

To allow the game to progress at a constant time (regardless of how many frames per second the game is running at), scale all the movements by the elapsed game time. The multiplier (gameTime) will be the amount of time that passed between the previous frame and the current frame. To get the multiplier use the gameTime, XNA creates, which we passed into the UpdateCamera method to get the ElapsedGameTime seconds. The seconds stored as deltaTime is the number of seconds elapsed.

code format="csharp" float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; code

Now start anaylizing the state of the thumb sticks to determine if there should be movement. Each thumb stick will be anaylized on its X and Y axis to determine the direction the stick is pushed. On each of the axises we need to determine if it is left or right for X and up or down for Y. The first set of code is to read the left stick to determine if it pushed to the left. Using the float of .30 to detect the the slightest movement, but to also make sure it is not a slightly off centered thumb stick. If the movement is greater then .30 the shooterCamera.Z plane acceleration is increased by acceleration * deltaTime. As mentioned above deltaTime is used to adjust for any frame per second discrepancies.

code format="csharp" if (currentState.ThumbSticks.Left.Y > 0.30f) { shooterCameraMove.Z += acceleration * deltaTime; } code

The next block analyzes for the left thumb stick being pushed to the right. If it detect the push the Z plane acceleration is decreased by acceleration * deltaTime. If it is negative the came moves to the right, the lower the number the faster the movement.

code format="csharp" if (currentState.ThumbSticks.Left.Y < -0.30f) { shooterCameraMove.Z -= acceleration * deltaTime; } code

The next block check the left thumb stick to be recentered, or not puched. If there is no movement and the camera is moving on the Z plane in either direction the acceleration is decreased or increased, depending on the direction, by deacceleration * deltaTime. However, if the current speed is less then deacceleration * deltaTime then the camera speed is set to 0 or stopped. Notices all changes for movement is to the cameraMove Z plane.

code format="csharp" if (currentState.ThumbSticks.Left.Y == 0.0f) {               if (shooterCameraMove.Z > 0.0f) if (shooterCameraMove.Z < deacceleration * deltaTime) shooterCameraMove.Z = 0.0f; else shooterCameraMove.Z -= deacceleration * deltaTime;

if (shooterCameraMove.Z < 0.0f) if (shooterCameraMove.Z > deacceleration * deltaTime) shooterCameraMove.Z = 0.0f; else shooterCameraMove.Z += deacceleration * deltaTime; } code

The above code completes the movement of the camera or the change in camera position. The next block makes the change in moving the camera along the X plane. The same system is used as above with except two differences. The gamepad is checked for changes on the left thumb X plane or up and down. If the change is detect the same acceration steps used above is appiled. This movement of the camera is forward and backwards.

code format="csharp" if (currentState.ThumbSticks.Left.X < -0.30f) {               shooterCameraMove.X += acceleration * deltaTime; }           if (currentState.ThumbSticks.Left.X > 0.30f) {               shooterCameraMove.X -= acceleration * deltaTime; }           if (currentState.ThumbSticks.Left.X == 0.0f) {               if (shooterCameraMove.X > 0.0f) if (shooterCameraMove.X < deacceleration * deltaTime) shooterCameraMove.X = 0.0f; else shooterCameraMove.X -= deacceleration * deltaTime;

if (shooterCameraMove.X < 0.0f) if (shooterCameraMove.X > deacceleration * deltaTime) shooterCameraMove.X = 0.0f; else shooterCameraMove.X += deacceleration * deltaTime; } code

The above code completes the movement of the camera’s position. However, it does leaves two issues to be resolved later. The first is the camera’s Y plane. The Y plane is the elevation of the camera. It is used to set the height of our camera standing, jumping, and crouching. The easiest way to think of the Y plane is the height or elevation of the camera from the ground or the Y plane’s 0 position. The movement of the Y plane will be dealt with separately in its own section.

The second issue is the speed of the camera’s position movement. Currently, there is no maximum. In otherwords, as long as the user continues to hold one direction the camera contunies to speed up. Maxiums need to be added to the accelerations. The maxiums will include a walk and run maximum speed.

Next, rotating the camera, i.e. head movement. The rotation will be compared to controlling a person’s head. If you push up on the right thumbstick the camear should look up…the same is true in all directions. To start the head movement detect if there the right thumb stick is pushed. The code checks the two axises of the thumb stick, like we did on the left thumb stick, then makes adjustments to the shooterCameraPitch and shooterCameraYaw. Back to our head analogy the shooterCameraPitch is a head looking up(+) and down(-) and the shooterCameraYaw is the head looking left(+) and right(-).

One of the main difference you will notice in the controling the head is we simpley move the pitch and yaw only when the thumb stick is pushed, there is no acceleration retained. Once the thumbstick is no longer pushed there is no movement.

The code below goes through the two axises below (highlighted alternativly to help separate):

code format="csharp" if (currentState.ThumbSticks.Right.Y < -0.30f) {               shooterCameraPitch += shooterCameraRotationSpeed; }           if (currentState.ThumbSticks.Right.Y > 0.30f) {               shooterCameraPitch -= shooterCameraRotationSpeed; }           if (currentState.ThumbSticks.Right.X < -0.30f) {               shooterCameraYaw += shooterCameraRotationSpeed; }           if (currentState.ThumbSticks.Right.X > 0.30f) {               shooterCameraYaw -= shooterCameraRotationSpeed; } code

If we used only the pitch value to look up and down a small problem occurs. Pitch does not stop, the camera would keep rolling around. Additionally, if the camera is allowed to look greater than 90 degrees up or down, the rotation and movement will behave erratically. To prevent this lock the pitch to a maximum rotation of between -89.9 and +89.9 degrees, this will ensure that a valid direction can always be obtained:

code format="csharp" shooterCameraPitch = MathHelper.Clamp(shooterCameraPitch, MathHelper.ToRadians(-89.9f), MathHelper.ToRadians(89.9f)); code

Moving the camera is relatively easy, but calculating the camera’s target is more complicated. To do so generate a Rotation Matrix, this will be based on the camera’s Pitch and Yaw:

code format="csharp" Matrix shooterCameraViewRotationMatrix = Matrix.CreateRotationX(shooterCameraPitch) * Matrix.CreateRotationY(shooterCameraYaw); code

Actually two separate rotation matrices need to be created. The first (above) will be used to rotate the view, allowing for movement in both the X and the Y axis. But if this rotation matrix was used to move the camera it would allow the camera to fly up in the air or under the ground. The movement we need is the camera to move as if walking on the ground. So a second rotation matrix is created based only on the Y axis rotation, and this will be used in the movement calculations.

code format="csharp" Matrix shooterCameraMoveRotationMatrix = Matrix.CreateRotationY(shooterCameraYaw); code

Now the rotation matrices are created set the transformedCameraReference by transforming the shooterCameraViewRotationMatrix to give the Vector3 needed:

code format="csharp" Vector3 transformedCameraReference = Vector3.Transform(shooterCameraReference, shooterCameraViewRotationMatrix); code

Adjust the camera’s position with rotation taken into account:

code format="csharp" shooterCameraPosition += Vector3.Transform(shooterCameraMove, shooterCameraMoveRotationMatrix); code

Finally, add the transformed camera reference to the camera’s position, to give a vector3 for the camera target which fully takes into account the camera’s rotation and any changes made to its position:

code format="csharp" shooterCameraTarget = transformedCameraReference + shooterCameraPosition; } } code

The above code completes the changes for the shootersCamera class. Next, how to call the methods to update the camera. First, use the Update method provided by XNA to call shootersCamera.UpdateCamera. The Update method in the main class is automatically written when starting a new XNA application. It is called any time XNA determine there has been an update to the game. In it add one line: shootersCamera.UpdateCamera(GamePad.GetState(PlayerIndex.One), gameTime); To call the UpdateCamera method in the shootersCamera class, which will cause the camera to update position bases on gamepad input.

code format="csharp" protected override void Update(GameTime gameTime) {           // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit;

shootersCamera.UpdateCamera(GamePad.GetState(PlayerIndex.One), gameTime);

base.Update(gameTime); } code

Just to quickly touch on a couple other changes. The Crate class was changed to have no public variable. All of the variables are now privates, where get accessors added if needed. Also, the model and modelPosition is set when the class is initialized.

code format="csharp" class Crate {       private Model model; private Vector3 modelPosition;

public Crate(Model Model, Vector3 ModelPosition) {           model = Model; modelPosition = ModelPosition; }

public Model Model {           get { return model; } }

public Vector3 ModelPosition {           get { return modelPosition; } }

} code

Below when the Crate class is inialized the model and position is now passed.

code format="csharp" protected override void LoadContent {           // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); crate = new Crate(Content.Load(@"Models\crate"), new Vector3(0.0f, -0.5f, 5.0f)); } code

Next, drawing the crate was removed from the XNA automatically created Draw method. It was removed because as more objects are added the Draw method it could get too long and unreadable. So, each object will be drawn in its own method, which will be called from the XNA Draw.

code format="csharp" protected override void Draw(GameTime gameTime) {           GraphicsDevice.Clear(Color.CornflowerBlue); DrawCrate; base.Draw(gameTime); }

code

Below is a look at the newly created DrawCrate method. Note it has been adjusted with the shootersCamera public get accessor calls:

code format="csharp" private void DrawCrate {           Matrix[] transforms = new Matrix[crate.Model.Bones.Count]; crate.Model.CopyAbsoluteBoneTransformsTo(transforms); foreach (ModelMesh mesh in crate.Model.Meshes) {               foreach (BasicEffect effect in mesh.Effects) {                   effect.Projection = shootersCamera.ShooterProjection; effect.View = Matrix.CreateLookAt(shootersCamera.ShooterCameraPosition, shootersCamera.ShooterCameraTarget, Vector3.Up); effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateTranslation(crate.ModelPosition); }               mesh.Draw; }       } code

The image below now allows the user to walk around the box and look in any direction. In the image below the user is standing by the corner of the box looking down. Since the eye level is currently set to the same level of the box looking down at the box causes the appears of looking under the box or moving the box up on the screen.




 * ADD A WEAPON**

Initially I had a a lot of trouble figuring out how to add a weapon, to a first person shooter. This was substanially different than any other type of model control I have done before. It took a lot of testing and a lot of time to get the weapon to work as I expected. A great resource was dopware.com and inversereality.org for help in understanding the weapon attachment, partically the weapon’s world matrix.

It took a much better understanding of start points, than I had. As better understand of martix, which now I know whenever you want to draw triangles (from a vertex buffer or from a mesh), you have to set the World transform first. If you wouldn’t, all triangles would be drawn relative to the origin, the (0,0,0) of the game. Instead with a world matrix the drawing is relative to the matrix. With every object priorly drawn relative to the origin worked, however, with the gun it will not. It will be drawn to a position of 0,0,0 based on the camera’s world matrix and maintaining the camera’s world matrix with an off set. This is stored in the weaponWorldMatrix.

Creating the weapon. Keeping in line with all of the other object the weapon is create with its own class, the weapon class.

code format="csharp" namespace Assignment2_FirstPerson { class Weapon { private Model weaponModel; code

The first four lines below is the scale and offset of the weapon in the weaponWorldMatrix from the camera’s matrix.. The offset and scale will vary based on the model loaded.

code format="csharp" private const float WEAPON_SCALE = 0.025f; private const float WEAPON_X_OFFSET = 0.35f; private const float WEAPON_Y_OFFSET = -0.80f; private const float WEAPON_Z_OFFSET = 1.85f; code

Create the weaponWorldMatrix to track the rotation and movement in reference to the camera’s world matrix.

code format="csharp" private Matrix weaponWorldMatrix; code

The transforms Matrix is going to manage the drawing the bones of the weapon model.

code format="csharp" public Matrix[] weaponTransforms; code

Initialize the weapon class by passing the model and shooterCameraPosition. In the initializing of the clss store the model, building the weapon transforms Matrix based on the model bones, and initialize the weaponWorldMatrix.

code format="csharp" public Weapon(Model Model, Vector3 shooterCameraPosition) { weaponModel = Model; weaponTransforms = new Matrix[Model.Bones.Count]; weaponWorldMatrix = Matrix.Identity; } code

The model and weaponWorldMatrix are going to be used to draw the weapon in the GameControl class, so we need to make the accessable. Since, they are only changed in the weapon class only use the get accessors.

code format="csharp" public Model WeaponModel { get { return weaponModel; } } public Matrix WeaponWorldMatrix { get { return weaponWorldMatrix; } } code

It gets a bit more complicated here as the matrix is build. The parameters required: First, the camera position will be used to get the initial weapon’s position. Once the initial vector3 we need to add in the offsets based on the current viewing direction and the Y and X axis of the camera. Finally, create\update the weaponWorldMatrix using the pitch and heading converted to degrees 360 and 90. The method to get the pitch and yaw in degrees is explored below in the changes to the ShootersCamera class.

code format="csharp" public void UpdateWeaponWorldMatrix(Vector3 shooterCameraPosition, Vector3 viewDir, Vector3 yAxis, Vector3 xAxis, float PitchDegrees, float HeadingDegrees) {

weaponModel.CopyAbsoluteBoneTransformsTo(weaponTransforms);

Vector3 weaponPos = shooterCameraPosition;

weaponPos += viewDir * WEAPON_Z_OFFSET; weaponPos += yAxis * WEAPON_Y_OFFSET; weaponPos += xAxis * WEAPON_X_OFFSET;

weaponWorldMatrix = Matrix.CreateScale(WEAPON_SCALE) * Matrix.CreateRotationX(MathHelper.ToRadians(PitchDegrees)) * Matrix.CreateRotationY(MathHelper.ToRadians(HeadingDegrees)) * Matrix.CreateTranslation(weaponPos); }

} } code

Now the creation of the weapon class is completed. Back in the GameControl class declare the weapon object. private Weapon weapon; Next initialize the weapon, passing the model.

code format="csharp" protected override void LoadContent {           // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); crate = new Crate(Content.Load(@"Models\crate"), new Vector3(0.0f, -0.5f, 5.0f)); weapon = new Weapon(Content.Load(@"Models\weapon")); spriteFont = Content.Load(@"Fonts\DemoFont"); } code

Create the DrawWeapon (remember we are moving the draw object to their own methods). The effects.View of the weapons model will use the shootersCamera.ViewMatrix.

code format="csharp" private void DrawWeapon {           foreach (ModelMesh m in weapon.WeaponModel.Meshes) {               foreach (BasicEffect e in m.Effects) {                   e.TextureEnabled = true; e.EnableDefaultLighting; e.World = weapon.weaponTransforms[m.ParentBone.Index] * weapon.WeaponWorldMatrix; e.View = shootersCamera.ViewMatrix; e.Projection = shootersCamera.ShooterProjection; }

m.Draw; }       }

code

Call the DrawWeapon from the Draw method provide by XNA:

code format="csharp" protected override void Draw(GameTime gameTime) {           GraphicsDevice.Clear(Color.CornflowerBlue); DrawCrate; DrawWeapon; DrawText; base.Draw(gameTime); }

code

Finally, update the weaponWorldMatrix each time the camera is updated.

code format="csharp" protected override void Update(GameTime gameTime) {           // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit;

shootersCamera.UpdateCamera(GamePad.GetState(PlayerIndex.One), gameTime); weapon.UpdateWeaponWorldMatrix(shootersCamera.ShooterCameraPosition, shootersCamera.ViewDirection,shootersCamera.YAxis,shootersCamera.XAxis,shootersCamera.PitchDegrees,shootersCamera.HeadingDegrees);

base.Update(gameTime); }

code

Next, make the required changes in the shooterCamera. The changes are actually mainly to pull from the camera’s view to build the weaponWorldMatrix. The first thing, get the pitch and yaw turned into percentages based on +-360 (yaw) and +-90 (pitch).

code format="csharp" GetRotationDegrees(((shooterCameraYaw - shooterCameraYawPrior) * 189.4736842105263f), ((shooterCameraPitch - shooterCameraPitchPrior) * 191.4893617021277f)); shooterCameraYawPrior = shooterCameraYaw; shooterCameraPitchPrior = shooterCameraPitch;

code

The final values needed from the shootersCamera for the weaponWorldMatrix need new variables. The next block of code below creates six new variables to store data need from the shootersCamera for the weaponWorldMatrix. The top four are Vector3s, however, with a specified plane. The Vector3s will be used to multiple one plane of the weapons position vector with the offset to create the weaponWorldMatrix. The final portion of the code block below initializes the variables and provides get accessors to access them.

code format="csharp" viewDir = Vector3.Forward; xAxis = Vector3.UnitX; yAxis = Vector3.UnitY; zAxis = Vector3.UnitZ; viewMatrix = Matrix.Identity; orientation = Quaternion.Identity; }

public float PitchDegrees {           get { return -accumPitchDegrees; } }

public float HeadingDegrees {           get { return -accumHeadingDegrees; } }

public Vector3 ViewDirection {           get { return viewDir; } }

public Vector3 XAxis {           get { return xAxis; } }

public Vector3 YAxis {           get { return yAxis; } }

public Vector3 ZAxis {           get { return zAxis; } }

public Matrix ViewMatrix {           get { return viewMatrix; } } code

The weaponWorldMatrix will use a viewMatrix of the camera, so create\upate the viewMatrix.

code format="csharp" private void UpdateViewMatrix {           Matrix.CreateFromQuaternion(ref orientation, out viewMatrix);

xAxis.X = viewMatrix.M11; xAxis.Y = viewMatrix.M21; xAxis.Z = viewMatrix.M31;

yAxis.X = viewMatrix.M12; yAxis.Y = viewMatrix.M22; yAxis.Z = viewMatrix.M32;

zAxis.X = viewMatrix.M13; zAxis.Y = viewMatrix.M23; zAxis.Z = viewMatrix.M33;

viewMatrix.M41 = -Vector3.Dot(xAxis, shooterCameraPosition); viewMatrix.M42 = -Vector3.Dot(yAxis, shooterCameraPosition); viewMatrix.M43 = -Vector3.Dot(zAxis, shooterCameraPosition);

viewDir.X = -zAxis.X;           viewDir.Y = -zAxis.Y;            viewDir.Z = -zAxis.Z;        }

code

To set the rest of the varialbes create a LookAt method to determine each of varialbes based on the current camera position and camera target.

code format="csharp" public void LookAt {

zAxis = shooterCameraPosition - shooterCameraTarget; zAxis.Normalize;

viewDir.X = -zAxis.X;           viewDir.Y = -zAxis.Y;            viewDir.Z = -zAxis.Z;

Vector3.Cross(ref yAxis, ref zAxis, out xAxis); xAxis.Normalize;

Vector3.Cross(ref zAxis, ref xAxis, out yAxis); yAxis.Normalize; xAxis.Normalize;

viewMatrix.M11 = xAxis.X;           viewMatrix.M21 = xAxis.Y;            viewMatrix.M31 = xAxis.Z;            Vector3.Dot(ref xAxis, ref shooterCameraPosition, out viewMatrix.M41); viewMatrix.M41 = -viewMatrix.M41;

viewMatrix.M12 = yAxis.X;           viewMatrix.M22 = yAxis.Y;            viewMatrix.M32 = yAxis.Z;            Vector3.Dot(ref yAxis, ref shooterCameraPosition, out viewMatrix.M42); viewMatrix.M42 = -viewMatrix.M42;

viewMatrix.M13 = zAxis.X;           viewMatrix.M23 = zAxis.Y;            viewMatrix.M33 = zAxis.Z;            Vector3.Dot(ref zAxis, ref shooterCameraPosition, out viewMatrix.M43); viewMatrix.M43 = -viewMatrix.M43;

viewMatrix.M14 = 0.0f; viewMatrix.M24 = 0.0f; viewMatrix.M34 = 0.0f; viewMatrix.M44 = 1.0f;

accumPitchDegrees = MathHelper.ToDegrees((float)Math.Asin(viewMatrix.M23)); accumHeadingDegrees = MathHelper.ToDegrees((float)Math.Atan2(viewMatrix.M13, viewMatrix.M33));

Quaternion.CreateFromRotationMatrix(ref viewMatrix, out orientation); }

code

Finally, get the pitch and yaw in degrees.

code format="csharp" public void GetRotationDegrees(float headingDegrees, float pitchDegrees) {           headingDegrees *= rotationSpeed; pitchDegrees *= rotationSpeed;

headingDegrees = -headingDegrees;

accumPitchDegrees += pitchDegrees;

if (accumPitchDegrees > 90.0f) {               pitchDegrees = 90.0f - (accumPitchDegrees - pitchDegrees); accumPitchDegrees = 90.0f; }

if (accumPitchDegrees < -90.0f) {               pitchDegrees = -90.0f - (accumPitchDegrees - pitchDegrees); accumPitchDegrees = -90.0f; }

accumHeadingDegrees += headingDegrees;

if (accumHeadingDegrees > 360.0f) accumHeadingDegrees -= 360.0f;

if (accumHeadingDegrees < -360.0f) accumHeadingDegrees += 360.0f;

float heading = MathHelper.ToRadians(headingDegrees); float pitch = MathHelper.ToRadians(pitchDegrees); Quaternion rotation = Quaternion.Identity;

// Rotate the camera about the world Y axis. if (heading != 0.0f) {               Quaternion.CreateFromAxisAngle(ref WORLD_Y_AXIS, heading, out rotation); Quaternion.Concatenate(ref rotation, ref orientation, out orientation); }

// Rotate the camera about its local X axis. if (pitch != 0.0f) {               Quaternion.CreateFromAxisAngle(ref WORLD_X_AXIS, pitch, out rotation); Quaternion.Concatenate(ref orientation, ref rotation, out orientation); }

UpdateViewMatrix; } code

Below is the game rendered on the Xbox. The weapon is build and off set from the camera. Notice the text at the top of screen, it was put in to test.



The two final changes made to the current code is adding terrain and the ShootersCameras posture. The code below adds the items, but they are not fully complete. The terrain is added the same as most of the other objects drawn on the world matrix, but is 2d textures, a height bitmap, and effects. The textures control the texture of the map, the textures can be “paint” four shades. The height bitmap is a 64×64 grey scale bitmgap where the white intensity determines the height. Finally, the effects, which is is optional, contains all of the effects such as lighting in a single file. In the images below you will see lighting on the gun, which was added with the terrain.

Now the terrain is not completed, I will just slight touch on the topic. It will be picked up in project 3. There still needs to be a lot of work in th terrain, such as collisions. Currenty, the FPS camera will walk through the hills in the terrain not on them. Also, I need to flatten the texture a bit and build borders.


 * Adding Terrain**

code format="csharp" using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input;

namespace Assignment2_FirstPerson {   class Terrain {

private VertexPositionNormalTexture[] vertices; private VertexDeclaration terrainVertexDeclaration; private VertexBuffer terrainVertexBuffer; private IndexBuffer terrainIndexBuffer;

private int[] indices; private int terrainX; private int terrainZ; private float[,] terrainY;

private Texture2D grasTexture; private Texture2D heightData; public Effect terrainEffect;

public Terrain(Texture2D GrasTexture, Effect TerrainEffect, Texture2D HeightData, GraphicsDeviceManager graphics) {           this.grasTexture = GrasTexture; this.terrainEffect = TerrainEffect; this.heightData = HeightData; device = graphics.GraphicsDevice; LoadTerrainY(heightData); SetUpVertices; SetUpIndices; CalculateNormals; CopyToBuffers; }

public GraphicsDevice terrainGraphics {           get { return device; } }

public int TerrainX {           get { return terrainX; } }

public int TerrainZ {           get { return terrainZ; } }

public Texture2D GrassTexture {           get { return grasTexture; } }

public VertexDeclaration TerrainVertexDeclaration {           get { return terrainVertexDeclaration; } }

public IndexBuffer TerrainIndexBuffer {           get { return terrainIndexBuffer; } }

public VertexBuffer TerrainVertexBuffer {           get { return terrainVertexBuffer; } }

public VertexPositionNormalTexture[] Vertices {           get { return vertices; } }

public int[] Indices {           get { return indices; } }

private void LoadTerrainY(Texture2D heightData) {           terrainX = heightData.Width; terrainZ = heightData.Height; terrainY = new float[terrainX, terrainZ];

Color[] heightDataColors = new Color[terrainX * terrainZ]; heightData.GetData(heightDataColors);

for (int x = 0; x < terrainX; x++) {               for (int z = 0; z < terrainZ; z++) {                   terrainY[x, z] = heightDataColors[x + z * terrainX].R / 4.0f; }           }        }

private void SetUpVertices {           vertices = new VertexPositionNormalTexture[terrainX * terrainZ]; float minHeight = float.MaxValue; float maxHeight = float.MinValue;

for (int x = 0; x < terrainX; x++) {               for (int z = 0; z < terrainZ; z++) {                   if (terrainY[x, z] < minHeight) minHeight = terrainY[x, z]; if (terrainY[x, z] > maxHeight) maxHeight = terrainY[x, z]; }           }

for (int x = 0; x < terrainX; x++) {               for (int z = 0; z < terrainZ; z++) {                   vertices[x + z * terrainX].Position = new Vector3(x, terrainY[x,z], -z); vertices[x + z * terrainX].TextureCoordinate.X = (float)x / 30.0f; vertices[x + z * terrainX].TextureCoordinate.Y = (float)z / 30.0f;

}           }

terrainVertexDeclaration = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements); }

private void SetUpIndices {           indices = new int[(terrainX - 1) * (terrainZ - 1) * 6]; int counter = 0; for (int z = 0; z < terrainZ-1; z++) {               for (int x = 0; x < terrainX - 1; x++) {                   int lowerLeft = x + z * terrainX; int lowerRight = (x + 1) + z * terrainX; int topLeft = x + (z + 1) * terrainX; int topRight = (x + 1) + (z + 1) * terrainX;

indices[counter++] = topLeft; indices[counter++] = lowerRight; indices[counter++] = lowerLeft; indices[counter++] = topLeft; indices[counter++] = topRight; indices[counter++] = lowerRight; }           }        }

private void CopyToBuffers {           terrainVertexBuffer = new VertexBuffer(device, vertices.Length * VertexPositionNormalTexture.SizeInBytes, BufferUsage.WriteOnly); terrainVertexBuffer.SetData(vertices); terrainIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); terrainIndexBuffer.SetData(indices); }

private void CalculateNormals {           for (int i = 0; i < vertices.Length; i++) vertices[i].Normal = Vector3.Zero;

for (int i = 0; i < indices.Length / 3; i++) {               int index1 = indices[i * 3]; int index2 = indices[i * 3 + 1]; int index3 = indices[i * 3 + 2];

Vector3 side1 = vertices[index1].Position - vertices[index3].Position; Vector3 side2 = vertices[index1].Position - vertices[index2].Position; Vector3 normal = Vector3.Cross(side1, side2);

vertices[index1].Normal += normal; vertices[index2].Normal += normal; vertices[index3].Normal += normal; }

for (int i = 0; i < vertices.Length; i++) vertices[i].Normal.Normalize; }   }

} code

Create the DrawTerrain in the GameControl class to update the terrain.

code format="csharp" Create the DrawTerrain in the GameControl class to update the terrain. private void DrawTerrain {

terrain.terrainEffect.CurrentTechnique = terrain.terrainEffect.Techniques["Textured"]; terrain.terrainEffect.Parameters["xView"].SetValue(shootersCamera.ViewMatrix); terrain.terrainEffect.Parameters["xProjection"].SetValue(shootersCamera.ShooterProjection); Matrix terrainWorldMatrix = Matrix.CreateTranslation(-terrain.TerrainX / 2.0f, 0, terrain.TerrainZ / 2.0f); terrain.terrainEffect.Parameters["xWorld"].SetValue(terrainWorldMatrix); terrain.terrainEffect.Parameters["xTexture"].SetValue(terrain.GrassTexture); terrain.terrainEffect.Parameters["xEnableLighting"].SetValue(true); Vector3 lightDirection = new Vector3(-0.5f, -1.0f, -0.5f); lightDirection.Normalize; terrain.terrainEffect.Parameters["xLightDirection"].SetValue(lightDirection); terrain.terrainEffect.Parameters["xAmbient"].SetValue(0.2f);

terrain.terrainEffect.Begin; foreach (EffectPass pass in terrain.terrainEffect.CurrentTechnique.Passes) {               pass.Begin; gDevices.VertexDeclaration = terrain.TerrainVertexDeclaration; gDevices.Indices = terrain.TerrainIndexBuffer; gDevices.Vertices[0].SetSource(terrain.TerrainVertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes); gDevices.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, terrain.Vertices.Length, 0, terrain.Indices.Length / 3); pass.End; }           terrain.terrainEffect.End;

}   } code

Below is an image of the gun, terrain, and crate. Notice the the gun is in a standing posture, it is higher from the ground than the second image where the user is crouching.



In the same location, but the user (me) is holding the A button on the gamepad, causing this user to crouch. Also notice the lighting and shading on the weapon.



In project three the game will pickup from this point. The first part will be completing the terrain. Then adding other objects, enemies, and the ability to shoot. At which time the game should start to be complete.