John Perry final
Writeup
Index:
1.) Write Up
2.) Screen Shots
3.) Code
1.) Write Up:
I was very happy with the progress I made towards
getting a great start on my research project for next semester. I learned a lot
about Visual Studio, the C# language and most importantly XNA. Ultimately I was
able to get an object, for my demonstration the object happened to be a space
ship, flying in three dimensions with the camera following it. It also had a
“spring effect” which worked well in emphasizing the speed of the object. I
started by doing some tutorials on XNA, then I did some research on moving in
three dimensions and ultimately dug up some old linear algebra and physics
equations to get the ship and the “spring effect” working. First I will describe
the tutorials.
I started with a little two dimensional tutorial on making a
picture float around the screen and when it hit the side it would bounce off it
in the opposite direction. This was a good introduction to XNA’s syntax however
I didn’t use much of the knowledge I gained there in the three dimensional
animation I ultimately demonstrated. After the two dimensional tutorial I did a
three part three dimensional tutorial. It was here that I learned most of the
knowledge that I applied for creating my final animation. The first step was
simply to display a rocket ship on the screen. I didn’t actually create the
rocket ship however it did give me experience in using .fbx files. The second
step was making the ship move in two dimensions and the third step was adding
sound. This taught me a lot of things necessary for the final project. First I
learned how three dimensional models work in XNA, second I learned how to take
input from both the key board and the xbox game pad. The third step was adding
sound when the ship boosted and when I warped the ship back to the center. I was
able to get this working in the tutorial, however, I started from scratch with
my demonstration and as things came towards the deadline I was not able to
re-implement the sound which was disappointing.
The first thing I decided I needed to do was to be able to
control the camera in three dimensional space, preferably with an object in
front of it. This is commonly known as third person view for most games. I first
tried to implement this based directly off the tutorial I finished. After making
no progress I began to search to see if there was any information on where to
begin with what I wanted to do. I was able to find information on exactly what I
was looking for and with an added "spring effect" that I actually planed on
implementing but not until later in the project. It was here I quickly learned
that movement in three dimensions is much more difficult than two dimensions.
For the object i needed 4 vectors, the object position, its direction, it up
vector and its right vector. All of this is just to get the ship's orientation
in 3D space. We also have a velocity and acceleration vector. Finally we also
have a vector for force, and constant for drag force. For as long as the user
hold down the space bar, we have a trust amount of 1. We then get force =
objectDirection * thrustAmount * MaxForce; where MaxForce is just an arbitrary
constant. Then we have acceleration = force / Mass; objectVelocity +=
acceleration * elapsed; where Mass is just a random constant I set, and
elapse is the time we have been at this acceleration. when the space bar is not
down acceleration and force = 0 so object velocity should be unaffected, however
i also have the following line of code. objectVelocity *= DragFactor;
While the spacebar is not down this is the acting force that slows the
spaceship. Finally we apply the velocity on the position and then make sure that
the y position never goes below the "floor". We also use a matrix to store
the for the rotations around the x and y axis.
The camera was a bit more difficult as it's position,
velocity etc. were all based on where the object was. We needed to know what
direction the the camera was looking to, much like how we found the direction
the object facing. For the camera there are also some special properties like
aspect ratio, field of view, near clipping plane and far clipping plane. Aspect
ratio is basically the dimensions of the screen, I used 4:3 which is the size of
a standard TV. Field of view is is how narrow or wide the camera is looking. The
clipping planes just show only things that are between them. The far clipping
plane is more important because anything beyond that plane won't be shown to the
user. The near clipping plane is basically where the camera is so if somehow I
got the ship to crash into the camera the camera would just not show the ship.
We have two main matrices one called view which holds the camera's position,
what it is looking at and it's up vector. Then we have a matrix called project
which holds those four camera properties I mentioned above. The spring physics
while conceptually was quite hard, here are the 2 lines of code which
essentially produced the effect.
Vector3
stretch = position - desiredCameraPosition;
Vector3 force
= -stiffness * stretch - damping * velocity;
the stretch vector is basically the difference between where the camera is now
and where we want it to be. Then we calculate the force vector as described
above. The stiffness needs to be pretty high so, like i showed in my
demonstration, the plane doesn't get too far away from the camera. The dampening
also needs to be much greater than the stretch so the the second term will not
oscillate from positive to negative, because if it did, the camera would bounce
back and forth behind the object. Once we have the force we can derive the
acceleration, velocity and position as we did with the object.
Finally the beginning class is called Game1. Here we make the
object the camera and the ground, and call the methods of theObject, and
CameraFollow. First we set the screen size and the camera properties. There are
a few other initializations done here which i will go over a bit later when
describing XNA as a whole. Here we also set that if we press r we reset the
ship, if we press escape we quit etc. Finally we draw the scene. The drawing is
a bit difficult to explain and part of it i used from the tutorials. Each
model is connect to a set of bones, and those bones have meshes and
the meshes move alone with its parent bone, and when we draw it we calculate all
the transformations on the bones and therefore the meshes. Finally we have the
text on screen as instructions. The way we did this is by creating a
SpriteBatch. Then i create a string with the instructions. Final we begin the
spritebatch, do a spriteBatch.DrawString(spriteFont, text, new
Vector2(65, 65), Color.Black); spriteFont is just a font i picked earlier
in the program, text is the string with the instructions, and Vector2(65,65)
just says where on screen the text should be drawn. this is a common way of
showing HUDs on video games.
Finally all XNA video games have the same basic structure.
The entry point with the main method and basically a game.run(); call. In the
game one class we have the game 1 method with many initializations. Then we have
the initialize method where it 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. Then we have
the load and unload graphics content methods where we load things like models
and fonts. Then we have the update method. the update method is very important
because this is where we describe how the scene changes from frame to frame.
Then we have the draw method that draws the scene was we describe it in update.
Ultimately i was very happy with the final outcome of my
project and i can't wait to get working on it again next
semester.
2.) Screen Shots
Here are some in game screen shots of of my final project:
First this is just the ship being idle a little bit above the ground
Here is the ship turning with normal spring settings:
Here is the ship thrusting with normal spring settings:
Finally this is a demonstration of one of the spring properties being altered.
It would be easier to see in a video but i did demonstrate this in class. In the
below picture the stiffness of the spring is greatly reduced therefore the ship
gets very far away from the camera before the camera start to follow it. Like a
normal spring it also only follows it very slowly, since in the physics of the
game, the masses of the camera and the ship are large, and the spring force is
not very large.
3.) Code
The object code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
namespace Final
{
class theObject
{
#region ---- Variable Declerations
----
//AudioEngine audioEngine;
//WaveBank waveBlank;
//SoundBank soundBlank;
//This is basically so there is a
floor that the object can't fall through
private const float MinimumAltitude =
350.0f;
// Location of the object in world
space.
public Vector3 objectPosition;
// Direction the object is facing.
public Vector3 objectDirection;
// The object's up vector.
public Vector3 Up;
// The object's right vector.
private Vector3 right;
public Vector3 Right
{
get { return
right; }
}
// The speed that the can rotate in
any direction
// this is measured in radians per
second
private const float RotationRate =
1.5f;
// Mass of the object.
private const float Mass = 1.0f;
// Maximum force that can be applied
along the object's direction.
private const float MaxForce =
24000.0f;
// Approximate wind resistance to
slow and eventually stop the vehical when trust is not applied
private const float DragFactor =
0.97f;
// Current object velocity.
public Vector3 objectVelocity;
// The object's world transform
matrix.
public Matrix World
{
get { return
world; }
}
private Matrix world;
#endregion
#region ---- Initializations ----
public theObject()
{
Reset();
}
// Restore the ship to its original
starting state
public void Reset()
{
objectPosition = new Vector3(0, MinimumAltitude, 0);
objectDirection = Vector3.Forward;
Up =
Vector3.Up;
right =
Vector3.Right;
objectVelocity = Vector3.Zero;
}
#endregion
#region ---- Update ----
/// Applies a simple rotation to the
ship and animates position based
/// on simple linear motion physics.
public void Update(GameTime gameTime)
{
KeyboardState
keyboardState = Keyboard.GetState();
GamePadState
gamePadState = GamePad.GetState(PlayerIndex.One);
float elapsed
= (float)gameTime.ElapsedGameTime.TotalSeconds;
// Determine
rotation amount from input
Vector2
rotationAmount = -gamePadState.ThumbSticks.Left;
if
(keyboardState.IsKeyDown(Keys.Left))
rotationAmount.X = 1.0f;
if
(keyboardState.IsKeyDown(Keys.Right))
rotationAmount.X = -1.0f;
if
(keyboardState.IsKeyDown(Keys.Up))
rotationAmount.Y = -1.0f;
if
(keyboardState.IsKeyDown(Keys.Down))
rotationAmount.Y = 1.0f;
// Scale
rotation amount to radians per second
rotationAmount = rotationAmount * RotationRate * elapsed;
// Correct
the X axis steering when the ship is upside down
if (Up.Y <
0)
rotationAmount.X = -rotationAmount.X;
// Create
rotation matrix from rotation amount
Matrix
rotationMatrix = Matrix.CreateFromAxisAngle(Right, rotationAmount.Y) *
Matrix.CreateRotationY(rotationAmount.X);
// Rotate
orientation vectors
objectDirection = Vector3.TransformNormal(objectDirection, rotationMatrix);
Up =
Vector3.TransformNormal(Up, rotationMatrix);
//
Re-normalize orientation vectors
// Without
this, the matrix transformations may introduce small rounding
// errors
which add up over time and could destabilize the ship.
objectDirection.Normalize();
Up.Normalize();
//
Re-calculate Right
right =
Vector3.Cross(objectDirection, Up);
// The same
instability may cause the 3 orientation vectors may
// also
diverge. Either the Up or Direction vector needs to be
//
re-computed with a cross product to ensure orthagonality
Up =
Vector3.Cross(Right, objectDirection);
//******************************************************************
// Determine
thrust amount from input.
//float
thrustAmount = gamePadState.Triggers.Right;
float
thrustAmount = 0;
if
(keyboardState.IsKeyDown(Keys.Space))
thrustAmount = 1.0f;
// Calculate
force from thrust amount
Vector3 force
= objectDirection * thrustAmount * MaxForce;
// Apply
acceleration
Vector3
acceleration = force / Mass;
objectVelocity += acceleration * elapsed;
// Apply
psuedo drag
objectVelocity *= DragFactor;
// Apply
velocity
objectPosition += objectVelocity * elapsed;
// Prevent
ship from flying under the ground
objectPosition.Y = Math.Max(objectPosition.Y, MinimumAltitude);
//
Reconstruct the ship's world matrix
world =
Matrix.Identity;
world.Forward
= objectDirection;
world.Up =
Up;
world.Right =
right;
world.Translation = objectPosition;
}
#endregion
}
}
The camera code
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
namespace Final
{
public class CameraFollow
{
#region ---- Declarations ----
#region ---- Declare vairables and
properties of the object we are following ----
// Position of object we are
following.
public Vector3 ObjectPosition
{
get { return
objectPosition; }
set {
objectPosition = value; }
}
private Vector3 objectPosition;
// Direction the object is facing.
public Vector3 ObjectDirection
{
get { return
objectDirection; }
set {
objectDirection = value; }
}
private Vector3 objectDirection;
// The object's Up vector.
public Vector3 Up
{
get { return
up; }
set { up =
value; }
}
private Vector3 up = Vector3.Up;
#endregion
#region ---- Set camera position
realtive to object and in world space ----
// Desired camera position relative
to the object.
public Vector3
DesiredCameraPositionOffset
{
get { return
desiredCameraPositionOffset; }
set {
desiredCameraPositionOffset = value; }
}
private Vector3
desiredCameraPositionOffset = new Vector3(0, 2.0f, 2.0f);
// Desired camera position in world
space.
public Vector3 DesiredCameraPosition
{
get
{
// Ensure correct value even if update has not been called this frame
// Defined below
UpdateWorldPositions();
return desiredCameraPosition;
}
}
private Vector3
desiredCameraPosition;
// Where the camera is looking at
based on The Object's coordinates
public Vector3 LookAtOffset
{
get { return
lookAtOffset; }
set {
lookAtOffset = value; }
}
private Vector3 lookAtOffset = new
Vector3(0, 2.8f, 0);
/// Look at point in world space.
public Vector3 LookAt
{
get
{
// Ensure correct value even if update has not been called this frame
UpdateWorldPositions();
return lookAt;
}
}
private Vector3 lookAt;
#endregion
#region ---- Setting physics values
for the camera ---
// Physics coefficient which controls
the influence of the camera's position
// over the spring force. The stiffer
the spring, the closer it will stay to
// the chased object.
public float Stiffness
{
get { return
stiffness; }
set {
stiffness = value; }
}
private float stiffness =1200.0f;
// Physics coefficient which
approximates internal friction of the spring.
// Sufficient damping will prevent
the spring from oscillating infinitely.
public float Damping
{
get { return
damping; }
set { damping
= value; }
}
private float damping = 600.0f;
// Mass of the camera body. Heaver
objects require stiffer springs with less
// damping to move at the same rate
as lighter objects.
public float Mass
{
get { return
mass; }
set { mass =
value; }
}
private float mass = 50.0f;
#endregion
#region ---- Position and velocity of
camera ----
// Position of camera in world space.
public Vector3 Position
{
get { return
position; }
}
private Vector3 position;
// Velocity of camera.
public Vector3 Velocity
{
get { return
velocity; }
}
private Vector3 velocity;
#endregion
#region ---- Perspective properties
----
// Perspective aspect ratio. Default
value should be overriden by application.
public float AspectRatio
{
get { return
aspectRatio; }
set {
aspectRatio = value; }
}
private float aspectRatio = 4.0f /
3.0f;
// Perspective field of view.
public float FieldOfView
{
get { return
fieldOfView; }
set {
fieldOfView = value; }
}
private float fieldOfView =
MathHelper.ToRadians(45.0f);
// Distance to the near clipping
plane.
public float
NearClippingPlaneDistance
{
get { return
nearClippingPlaneDistance; }
set {
nearClippingPlaneDistance = value; }
}
private float
nearClippingPlaneDistance = 1.0f;
// Distance to the far clipping
ClippingPlane.
public float FarClippingPlaneDistance
{
get { return
farClippingPlaneDistance; }
set {
farClippingPlaneDistance = value; }
}
private float
farClippingPlaneDistance = 10000.0f;
#endregion
#region ---- Matrix properties ----
// View transform matrix.
public Matrix View
{
get { return
view; }
}
private Matrix view;
// Projecton transform matrix.
public Matrix Projection
{
get { return
projection; }
}
private Matrix projection;
#endregion
#endregion
//Actual Methods below
#region ---- UpdateWorldPositions
----
// translates coordinates that are
relative to the object into real world positions
// this is used by the camera since
it's position is realtive to the object's
private void UpdateWorldPositions()
{
// Construct
a matrix to transform from object space to worldspace
// always
need a forward, right and up vector.
Matrix
transform = Matrix.Identity;
transform.Forward = ObjectDirection;
transform.Up
= Up;
// this does
the cross product
transform.Right = Vector3.Cross(Up, ObjectDirection);
// Calculate
desired camera properties in world space
desiredCameraPosition = ObjectPosition +
Vector3.TransformNormal(DesiredCameraPositionOffset, transform);
lookAt =
ObjectPosition + Vector3.TransformNormal(LookAtOffset, transform);
}
#endregion
#region ---- UpdateMatricies ----
// Rebuilds camera's view and
projection matricies.
private void UpdateMatrices()
{
view =
Matrix.CreateLookAt(this.Position, this.LookAt, this.Up);
projection =
Matrix.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio,
NearClippingPlaneDistance, FarClippingPlaneDistance);
}
#endregion
#region ---- Reset ----
// Forces camera to be at desired
position and to stop moving. The is useful
// when the object is first created
or after it has been teleported back to original position.
// Failing to call this after a large
change to the chased object's position
// will result in the camera quickly
flying across the world.
public void Reset()
{
UpdateWorldPositions();
// Stop
motion
velocity =
Vector3.Zero;
// Force
desired position
position =
desiredCameraPosition;
UpdateMatrices();
}
#endregion
#region ---- Update ----
// This animates the camera everytime
theCamera.Update(gameTime) is called in the Game1 class
public void Update(GameTime gameTime)
{
if (gameTime
== null)
throw new ArgumentNullException("gameTime");
UpdateWorldPositions();
float elapsed
= (float)gameTime.ElapsedGameTime.TotalSeconds;
// Calculate
spring force
Vector3
stretch = position - desiredCameraPosition;
Vector3 force
= -stiffness * stretch - damping * velocity;
// Apply
acceleration
Vector3
acceleration = force / mass;
velocity +=
acceleration * elapsed;
// Apply
velocity
position +=
velocity * elapsed;
UpdateMatrices();
}
#endregion
}
}
The game1 code
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace Final
{
public class Game1 : Microsoft.Xna.Framework.Game
{
#region ---- Variable Declarations
----
GraphicsDeviceManager graphics;
ContentManager content;
// for 2d graphics typcially health
bars etc.
SpriteBatch spriteBatch;
SpriteFont spriteFont;
KeyboardState lastKeyboardState = new
KeyboardState();
KeyboardState currentKeyboardState =
new KeyboardState();
CameraFollow theCamera;
theObject theObject = new
theObject();
Model objectModel;
Model groundModel;
bool cameraSpringEnabled = true;
#endregion
#region ---- Game1 and Initialize
----
public Game1()
{
graphics =
new GraphicsDeviceManager(this);
content = new
ContentManager(Services);
graphics.PreferredBackBufferWidth = 1440;
graphics.PreferredBackBufferHeight = 900;
theCamera =
new CameraFollow();
// Set the
theCamera offsets
theCamera.DesiredCameraPositionOffset = new Vector3(0.0f, 2000.0f, 3500.0f);
theCamera.LookAtOffset = new Vector3(0.0f, 150.0f, 0.0f);
// Set
theCamera perspective
theCamera.NearClippingPlaneDistance = 10.0f;
theCamera.FarClippingPlaneDistance = 100000.0f;
}
/// <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()
{
base.Initialize();
// Set the
camera aspect ratio
// This must
be done after the class to base.Initalize() which will
// initialize
the graphics device.
// this will
set the Aspect ratio equal to the entire screen
theCamera.AspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
graphics.GraphicsDevice.Viewport.Height;
// Perform an
inital reset on the camera so that it starts resting behind the object.
// If we
don't do this, the camera will start at the origin and
// fly across
the world to get behind the object.
// This is
performed here because the aspect ratio is needed by Reset.
UpdateCameraObjectTarget();
theCamera.Reset();
}
#endregion
#region ---- Load / Unload content
----
/// <summary>
/// Load your graphics content.
If loadAllContent is true, you should
/// load content from both
ResourceManagementMode pools. Otherwise, just
/// load
ResourceManagementMode.Manual content.
/// </summary>
/// <param
name="loadAllContent">Which type of content to load.</param>
protected override void
LoadGraphicsContent(bool loadAllContent)
{
if
(loadAllContent)
{
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
spriteFont = content.Load<SpriteFont>("Content/Arial");
objectModel = content.Load<Model>("Content/p1_wedge");
groundModel = content.Load<Model>("Content/Ground");
}
}
/// <summary>
/// Unload your graphics
content. If unloadAllContent is true, you should
/// unload content from both
ResourceManagementMode pools. Otherwise, just
/// unload
ResourceManagementMode.Manual content. Manual content will get
/// Disposed by the GraphicsDevice
during a Reset.
/// </summary>
/// <param
name="unloadAllContent">Which type of content to unload.</param>
protected override void
UnloadGraphicsContent(bool unloadAllContent)
{
if
(unloadAllContent)
{
content.Unload();
}
}
#endregion
#region ---- Update ----
/// <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)
{
lastKeyboardState = currentKeyboardState;
currentKeyboardState = Keyboard.GetState();
// Allows the
game to exit
if
(currentKeyboardState.IsKeyDown(Keys.Escape))
this.Exit();
// This
switches whether or not spring physicis is on
if
(lastKeyboardState.IsKeyUp(Keys.A) &&
(currentKeyboardState.IsKeyDown(Keys.A)) )
{
cameraSpringEnabled = !cameraSpringEnabled;
}
// Reset to
starting position if R is pressed
if
(currentKeyboardState.IsKeyDown(Keys.R))
{
theObject.Reset();
theCamera.Reset();
}
// Update the
object
theObject.Update(gameTime);
// Update the
camera to chase the new target
UpdateCameraObjectTarget();
// The chase
camera's update behavior is the springs, but we can
// use the
Reset method to have a locked, spring-less camera
if
(cameraSpringEnabled)
theCamera.Update(gameTime);
else
theCamera.Reset();
base.Update(gameTime);
}
// Update the values to be chased by
the camera
private void
UpdateCameraObjectTarget()
{
theCamera.ObjectPosition = theObject.objectPosition;
theCamera.ObjectDirection = theObject.objectDirection;
theCamera.Up
= theObject.Up;
}
#endregion
#region ---- Draw ----
/// <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)
{
//graphics.GraphicsDevice.Clear(Color.DarkSeaGreen);
GraphicsDevice theDevice = graphics.GraphicsDevice;
theDevice.Clear(Color.DarkSeaGreen);
DrawModel(objectModel, theObject.World);
DrawModel(groundModel, Matrix.Identity);
DrawOverlayText();
base.Draw(gameTime);
}
// Simple model drawing method. The
interesting part here is that
// the view and projection matrices
are taken from theCamera object.
private void DrawModel(Model model,
Matrix world)
{
Matrix[]
transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
foreach
(ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.PreferPerPixelLighting = true;
effect.World = transforms[mesh.ParentBone.Index] * world;
// Use the matrices provided by the chase camera
effect.View = theCamera.View;
effect.Projection = theCamera.Projection;
}
mesh.Draw();
}
}
// Display the instructions in 2d on
the screen
private void DrawOverlayText()
{
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred,
SaveStateMode.SaveState);
string text =
"Spacebar = thrust\n" +
"Arrow keys = steer\n" +
"R = reset position of ship to middle\n" +
"A = toggle camera spring (" + (cameraSpringEnabled ?
"on" : "off") + ")\n" +
"Esc = exit";
// Draw the
string twice to create a drop shadow, first colored black
// and offset
one pixel to the bottom right, then again in white at the
// intended
position. This makes text easier to read over the background.
spriteBatch.DrawString(spriteFont, text, new Vector2(65, 65), Color.Black);
spriteBatch.DrawString(spriteFont, text, new Vector2(64, 64), Color.White);
spriteBatch.End();
}
#endregion
}
}