The Holodeck Part V

This isn't much of a virtual reality program if you can't move around the environment and look around. So now let's address that and create a "camera" class to allow us to do that.

In the previous parts, I added some rudimentary "camera" code to allow movement forward and backward with keyboard keys. One of the reasons that I kept that simple is that I knew it was going to take quite a bit of code to do it right.

Notice that a few lines of code created a "camera" in the earlier parts. Keep that in mind; just to create a camera doesn't require a lot of code. The mountains of code for cameras come in when you want the camera to act a specific way and respond to controls and events.

I think the tendency, when you're starting out, is to think that the mountains of code are the "camera", but really the "camera" is the game's View matrix and nothing more. Big camera classes just have code that manipulate the View matrix in a way that's fits the game.

Source Code

Modifying the Main class

I created the whole project over as a new project, copying and pasting the existing code into a new project. I gave the Game1.cs file that we renamed Holodeck.cs a completely new name "BuildCharacter.cs". You could probably just rename the main cs file. Notice this changes the namespace to BuildCharacter and all the cs files need to use that same namespace in order to see oneanother. Again, BuildCharacter.cs replaces the Game1.cs file that XNA automatically generates for you.

If you create a new project, don't forget to put the art files such as the room and chair .X models and all the texture files into the project's Content folder like before. Then you still need to add them to the project's Content after that by right clicking on Content in Solution Explorer.

Ok. So first we need to create an instance of our class. This is maybe a little backwards since we haven't created the new class yet, but we'll do that shortly.

Add the following code to the fields of the Game1 class:

PlayerCharacter Character;

namespace BuildingCharacter
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager GraphicsManager;  //Rename this like so.
        GraphicsDevice GraphicsCard;            //You can basically think of this as your graphics card or screen.
        SpriteBatch spriteBatch;                //You can leave this in even though we could remove it from this program.

        Texture2D GridTexture;  //Stores our texture in memory.
        Model HoloDeckModel;    //Stores our model in memory.
        Matrix HoloDeckModelWorldMatrix = Matrix.Identity;    //Stores the position and sizing info for our model.
        Model Chair;     //Stores the model named Chair.
        Matrix ChairsWorldMatrix;    //Stores Chair's position, facing and scaling.
        Matrix Projection;  //Think of this as the "lens system" for our camera.
        PlayerCharacter Character;
        const float HoloDeckWidth = 40f;    //Our model is 2 units cubed. This will be the number to multiply that size by.

        public Game1()
        {
      

After that, we still need to create an instance of the object we just declared:

Character = new PlayerCharacter(new Vector3(0f, 0f, 0f), new Vector2(0f, -1f));

This code replaces the existing camera code which I've commented out in the Initialize method.

The Vector3 parameter is the position where the user will start out at and the Vector2 is a normalized unit vector that points in the direction that the character will be facing when the game starts.

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            GraphicsCard = GraphicsManager.GraphicsDevice;  //Introduce the Graphics Manager to our graphics card.
            Window.Title = "The Holo-Deck"; //Your window's title displays in the top left corner in windowed mode.

            //We need to load up our projection matrix with our camera's Field of View,
            //our screen aspect ratio, and the clipping planes. Changes this are only
            //updated when you change the aspect ratio, I believe. So it's pretty much
            //set it up once and leave it alone.
            Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(40f),
                GraphicsCard.Adapter.CurrentDisplayMode.AspectRatio, 0.1f, 10000f);

            //CameraPostion = new Vector3(0f, 1.8f, 0f);    //Start the camera in the center of our world but 1.8 unit above ground.
            //CameraLookAt = CameraPostion + Vector3.Forward;
            //Think of the View Matrix as our camera. I'm loading it with our 3D position. And 
            //then I'm telling it to look straight forward by telling it to look at a point
            //that is 1 unit directly in front of the camera. Vector3.Forward is a unit
            //vector of 1 length that points in the forward direction, but it's tail
            //is at 0,0,0 and we need it to be pointing "from" the camera. Instead of
            //defining it this way you can just load up a Vector3 with the position
            //you want it to look at just like we did with Camera position.
            //CreateLookAt wants you to define "up" for it and so you send it an up vector.
            //View = Matrix.CreateLookAt(CameraPostion, CameraLookAt, Vector3.Up);
            Character = new PlayerCharacter(new Vector3(0f, 0f, 0f), new Vector2(0f, -1f));


            base.Initialize();
        }

The Draw method must be changed to use the Character object. Any reference to the game's View matrix should be handled by the Character object's matrix. In the Draw code for both the room/cube and the chair, you need to set the view up like this:

Shader.View = Character.View;

This gives the Character object control over the camera. Make sure you change it in both places of the Draw method.

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            // TODO: Add your drawing code here
            //Normally you don't HAVE to change the RasterizerState but we want to change the 
            //CullMode. The rasterizer is part of the screen drawing mechanism and we're telling
            //XNA how things should be drawn. The cull mode tries to cull out triangles that don't
            //"need" to be drawn. The default is CullCounterClockwiseFace which tells XNA not to 
            //draw the back sides of triangles. Most of the time this makes sense; you can't "see"
            //the triangles that are facing away from you, so why draw them; it's just wasting CPU
            //time. It decides which side is the front of the triangle by the cull mode and the
            //direction you "wind", or define, the vertices in. This is why you should always 
            //wind your vertices clockwise according to the direction you want them to face.
            //Since we're debugging, there's a good chance that we messed up and wound our
            //vertices in the wrong direction. So, we turn off culling and draw both sides of 
            //all triangles by setting the cull mode to None. Once we get the program working,
            //we can change CullMode back to CullCounterClockwiseFace and our game will run
            //faster.
            RasterizerState RS = new RasterizerState();
            RS.CullMode = CullMode.None;
            RS.FillMode = FillMode.Solid;
            GraphicsCard.RasterizerState = RS;


            foreach (ModelMesh Mesh in HoloDeckModel.Meshes)
            {
                //Here we define an effect. The effect is kind of like the pen or brush that we will "draw" our
                //3D world with. It's basically what they call a "shader". Shaders are code that draw things on
                //the screen. XNA 4.0 has about 5 different effects, or shaders, built into it. BasicEffect is the one
                //you will use most of the time. The others are more for special purposes.
                //We're loading up a BasicEffect here so that we can use it. We could have loaded up one of the other
                //effects or written our own effect and loaded it up. Writing your own effects is done in a language
                //called HLSL (High Level Shader Language). You don't really need to do that starting out though;
                //because BasicEffect does a pretty good job and you really need to know what you're doing to write a
                //shader that does a better job drawing the screen than the built in effects.
                foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with.
                {
                    Shader.EnableDefaultLighting();
                    Shader.TextureEnabled = true;   //We're going to draw using a texture.
                    Shader.Texture = GridTexture;   //The model will draw without this call, but it will use the texture in the file.
                    Shader.LightingEnabled = false; //Don't use BasicEffect's "advanced" lighting for now.
                    Shader.World = HoloDeckModelWorldMatrix;    //Tell the shader what World Matrix to use to draw the object.
                    Shader.View = Character.View;             //Tell the shader where to draw from (the camera).
                    Shader.Projection = Projection; //Tell the shader how to draw (like the camera lens... sort of).
                }
                Mesh.Draw();  //Draw the mesh
            }


            foreach (ModelMesh Mesh in Chair.Meshes)
            {
                foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with.
                {
                    Shader.EnableDefaultLighting();
                    Shader.TextureEnabled = true;   //We're going to draw using a texture.
                    Shader.World = ChairsWorldMatrix;    //Tell the shader what World Matrix to use to draw the object.
                    Shader.View = Character.View;             //Tell the shader where to draw from (the camera).
                    Shader.Projection = Projection; //Tell the shader how to draw (like the camera lens... sort of).
                }
                Mesh.Draw();  //Draw the mesh
            }


            base.Draw(gameTime);    //Always include this.
        }

And we need to remove the code from the Update method and completly replace it with this code:

 protected override void Update(GameTime gameTime)
        {
            KeyboardState KBState;
            Vector3 RequestedMovement = Vector3.Zero;

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here
            KBState = Keyboard.GetState();
            if (KBState.IsKeyDown(Keys.Escape)) this.Exit();

            Character.State = PlayerCharacter.States.Standing;


            if (KBState.IsKeyDown(Keys.LeftShift) || KBState.IsKeyDown(Keys.RightShift))
            {
                if (KBState.IsKeyDown(Keys.D) || KBState.IsKeyDown(Keys.Right))
                {
                    Character.State = PlayerCharacter.States.StrafingRight;
                }

                if (KBState.IsKeyDown(Keys.A) || KBState.IsKeyDown(Keys.Left))
                {
                    Character.State = PlayerCharacter.States.StrafingLeft;
                }
            }
            else
            {
                if (KBState.IsKeyDown(Keys.Up) || KBState.IsKeyDown(Keys.W))
                {
                    Character.State = PlayerCharacter.States.Walking;
                }

                if (KBState.IsKeyDown(Keys.Down) || KBState.IsKeyDown(Keys.S))
                {
                    Character.State = PlayerCharacter.States.Backing;
                }

                if (KBState.IsKeyDown(Keys.Left) || KBState.IsKeyDown(Keys.A))
                {
                    Character.TurnLeft(gameTime);
                }

                if (KBState.IsKeyDown(Keys.Right) || KBState.IsKeyDown(Keys.D))
                {
                    Character.TurnRight(gameTime);
                }

                if (KBState.IsKeyDown(Keys.PageUp) || KBState.IsKeyDown(Keys.E))
                {
                    Character.LookUp(gameTime);
                }

                if (KBState.IsKeyDown(Keys.PageDown) || KBState.IsKeyDown(Keys.Q))
                {
                    Character.LookDown(gameTime);
                }
            }
            
            

            Character.Update(gameTime, ref RequestedMovement);
            Character.IsAt += RequestedMovement;

            base.Update(gameTime);
        }

So let's look at the code in the new Update method. First, we've added the RequestedMovement 3D vector to hold the position that the player is trying to move to. The whole idea here is not to simply let the player move wherever they want but to have them "ask" to move somewhere and let the game decide what should happen when a player tries to move there. For example, if a player tries to move through a wall, the game can not only deny the request but also take into consideration where the player is currently positioned at and the distance to the wall. Then the game can instead move the player right up to the wall, but not through it. It also allows the game to consider whether the player's movement causes collision with objects and how to respond to that before actually moving the player. The vector is initialized to zero to make it easier to debug.

Vector3 RequestedMovement = Vector3.Zero;

The KBState field will store any input from the keyboard.

Now, I've chosen to have our "camera" represent the player's character rather than a "camera". I mean, generic cameras are fine and perfect for a CAD program or modeling program, but we want the camera here to be the player's "eyes" into the world. So I decided to represent it that way as a class.

As part of that, I decided to have several "states" that the player can be in that cause the "camera" to act differently. That's what this line of code is all about:

Character.State = PlayerCharacter.States.Standing;

There, I'm setting the player to a "standing still" state. This state just represents the camera staying in one position, but allows the player to turn and look around.

The next line of code below is saying that the code will be executed if the shift key is held down.  The whole point of this code is to change the behavior of the left and right movement keys to strafe instead of turning. There's probably a better way to handle this, but it would probably involve abandoning the player state idea and handling all of this very differently because the states don't allow for forward movement and strafing at the same time. But this isn't really about the "best" method, but just giving you an idea of what can be done.

if (KBState.IsKeyDown(Keys.LeftShift) || KBState.IsKeyDown(Keys.RightShift))

Depending on which keyboard buttons are pressed, you can set the state of the player character object to different states such as StrafingLeft, Walking, or Backing. Turning right and looking down are examples of direct calls to the player character object to just do something, as opposed to doing it through a state. One of the big differences is that the states do something extra, such as play a walking sound effect.

We call the player character object to update itself based on the keyboard commands we've given it.

Character.Update(gameTime, ref RequestedMovement);

That line passes the RequestedMovement position vector in by reference. Passing by reference will modify the actual field. Hopefully, you know enough C# to know what that means, but basically it means that it's really there as an "output" field. In other words, after this method is called RequestedMovement will have the position that the update caused the character object to "want" to move to.

So then we have a new position in RequestedMovement that the character object is trying to move to and we can react to that. For now, we're just simply going to grant the request without any consideration with the following code:

Character.IsAt += RequestedMovement;

Notice that RequestedMovement is really an "offset" from the character's current position such as "3 units forward" rather than an absolute position. Being a vector, it's essentially what direction and how far the player is trying to move during this frame. If it passes through a wall, we can keep the direction the same, but just make the distance shorter allowing us to move the player to the wall but not through it. But for now, we're allowing the player's request to be granted even if it moves the player through an object. That's why everything in this virtual reality will be "ethereal" like ghost objects for now. We'll address that problem in Part VI.

Once you get the code for this lesson actually running, walk to a corner of the room and walk through it and turn around. You'll walk out into "empty space" and be able to turn around and look back at the holodeck/room/cube from the outside. This is actually kind of bad since we've really got nothing out here for the player to see or do, not to mention that walls should be "solid".

At the end of the update we leave this line of code that was generated by XNA:

base.Update(gameTime);

That's mainly there to call game components which we aren't using at this point, but it's best to just leave it in the code. In a full sized game we "should" be using game components.

Creating the Player Character Class

Instead of creating a Game Component class, we'll create just a regular C# class to represent the user.

In Solution Explorer, right click on the project name "Holodeck". Select "Add" and "New Item" or you can use the hot keys "Controll+Shift+A".

Create New Class

You should get the "Add New Item" dialog box where you can select "Class" and rename the class as "Character.cs" in the "Name:" edit box. Then click on the "Add" button to add the new class to this project.

Add Character Class

There is a tremendous amount of code in this Character.cs class. You may want to just go to the "Complete Source Code for Character.cs" and paste all that code in at once or download the whole file and add it in as an existing item.

However you do it, the namespace name in Character.cs needs to match the namespace used in your main .cs file which was originally named Game1.cs, but I think we've renamed it to Holodeck.cs at this point. If the namespaces don't have the same name you'll get errors because parts of the program won't be able to see other parts of the program.

This code defines the PlayerCharacter class which is instantiated in the main program as the Character object. The idea is to represent the character that the user is controlling. In this simple simulation it's not much of a character, but you might have something similar in a more complex simulation or game.

The first declaration defines the character's height as 1.75 unit. 1.75 meters is basically the average height of a man.

private float Height = 1.75f;

The next line defines the character's "eye level". The camera shouldn't be viewing from on top of the character's head. The camera is supposed to represent the character's eyes. So, it should be at eye level. Top of head level is more for collision information and that sort of thing.

private float EyeLevel = 1.64f;

Then we define a starting position for the character in the 3D world. Starting out, we just initialize it to be right at the origin. Note that this is the position of the bottom of the character's feet.

private Vector3 Position = new Vector3(0f, 0f, 0f);

And we'll use a 2D vector the way vectors should be used: as a direction. The direction it holds is the direction the player is facing. Keeping this in 2D makes sense since all movement will be along the X,Z plane where Y is an altitude that can be maintained seperately. And in this particular simulation, we're not going to let the player leave the ground, and so Y will always be exactly zero, making it even less important.

private Vector2 FaceTowards = new Vector2(0f, -1f);

Then we declare floats for the angle or pitch, yaw, and maximum pitch for the head. This will store which direction the character's face is pointing, giving them the ability to look up and look down without moving from their spot. In other words, the user can move the character's head around and look in different directions without moving.

Every game object that is drawn on the screen must have it's own world matrix. The player, however, is represented by the camera and that's the View Matrix. The View Matrix represents the user's eyes and is the position that everything will be drawn from.

private Matrix CharactersViewMatrix;

We initialize the WalkingVelocity = 2.7 which is faster than real world walking speeds of 1 to 1.5 meters per second. For whatever reason, 1 to 1.5 seems too slow. Maybe it's because it's a game and not real life and you don't want to spend all your time walking. Regardless, I've set it to 2.7 but you can change it to whatever you like.

StrafingVelocity is for stepping sideways and is slower because of that.

TurnRate is the amount to turn each frame, measured in radians, when the user is telling the character to turn around.

And for this simulation, we are going to define the following states:

public enum States{Standing, Walking, Backing, StrafingLeft, StrafingRight, Running, Sitting, Lying}

This enumeration allows the character to be in several different possible states which the user will be able to select. Standing means not moving at all. Some of these won't be defined in this tutorial such as Running, Sitting, and Lying down. I largely included them with the plans of eventually using them.

The Constructor

The constructor for this class accepts two parameters. The first parameter is a 3D vector. Well, actually it's not. It's a point, or coordinate, stored in a 3D vector. It represents the place where the player will start the simulation at. This allows the calling program to define the starting position.

The second position is a 2D vector. Now that is an actual vector. It's a direction and an amount. Well, actually the amount is 1 because it "should" be a unit vector; however, this is not enforced. But, FacingTowards should be loaded with a Normalized 2D vector that defines the direction the player will be facing when starting the simulation. Again, the initial facing is defined by the calling program.

When this object is initialized. The player will begin in the "standing still" state.

SetCharactersView() is called in order to update the View matrix for any changes caused by the setting of Position and facing. The method returns the View matrix, but we don't need it here; so there's no point in getting it.

public PlayerCharacter(Vector3 StartingPosition, Vector2 FacingTowards)
        {
            Position = StartingPosition;
            FaceTowards = FacingTowards;
            State = States.Standing;   
            SetCharactersView();
        }

IsAt

The IsAt() method allows the calling program to retrieve of set the position of the character. For this example, Y (altitude) should always be zero, but it's 3D if you want to implement jumping or something that allows for an altitude change.

View

The View property allows the View matrix to be retrieved, which will be used every time we want to draw the scene from the position of the character's eyes. It also allows the character's view matrix to be set by the calling program, which might use this for a special effect or something. For this example, we won't be setting the character's view matrix this way. Instead, we'll use the  SetCharactersView() method which doesn't allow us to set the View matrix, but instead defines the character's view matrix as being in front of the character's eyes and facing in the direction that the character's head is facing.

CurrentState

The CurrentState property allows the calling program to get or set the state the character is in.

SetCharactersView

The heart of SetCharactersView() is this line:

CharactersViewMatrix = Matrix.CreateLookAt(EyePosition(), EyePosition() + LookTowards(), Vector3.Up);

This loads the view matrix stored in the PlayerCharacter class with a "LookAt" matrix. The LookAt (view) matrix loads up a matrix that defines a camera that has a position and looks towards a position. In this case, we place the camera at the character's eyes. Normally, CreateLookAt will point the camera at a spot that you define, but that's not really the way we want our camera to work. It would not be good if the camera always looked at one spot in the room no matter which direction we faced or where we moved in the room. But this is exactly how CreateLookAt works. This may be great for a chase camera, but not to simulate our character's eyes.

So, we tell CreateLookAt to look one unit "in front of the character's eyes". The EyePosition() method returns a 3D position where the character's eyes are at. We combine that 3D "vector" with the actual 3D vector returned by LookTowards(). LookTowards returns a normalized vector (one unit in length) that points in whatever direction the character's head is facing. So, even if the character is looking down, it will be from the position of the character's eyes.

RequestWalk

The RequestWalk() method asks the Character object where it would go if it walked forward. It takes the GameTime as an input parameter in order to correctly calculate the distance to walk per frame. The method starts by taking the 2D direction vector FaceTowards, which stores the direction the character is facing, and converts it to a 3D vector on the Y=0 (X,Z) plane. Just in case this normalized vector managed to somehow get un-normalized, we go ahead and normalize it. Really, there's a bug in the program somewhere if this vector is ever not normalized, but this will insure that a rogue calling program, or something, doesn't give us an un-normalized FaceTowards vector and cause this method to have problems.

Take a close look at this line:

MovePerFrame *= (WalkingVelocity* Time.ElapsedGameTime.Milliseconds/1000f));

We're multiplying the 3D FaceTowards normalized vector (in MovePerFrame) times a "scalar". A scalar is just a normal number. Vectors aren't numbers. Chant the mantra with me "A vector is only a direction permanently tied to an amount"! Therefore, vectors are not numbers. A scalar is a normal number. Multiplying a vector times a scalar is multiplying a vector (not a number) by a scalar (which is a number). The way this works is that multiplying a vector times a scalar multiplies the length of the vector (which by itself is a number when separated from the direction) by the scalar number value.

This is why the MovePerFrame/FaceTowards 3D vector MUST be normalized. Multiplying a normalized vector times a scalar will simply set the length of the vector to the value of the scalar because it's normalized to a length of one, and anything times one is that value.

ElapsedGameTime (the amount of time since the last update frame occured) is measured in milliseconds. We multiply the number of milliseconds elapsed since the last update times the number of units/meters of the character's walking speed per second. We multiply that value by one second per 1000 milliseconds. Essentially, the divide by 1000 is converting the character's walking speed from seconds to milliseconds. So, we end up with a scalar value for the character's walking speed in units, or meters, per frame rather than seconds or milliseconds. It's the distance the character would walk during the time between updates.

Multiplying this scalar value times the FaceTowards 3D vector will give a vector that continues to face in the same direction as FaceTowards did before, but now has a length of exactly the distance the character can walk during the time of one frame (the time between update calls).

 Adding this vector the character's current position would give you a new position where the character could walk to in the time of one frame (time between update calls).

The way we're using it in this program is to allow a request to be made to walk to a certain position during the next frame, which the calling program can grant or deny, mostly based on whether a collision should prevent the character from going there.

 private Vector3 RequestWalk(GameTime Time)
          {
            Vector3 MovePerFrame;

            MovePerFrame = new Vector3(FaceTowards.X, 0f, FaceTowards.Y);
            MovePerFrame.Normalize();   
            MovePerFrame *= (WalkingVelocity* Time.ElapsedGameTime.Milliseconds/1000f));   
            return MovePerFrame;  
          }

TurnLeft/TurnRight

The TurnLeft and TurnRight methods allow the player to change the direction that the character's body is facing. FaceTowards is a 2D vector, as mentioned earlier. The turn rate is fixed by this line:

private float TurnRate = MathHelper.ToRadians(60f);

By limiting FaceTowards to 2 dimensions we avoid any issues where forward/backward or turning actions force the character to fly or wind up below ground. And really, we don't want the character's body to face up or down. Their head can face those directions but not their body. Probably the only time you might want to face up or down is if the character is lying down. And that can be faked in other ways. Just keep in mind that this is a 2D direction/vector in a 3D world.

The Turn variable will hold the angle to turn each update frame in radians.

I can't find any methods in XNA to rotate a 2D vector. So, that gives us only one choice, and that is to rotate it ourselves.

The rotation formulas are algebra formulas that use trigonometric functions to mathematically rotate a 2D point. Notice that there are 2 formulas. One for X and one for Y (which in our 3D world is the Z axis, not Y). Both are required to rotate a point, because a point is 2 numbers (the X and the Y value). The formula takes an angle in radians as well as the X and the Y of the  current position and gives the resulting X and Y values as a new position.

Here are the rotation formulas:

x = FaceTowards.X * (float)Math.Cos(Turn) - FaceTowards.Y * (float)Math.Sin(Turn);

 z = FaceTowards.X * (float)Math.Sin(Turn) + FaceTowards.Y * (float)Math.Cos(Turn);

If you are don't know Trigonometry, you might not be familiar with Sine and Cosine. But it's really not necessary to understand them at this point; XNA takes care of calculating them properly here.

The main thing you need to know is that these formulas will rotate a 2D vector's head around it's tail when the tail is at the origin (X=0, Y=0). All vectors in XNA have their tail at the origin.

 For 3D vectors XNA has rotation matrices which use matrices to apply this same formula to 3D vectors. Notice that there are three rotation matrices, one for X, one for Y, and one for Z. This is because the rotation formula is actually 2D and so rotation in 3D is done around only one axis. Really, all three 3D axis rotations use the exact same formula, they just redefine the 2D plane for the rotation formula according to which of the three 3D axes you want to rotate around. Also, notice that the rotation matrices require the center of rotation to be moved to th origin, because the rotation formula assumes you are rotating around the origin.

Again, XNA stores X and Y in it's 2D vectors, but we're placing our FaceTowards 2D vector on the X,Z plane. Therefore, Y in the 2D vector is the same dimension as Z in our 3D world. This is because we have no way to get XNA to store the 2D vector as an X,Z position. Just don't let it get you confused.

We normalize the FaceTowards vector because FaceTowards is a direction without an amount. So, we set the amount, or length of the vector, to one by normalizing it. When a vector's length is one it can be multiplied by a scalar value to set it to any length that you like.

LookUp/LookDown

The LookUp() and LookDown() methods pitch the character's head up or down to change the direction that the character is looking at each frame.

RequestStrafe

When I looked back at the RequestStrafe() method, it took me a couple minutes to remember what I was trying to do with that code. RequestStrafe() allows the calling program to ask where the character would go if it were to strafe, or step sideways, for one frame of game time. It's done as a request, so the calling program can decide whether to grant or deny the movement, usually based on whether it would cause the character to collide with another object.

The obvious question is "why are we using the rotation formulas just to take a side step"? If you don't really think about it, you might think that you can just change the X or Z value to move to the side. That would work if the character were always aligned with the X or Z axis, but they almost never will be. What we need is to know what direction the character is facing (a vector) and calculate a normalized vector that is perpendicular (90 degrees) with the direction the character is facing. This should give us the correct direction to move towards when strafing, or side stepping, to the right. The same vector can be multiplied by a negative one scalar value to reverse it's direction. This would give us the correct direction to strafe, or side step, to the left. And that's exactly what this code does.

The RequestStrafe() method returns a normalized vector that points in the correct strafing direction which has had it's length changed to the distance a character would move in one update frame of the game. Adding this to the character's current position, would give a new position where the character would be positioned at if the strafe movement took place.

Update

The Update() method contains code that "would" have gone in the Update() method of our game's main class, but has been moved here to keep it with the Character object.

Really, all it does is use a Switch to call either RequestWalk() or RequestStrafe() or has the character stand still. Both the vector returned by RequestWalk() and RequestStrafe(), are simply reversed with a negative for walking backwards or strafing left.

Source Code

And that's pretty much our entire PlayerCharacter class which is mostly just a camera implementation that represents a character controlled by the user like a first person camera.

The source code for the program, up to this point, is provided below along with hyperlinks to just download the files in their entirety.

The Complete Source Code for Character.cs:

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 BuildingCharacter
{

    class PlayerCharacter
    {

        private float Height = 1.75f;   //Height at the top of the character's head.
        private float EyeLevel = 1.64f;     //Height of the character's eyes.
        private Vector3 Position = new Vector3(0f, 0f, 0f); //3D coordinates of where the character's feet are at.
        private Vector2 FaceTowards = new Vector2(0f, -1f); //Direction that the character's body is facing
        private float HeadPitch = 0f;   //Radians that the character's head is pitched from straight ahead.
        private float MaxHeadPitch = MathHelper.ToRadians(80f); //Max head pitch angle upwards from level.
        private float HeadYaw = 0f;     //Radians that the character's head is yawed from straight ahead.
        private Matrix CharactersViewMatrix;  //View Matrix for what the character is looking at.
        private float WalkingVelocity = 2.7f;   //Speed that the character will walk at in m/s. 1 to 1.5 is avg.
        private float StrafingVelocity = 2.0f;  //Speed that the character will side step at per m/s.
        private float TurnRate = MathHelper.ToRadians(60f);   //Radians to turn per second.
        private float HeadTurnRate = MathHelper.ToRadians(60f);   //Radians to turn per second.
        public enum States{Standing, Walking, Backing, StrafingLeft, StrafingRight, Running, Sitting, Lying}
        public States State;


        public PlayerCharacter(Vector3 StartingPosition, Vector2 FacingTowards)
        //===================================================================================
        // PlayerCharacter()
        //
        // Purpose: Constructor for the class initializes the class on creation.
        // Parameters:  StartingPosition - Sets the character's initial starting position.
        //              FacingTowards - Sets the initial 2D direction the character is facing.
        //
        // Returns: Constructor
        //
        // Notes: I got to realizing how CPU expensive it is to calculate a new view for the 
        //        character every time the screen is drawn and decided to hold the value
        //        as a field. That required it to be initialized.
        //===================================================================================
        {
            Position = StartingPosition;
            FaceTowards = FacingTowards;
            State = States.Standing;    //Start the character out standing still.
            SetCharactersView();
        }
        //===================================================================================

        
        public Vector3 IsAt
        //===================================================================================
        // Property: IsAt
        //
        // Purpose: Allows the calling program to set the position of the character at will.
        //
        // Returns: Position
        //
        // Notes: This is very useful in collision detection. Another method requests movement
        //        but nothing actually moves the character. So, the calling program can "see"
        //        where the character is "trying" to go, and then decide whether to grant that
        //        request or not. The calling program can even "partially" grant the request
        //        because the IsAt property allows the caller to put the character in any 
        //        position it likes. This can also be used to make the character follow the
        //        height of the terrain when walking by adding terrain height to the Y component.
        //      
        //        If a Position change occurs the view matrix needs to be updated or the camera
        //        will not move to the new position.
        //===================================================================================
        {
            get { return Position; }
            set 
            {
                Position = value;       //Move the character to a new spot.
                SetCharactersView();    //Update the character's view matrix.
            }
        }
        //===================================================================================


        public Matrix View
        //===================================================================================
        // Property: View
        //
        // Purpose: Gives direct access to the character's view matrix.
        //
        // Returns: CharactersViewMatrix
        //
        // Notes: This has the potential to be used for something like "casting a spell"
        //        where the character steps out of their body and looks back at themselves.
        //
        //        It's primarily here, though, just to allow the calling program to get 
        //        the character's view matrix and I even considered not allowing a set.
        //===================================================================================
        {
            get { return CharactersViewMatrix; }
            set { CharactersViewMatrix = value; }
        }
        //===================================================================================


        public States CurrentState
        //===================================================================================
        // Property: CurrentState
        //
        // Purpose: Manages the "state" of the character.
        //
        // Returns: State
        //
        // Notes: Right now, this allows for different states, such as walking, running,
        //        sitting, etc. This is primarily for states of "motion".
        //===================================================================================
        {
            get { return State; }
            set { State = value; }
        }
        //===================================================================================


        private Matrix SetCharactersView()
        //===================================================================================
        // SetCharactersView()
        //
        // Purpose: To easily get a view matrix for what the character is looking at.
        // Parameters:  
        // Returns: CharactersViewMatrix - A "Look At" matrix which contains a view matrix for
        //          what the character is looking at.
        //
        // Notes: This is provided to make it easy to get a look at view matrix for the character.
        //        It is meant to simulate the character's head, which means LookTowards should be
        //        limited to 
        //===================================================================================
        {
            //Creates a "lookat" vector in front of the character's face that looks where the head faces.
            CharactersViewMatrix = Matrix.CreateLookAt(EyePosition(), EyePosition() + LookTowards(), Vector3.Up);
            return CharactersViewMatrix;
        }
        //===================================================================================


        public Vector3 EyePosition()
        //===================================================================================
        // EyePosition()
        //
        // Purpose: To easily get a 3D position vector that represents the position of the 
        //          character's eyes.
        // Parameters:  
        // Returns: Eyes - A 3D position vector for character's eye position.
        //
        // Notes: Position is where the character's feet are at and it doesn't make sense to
        //        do something like place a camera there. You need to know where "eye level"
        //        is at. This can then be used to calculate a "LookAt" vector for the
        //        camera's view matrix since you can use EyePosition as the "tail" of the 
        //        vector. You can "translate" the LookAt vector by the EyePosition and
        //        thereby create a LookAt immediately in front of the eyes.
        //===================================================================================
        {
            Vector3 Eyes = new Vector3(0f, EyeLevel, 0f);

            Eyes = Position + Eyes;
            return Eyes;
        }
        //===================================================================================


        private Vector3 LookTowards()
        //===================================================================================
        // LookTowards()
        //
        // Purpose: To provide a 3D vector in the direction the character's head is facing.
        // Parameters:  
        // Returns: LookingAt
        //
        // Notes: This should return a normalized vector that points in the direction that
        //        the character is looking towards. This is different than the direction that
        //        the character's body is facing. Adding the result vector to the position of
        //        the character's eyes will give a "LookAt" vector for the camera matrix.
        //
        //        Calculating the answer is a little tricky. FaceTowards is a 2D vector
        //        that represents the direction the character's body is facing, which is
        //        different from the direction the head is facing. The body is never allowed 
        //        to face up or down. So, FaceTowards.X is X in 3D, bute FaceTowards.Y is Z in 
        //        3D. A little confusing but this keeps us from having problems with the body
        //        facing in some odd upward or downward position due to a bug in the code.
        //        (I had to think about what I'm going to do if the character is allowed to 
        //        lie down but I think I will assign that to a "fixed" camera.)
        //
        //        The rotation formula is:
        //          x = x*cos(angle) - y*sin(angle)
        //          y = x*sin(angle) + y*cos(angle)
        //
        //        For anyone new to the rotation formula, notice it takes two formulas to
        //        rotate something in 2D space. http://en.wikipedia.org/wiki/Rotation_(mathematics)
        //
        //        I started to use the rotation formula and then realized that I could project
        //        the 2D vector into 3D space easily. Once in 3D space you can use rotation 
        //        matrices.
        //
        //        I'm assuming that HeadYaw and pitch will be kept to realistic angles elsewhere.
        //
        //        This is a lot of math to perform
        //===================================================================================
        {
            Vector3 LookingAt;  //Return value.
            Vector3 Right;
            Matrix LookAtMatrix = Matrix.Identity;   //Empty matrix.
            Matrix YawMatrix = Matrix.Identity;     //Empty matrix.
            

            LookingAt = new Vector3(FaceTowards.X, 0f, FaceTowards.Y);  //Project FaceTowards into 3D space.
            Right = LookingAt;      //Prepare to create a right facing vector from LookingAt

            YawMatrix = Matrix.CreateFromAxisAngle(Vector3.Up, HeadYaw);    //Load matrix with yaw rotation.
            //Right is rotated 90 degrees from LookingAt to "make it" a right angle with LookingAt.
            Right = Vector3.Transform(Right, Matrix.CreateFromAxisAngle(Vector3.Down, MathHelper.ToRadians(90)));
            Right = Vector3.Transform(Right, YawMatrix);    //Now rotate it by the yaw formula matrix.

            LookAtMatrix = Matrix.CreateFromAxisAngle(Right, HeadPitch);    //Now we can pitch with the right vector.
            LookingAt = Vector3.Transform(LookingAt, LookAtMatrix);  //Apply Pitch rotation.

            return LookingAt;
        }
        //===================================================================================



        private Vector3 RequestWalk(GameTime Time)
        //===================================================================================
        // RequestWalk()
        //
        // Purpose: Returns a vector of the distance change and direction that a walk causes.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // Returns: MovePerFrame - Position change that a frame of walking causes.
        //
        // Notes: Position + MovePerFrame is where the character would walk to in one frame.
        //        Instead of just going there, we're returning a vector for MovePerFrame as a
        //        "suggested" spot to walk to. This allows the calling game to reject the 
        //        request if a wall or some object forbids the request from happening.
        //===================================================================================
        {
            Vector3 MovePerFrame;

            MovePerFrame = new Vector3(FaceTowards.X, 0f, FaceTowards.Y);
            MovePerFrame.Normalize();   //FaceTowards "should" have been normalized, but I'm not taking chances.
            MovePerFrame *= (WalkingVelocity*(Time.ElapsedGameTime.Milliseconds/1000f));    //Velocity/FPS.

            return MovePerFrame;   //Ask to move the walking distance per frame in Facing direction.
        }
        //===================================================================================


        public void TurnLeft(GameTime Time)
        //===================================================================================
        // TurnLeft()
        //
        // Purpose: Turns the character's body to the left.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: TurnRate - number of radians to turn every frame. Negative for left turns.
        //            FaceTowards - This method changes the value of FaceTowards.
        // Returns: void
        //
        // Notes: The rotation formula is:
        //          x = x*cos(angle) - y*sin(angle)
        //          y = x*sin(angle) + y*cos(angle)
        //
        //        For anyone new to the rotation formula, notice it takes two formulas to
        //        rotate something in 2D space. http://en.wikipedia.org/wiki/Rotation_(mathematics)
        //===================================================================================
        {
            float x = 0f;
            float z = 0f;
            float Turn = -TurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);


            x = FaceTowards.X * (float)Math.Cos(Turn) - FaceTowards.Y * (float)Math.Sin(Turn);
            z = FaceTowards.X * (float)Math.Sin(Turn) + FaceTowards.Y * (float)Math.Cos(Turn);
            FaceTowards = new Vector2(x, z);
            FaceTowards.Normalize();    //"Should" already be normalized, but "just in case".
        }
        //===================================================================================


        public void TurnRight(GameTime Time)
        //===================================================================================
        // TurnRight()
        //
        // Purpose: Turns the character's body to the left.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: TurnRate - number of radians to turn every frame. 
        //            FaceTowards - This method changes the value of FaceTowards.
        // Returns: void
        //
        // Notes: The rotation formula is:
        //          x = x*cos(angle) - y*sin(angle)
        //          y = x*sin(angle) + y*cos(angle)
        //
        //        For anyone new to the rotation formula, notice it takes two formulas to
        //        rotate something in 2D space. http://en.wikipedia.org/wiki/Rotation_(mathematics)
        //===================================================================================
        {
            float x = 0f;
            float z = 0f;
            float Turn = TurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);


            x = FaceTowards.X * (float)Math.Cos(Turn) - FaceTowards.Y * (float)Math.Sin(Turn);
            z = FaceTowards.X * (float)Math.Sin(Turn) + FaceTowards.Y * (float)Math.Cos(Turn);
            FaceTowards = new Vector2(x, z);
            FaceTowards.Normalize();    //"Should" already be normalized, but "just in case".
        }
        //===================================================================================


        public void LookUp(GameTime Time)
        //===================================================================================
        // LookUp()
        //
        // Purpose: Turns the character's head upwards.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: HeadTurnRate - number of radians to turn every frame. Negative for left turns.
        //            HeadPitch - Radians that the character's head is pitched up or down.
        // Returns: void
        //
        // Notes: 
        //===================================================================================
        {
            HeadPitch += HeadTurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);
            if (HeadPitch > MaxHeadPitch)
            {
                HeadPitch = MaxHeadPitch;                
            }
        }
        //===================================================================================


        public void LookDown(GameTime Time)
        //===================================================================================
        // LookDown()
        //
        // Purpose: Turns the character's head downwards.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: HeadTurnRate - number of radians to turn every frame. Negative for left turns.
        //            HeadPitch - Radians that the character's head is pitched up or down.
        // Returns: void
        //
        // Notes: 
        //===================================================================================
        {
            HeadPitch += -HeadTurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);
            if (HeadPitch < -MaxHeadPitch)
            {
                HeadPitch = -MaxHeadPitch;
            }
        }
        //===================================================================================


        private Vector3 RequestStrafe(GameTime Time)
        //===================================================================================
        // RequestStrafe()
        //
        // Purpose: Returns a vector of the distance change and direction that a strafe causes.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // Returns: MovePerFrame - Position change that a frame of strafing causes.
        //
        // Notes: Position + MovePerFrame is where the character would move to in one frame.
        //        Instead of just going there, we're returning a vector for MovePerFrame as a
        //        "suggested" spot to move to. This allows the calling game to reject the 
        //        request if a wall or some object forbids the request from happening.
        //===================================================================================
        {
            Vector3 MovePerFrame;
            float x = 0f;
            float z = 0f;


            x = FaceTowards.X * (float)Math.Cos(MathHelper.ToRadians(90f)) - FaceTowards.Y * (float)Math.Sin(MathHelper.ToRadians(90f));
            z = FaceTowards.X * (float)Math.Sin(MathHelper.ToRadians(90f)) + FaceTowards.Y * (float)Math.Cos(MathHelper.ToRadians(90f));

            MovePerFrame = new Vector3(x, 0f, z);
            MovePerFrame.Normalize();   //FaceTowards "should" have been normalized, but I'm not taking chances.
            MovePerFrame *= (StrafingVelocity * (Time.ElapsedGameTime.Milliseconds / 1000f));    //Velocity/FPS.

            return MovePerFrame;   //Ask to move the walking distance per frame in Facing direction.
        }
        //===================================================================================


        public void Update(GameTime Time, ref Vector3 RequestedPositionChange)
        //===================================================================================
        // Update()
        //
        // Purpose: Handle anything that should occur every time the main game's Update is called.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        //              RequestedPositionChange - Should be empty coming in. Does not matter.
        // DependsOn: TurnRate - number of radians to turn every frame. Negative for left turns.
        //            FaceTowards - This method changes the value of FaceTowards.
        // Returns: RequestedPositionChange - Vector, when combined with Position, gives the path
        //            that we would "like" to move the character through.
        //
        // Notes: RequestedPositionChange is a way of working with a collision detection system. By
        //        "requesting" a change in position from the main program, the main program can 
        //        decide whether to grant the request or "how" to grant the request. This
        //        parameter is "supposed" to be empty when it comes in and this method fills in a
        //        value for it so that the caller knows where the character wants to be repositioned
        //        to. Since Position is exposed as a property (IsAt), the calling program can just 
        //        simply move the character whereever it likes. So, if this call returns a requested
        //        move that the game doesn't like (for example would cause a collision) then the
        //        game can adjust the move to something that conforms to the rules of the game.
        //===================================================================================
        {
            switch (State)
            {
                case States.Walking:
                    RequestedPositionChange = RequestWalk(Time);
                    break;
                case States.Backing:
                    //Making the movement vector negative will point it in the opposite direction.
                    RequestedPositionChange = -RequestWalk(Time);
                    break;
                case States.StrafingRight:
                    RequestedPositionChange = RequestStrafe(Time);
                    break;
                case States.StrafingLeft:
                    RequestedPositionChange = -RequestStrafe(Time);
                    break;
                //If the character is not doing anything else, the character is standing.
                default:
                    RequestedPositionChange = Vector3.Zero;
                    //Do standing actions.
                    break;
            }
        }
        //===================================================================================

    }

}

 

The Complete Source Code for BuildCharacter.cs:

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 BuildingCharacter
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager GraphicsManager;  //Rename this like so.
        GraphicsDevice GraphicsCard;            //You can basically think of this as your graphics card or screen.
        SpriteBatch spriteBatch;                //You can leave this in even though we could remove it from this program.

        Texture2D GridTexture;  //Stores our texture in memory.
        Model HoloDeckModel;    //Stores our model in memory.
        Matrix HoloDeckModelWorldMatrix = Matrix.Identity;    //Stores the position and sizing info for our model.
        Model Chair;     //Stores the model named Chair.
        Matrix ChairsWorldMatrix;    //Stores Chair's position, facing and scaling.
        Matrix Projection;  //Think of this as the "lens system" for our camera.
        PlayerCharacter Character;
        const float HoloDeckWidth = 40f;    //Our model is 2 units cubed. This will be the number to multiply that size by.

        public Game1()
        {
            GraphicsManager = new GraphicsDeviceManager(this);  //Since we renamed this it has to be changed here too.
            //Changing the backbuffer is not absolutely necessary. I have a 16:9 screen and I like
            //to set the backbuffer to what I want it to be rather then let XNA pick for me.
            //The backbuffer is where things are drawn. Once the drawing is complete and ready to
            //be displayed on the screen the drawing on the backbuffer is sent to the front buffer.
            //The front buffer is basically your screen. That's why the size needs to match the 
            //resolution of your screen.
            GraphicsManager.PreferredBackBufferWidth = 1280;    //Screen width horizontal. Change this to fit your screen.
            GraphicsManager.PreferredBackBufferHeight = 720;    //Screen width vertical. Change this to fit your screen.
            //You can run your game in full screen or windowed mode. Sometimes you write bugs
            //into your code that make it hard to recover in full screen mode. (Try Alt+F4 or 
            //Ctrl+Alt+Del to get control in those situations.)
            GraphicsManager.IsFullScreen = true;  //Feel free to set this to true once your code works. 
            //this.IsFixedTimeStep = false;     //Not 60 FPS.
            //this.TargetElapsedTime = TimeSpan.FromSeconds(1.0f / 100.0f);   //Set FrameRate to 100FPS.
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            GraphicsCard = GraphicsManager.GraphicsDevice;  //Introduce the Graphics Manager to our graphics card.
            Window.Title = "The Holo-Deck"; //Your window's title displays in the top left corner in windowed mode.

            //We need to load up our projection matrix with our camera's Field of View,
            //our screen aspect ratio, and the clipping planes. Changes this are only
            //updated when you change the aspect ratio, I believe. So it's pretty much
            //set it up once and leave it alone.
            Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(40f),
                GraphicsCard.Adapter.CurrentDisplayMode.AspectRatio, 0.1f, 10000f);

            //CameraPostion = new Vector3(0f, 1.8f, 0f);    //Start the camera in the center of our world but 1.8 unit above ground.
            //CameraLookAt = CameraPostion + Vector3.Forward;
            //Think of the View Matrix as our camera. I'm loading it with our 3D position. And 
            //then I'm telling it to look straight forward by telling it to look at a point
            //that is 1 unit directly in front of the camera. Vector3.Forward is a unit
            //vector of 1 length that points in the forward direction, but it's tail
            //is at 0,0,0 and we need it to be pointing "from" the camera. Instead of
            //defining it this way you can just load up a Vector3 with the position
            //you want it to look at just like we did with Camera position.
            //CreateLookAt wants you to define "up" for it and so you send it an up vector.
            //View = Matrix.CreateLookAt(CameraPostion, CameraLookAt, Vector3.Up);
            Character = new PlayerCharacter(new Vector3(0f, 0f, 0f), new Vector2(0f, -1f));


            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            GridTexture = Content.Load<Texture2D>("GridTexture");   //Load the Grid texture into memory from disk.
            HoloDeckModel = Content.Load<Model>("HoloDeckMesh");    //Load our .X file model for the room into memory.

            //We load up a CreateScale matrix to resize our model since it's about 2 units cubed in size.
            //The CreateScale matrix will have the info in it to correctly size our model according to the value
            //we pass to it. We combine it (through multiplication) with a CreateTranslation matrix loaded
            //up with a position change that moves our model upwards by half the width of our cube. 
            HoloDeckModelWorldMatrix = Matrix.CreateScale(HoloDeckWidth) * Matrix.CreateTranslation(0.0f, HoloDeckWidth, 0.0f);   //Resize our model larger and move it.

            //Load the "Chair" model into memory and position it at x,y,z coordinates 4,0,-2.
            //Chair Silla by "Ketchup" from Sketchup 8 Model Warehouse
            //Chair is an absolutely enormous model for our scale of one unit equals one meter.
            //We need to shrink Chair down to a normal size. I also want to have it facing more towards 
            //the center of the room. You can comment out the various matrices to see what happens
            //without them.
            Chair = Content.Load<Model>("Chair");
            ChairsWorldMatrix = Matrix.CreateScale(0.035f);   //Chair is WAY too big.
            ChairsWorldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(30f)); //Combine a rotation of Chair by 30 degrees.
            ChairsWorldMatrix *= Matrix.CreateTranslation(new Vector3(-8f, 0f, -8f)); //Combine its world matrix with a movement/translation.

        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            KeyboardState KBState;
            Vector3 RequestedMovement = Vector3.Zero;

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here
            KBState = Keyboard.GetState();
            if (KBState.IsKeyDown(Keys.Escape)) this.Exit();

            Character.State = PlayerCharacter.States.Standing;


            if (KBState.IsKeyDown(Keys.LeftShift) || KBState.IsKeyDown(Keys.RightShift))
            {
                if (KBState.IsKeyDown(Keys.D) || KBState.IsKeyDown(Keys.Right))
                {
                    Character.State = PlayerCharacter.States.StrafingRight;
                }

                if (KBState.IsKeyDown(Keys.A) || KBState.IsKeyDown(Keys.Left))
                {
                    Character.State = PlayerCharacter.States.StrafingLeft;
                }
            }
            else
            {
                if (KBState.IsKeyDown(Keys.Up) || KBState.IsKeyDown(Keys.W))
                {
                    Character.State = PlayerCharacter.States.Walking;
                }

                if (KBState.IsKeyDown(Keys.Down) || KBState.IsKeyDown(Keys.S))
                {
                    Character.State = PlayerCharacter.States.Backing;
                }

                if (KBState.IsKeyDown(Keys.Left) || KBState.IsKeyDown(Keys.A))
                {
                    Character.TurnLeft(gameTime);
                }

                if (KBState.IsKeyDown(Keys.Right) || KBState.IsKeyDown(Keys.D))
                {
                    Character.TurnRight(gameTime);
                }

                if (KBState.IsKeyDown(Keys.PageUp) || KBState.IsKeyDown(Keys.E))
                {
                    Character.LookUp(gameTime);
                }

                if (KBState.IsKeyDown(Keys.PageDown) || KBState.IsKeyDown(Keys.Q))
                {
                    Character.LookDown(gameTime);
                }
            }
            
            

            Character.Update(gameTime, ref RequestedMovement);
            Character.IsAt += RequestedMovement;

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            // TODO: Add your drawing code here
            //Normally you don't HAVE to change the RasterizerState but we want to change the 
            //CullMode. The rasterizer is part of the screen drawing mechanism and we're telling
            //XNA how things should be drawn. The cull mode tries to cull out triangles that don't
            //"need" to be drawn. The default is CullCounterClockwiseFace which tells XNA not to 
            //draw the back sides of triangles. Most of the time this makes sense; you can't "see"
            //the triangles that are facing away from you, so why draw them; it's just wasting CPU
            //time. It decides which side is the front of the triangle by the cull mode and the
            //direction you "wind", or define, the vertices in. This is why you should always 
            //wind your vertices clockwise according to the direction you want them to face.
            //Since we're debugging, there's a good chance that we messed up and wound our
            //vertices in the wrong direction. So, we turn off culling and draw both sides of 
            //all triangles by setting the cull mode to None. Once we get the program working,
            //we can change CullMode back to CullCounterClockwiseFace and our game will run
            //faster.
            RasterizerState RS = new RasterizerState();
            RS.CullMode = CullMode.None;
            RS.FillMode = FillMode.Solid;
            GraphicsCard.RasterizerState = RS;


            foreach (ModelMesh Mesh in HoloDeckModel.Meshes)
            {
                //Here we define an effect. The effect is kind of like the pen or brush that we will "draw" our
                //3D world with. It's basically what they call a "shader". Shaders are code that draw things on
                //the screen. XNA 4.0 has about 5 different effects, or shaders, built into it. BasicEffect is the one
                //you will use most of the time. The others are more for special purposes.
                //We're loading up a BasicEffect here so that we can use it. We could have loaded up one of the other
                //effects or written our own effect and loaded it up. Writing your own effects is done in a language
                //called HLSL (High Level Shader Language). You don't really need to do that starting out though;
                //because BasicEffect does a pretty good job and you really need to know what you're doing to write a
                //shader that does a better job drawing the screen than the built in effects.
                foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with.
                {
                    Shader.EnableDefaultLighting();
                    Shader.TextureEnabled = true;   //We're going to draw using a texture.
                    Shader.Texture = GridTexture;   //The model will draw without this call, but it will use the texture in the file.
                    Shader.LightingEnabled = false; //Don't use BasicEffect's "advanced" lighting for now.
                    Shader.World = HoloDeckModelWorldMatrix;    //Tell the shader what World Matrix to use to draw the object.
                    Shader.View = Character.View;             //Tell the shader where to draw from (the camera).
                    Shader.Projection = Projection; //Tell the shader how to draw (like the camera lens... sort of).
                }
                Mesh.Draw();  //Draw the mesh
            }


            foreach (ModelMesh Mesh in Chair.Meshes)
            {
                foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with.
                {
                    Shader.EnableDefaultLighting();
                    Shader.TextureEnabled = true;   //We're going to draw using a texture.
                    Shader.World = ChairsWorldMatrix;    //Tell the shader what World Matrix to use to draw the object.
                    Shader.View = Character.View;             //Tell the shader where to draw from (the camera).
                    Shader.Projection = Projection; //Tell the shader how to draw (like the camera lens... sort of).
                }
                Mesh.Draw();  //Draw the mesh
            }


            base.Draw(gameTime);    //Always include this.
        }
    }
}

 







 

 

Summary

In this part we implement a "camera" class to allow movement around the room like a person walking around the room. We create a separate class to make this code more modular and keep in line with Object Oriented Programming principles.

Source Code





Tutorials

The Holodeck

Subsites

Blog

Files


Future Use