Davis-Project2

Select portions of this project have been included on this wiki page. The project, in its entirety can be found here:


Both projects 2 and 3 will focus on 3D game programming using XNA Game Studio 3.1/C#. This project (project 2) will introduce 3D game programming concepts and demonstrate my knowledge of the concepts introduced in the tutorials. Project 3 will implement a game using these concepts with a partner/classmate.
 * Project Scope**

Many of the basic game programming concepts, such as game design, apply to both 2D and 3D game programming and were discussed in Project 1. Several of the XNA specific concepts, such as predefined methods, will be discussed again here for completeness.


 * Differences between 2D and 3D Game Development**

In 2D game development, position, orientation, and motion can be represented by using Vector2s and a single rotation angle. A 3D game introduces the concept of a third dimension and a whole host of new objects. The 3D world differs from the 2D world by the inclusion of a third coordinate—the Z, or depth coordinate.

A point has an X, a Y, and a Z coordinate. Much like a point in 2D space, a point in 3D space can be moved by using a vector, but in 3D space, we use a Vector3, which contains a Z coordinate. In both 2D games and 3D games, a positive Y coordinate is up while a negative Y coordinate is down and a positive X coordinate is right while a negative X coordinate is left. With the addition of depth in 3D programming, a positive Z coordinate is forward, while a negative Z coordinate is backwards.

Much more can be accomplished with a 3D point, however, by using mathematical structures that are called matrices. These structures, which are represented by the XNA Framework class Matrix, describe a range of transformations that can be applied to a point, a set of points, or a vector. By using matrices, 3D objects can be moved, rotated, scaled, and—through the use of special matrices called view matrices and projection matrices—drawn to a 2D image that can be displayed on the screen.

The change to 3D means that instead of drawing static Texture2D objects on a flat world, 3D models will be used – represented by Model objects. These are collections of connected 3D points called meshes, in a full world, where the final rendering of the objects to the screen depends on the position and orientation of the objects, and on the position and orientation of a camera through which the entire scene is rendered. Programming for a 3D world relies on only a few very fundamental elements. Points have their own position and orientation information relative to some center point, called the origin. Without any transformation, the points merely exist in their own space, called object space.

When this set of points is loaded and added to the 3D world, we give them a position in the 3D world, to rotate, and/or to scale them. The product of these operations is called a world transform matrix. After the matrix is applied to the 3D points, the points are considered to be in world space.

To render the points to the screen, additional information is needed. It is not possible to translate an entire 3D scene down to a 2D image without information about the angle and position from which to draw the scene. It is necessary to use a viewport, also known as a camera, which looks at the 3D scene and draws only what it sees. In preparation for this drawing, the camera information is computed into a view matrix, which orients and positions the 3D points relative to the camera. The 3D points are then considered to be in view space.

Finally, information about how to draw the 3D scene in 2D is needed, such as perspective and field of view. This information is stored in a projection matrix, and when applied, brings the points from a 3D coordinate system into a 2D coordinate system, called screen space, where they can be drawn as pixels.


 * Drawing a 3D Model onto the screen**

Drawing a 3D model on the screen is a complex action. It involves the rendering of a large number of polygons, which are often textured in various ways. Some may be art-based, and some may be programmatic. Drawing is also an essential action. Every 3D object that is to be visually represented in a game passes through the drawing sequence.

There is no single way to structure a model drawing method. The parameters, bounds, and drawing algorithms for each game differ, depending on the needs and techniques desired in the game. The XNA Framework does not try to provide a single model drawing method. It instead provides the elements and leaves the task of combining them to the programmer.

A Model is the fundamental object in the XNA Framework that represents a 3D model A model is composed of one or more ModelMesh objects that exist in a collection. Each ModelMesh is a collection of vertices that make up a mesh, which can be moved independently of other ModelMesh objects in the same Model.

The ModelMesh function contains the Draw method. This method is called once for each ModelMesh drawn to the screen. Without setting up proper transforms and lighting on the ModelMesh first, however, you do not see anything.

Transforms determine where the ModelMesh is oriented in the world, and ultimately, if and where the object appears on the screen.

Lighting determines how textures associated with the ModelMesh are displayed, and are not applied at the ModelMesh level. Each ModelMesh has a number of Effects that are associated with it. An Effect can be thought of as instructions to the graphics device on how to set up a ModelMesh to draw.

Effects are generally made up of complicated vertex and pixel shaders. This is possible in XNA Game Studio, and it is necessary for advanced graphical effects. But for simple rendering, the XNA Framework Content Pipeline provides an easy mechanism for basic lighting and drawing called BasicEffect.

Every ModelMesh that is loaded through the content pipeline receives one or more BasicEffect objects, which can be used to set simple transforms and lighting without having to deal with pixel or vertex shaders. BasicEffect only requires a set of Matrix objects that correspond to World, View, and Projection matrices for the ModelMesh, and some lighting parameters. Default parameters can be set by calling the BasicEffect.EnableDefaultLighting method. After the BasicEffect parameters are set, all that remains is to call ModelMesh.Draw, to draw the ModelMesh to the screen by using the parameters that are passed into BasicEffect.

3D models are created by using 3D modeling software. In these software packages, the artist creates the model in a 3D world. The world has a center, called an origin, and the model is built relative to this center. The position, rotation, and scale are all relative to this local origin, and when a game loads a model, it keeps the relative position, rotation, and scale. The model is said to be in object space or model space.

When the game draws a model, it lines up the origin of the model’s space with the origin of the game world. If the model’s center was exactly at the origin of model space when it was saved in the 3D modeling software, then it will show up at the world origin when drawn in the game.


 * Transforming the 3D Model**

When a 3D model is drawn in the game, three transformations need to be applied. The first of the three, the world transformation, identifies how to transform your model from its initial position, orientation, and scale (called object space), into a position, orientation, and scale somewhere in the game world.

A standard world transformation is made up of three separate transformation matrices—rotation, scale, and translation—all combined into one world transformation matrix. code format="csharp" effect.World = Matrix.CreateFromYawPitchRoll(gameobject.rotation.Y, gameobject.rotation.X, gameobject.rotation.Z) * Matrix.CreateScale(gameobject.scale) * Matrix.CreateTranslation(gameobject.position); code
 * Rotation** changes the orientation angle of the model. There are several ways to calculate and store rotation values, one of the most popular being Euler angles, which rotate an object around the three axes of the 3D world: X, Y, and Z. To create a rotation matrix, you can use the Matrix.CreateFromYawPitchRoll method, passing in the rotation angle around each axis. These angles are stored as a Vector3 type.


 * Scale** changes the size of the model, making it either bigger or smaller. While it is possible to make the model stretch along one axis or another, it is more common to apply a uniform scale, which makes the entire model shrink or grow. To create a scale matrix, use the Matrix.CreateScale method, passing in the factor by which you want to shrink or grow the model. The scale factor for these game objects are stored as a simple float type.


 * Translation** moves the entire model along the three axes of the 3D world. Side to side (X), up and down (Y), and forward and back (Z). The translation matrix can be created using the Matrix.CreateTranslation method, passing in the amounts by which we want to move the model along each of the three axes. The translation values can be stored using a Vector3 type.

Once these matrices are created, they must be combined into a single world transformation matrix. This process is called concatenation using the multiplication operator (*). World Transformation = Rotation * Scale * Translation code format="csharp" effect.World = Matrix.CreateFromYawPitchRoll(gameobject.rotation.Y, gameobject.rotation.X, gameobject.rotation.Z) * Matrix.CreateScale(gameobject.scale) * Matrix.CreateTranslation(gameobject.position); code The concatenation order is important because matrix multiplication is not commutative. Multiplying in the wrong order yields undesired results—accidentally translating before rotating, for example, rotates the translation of the model around the world origin, leaving your model far off somewhere in world space, rather than simply rotating the model.

By changing the values you input into your individual transform matrices over time, you can animate an object moving across the world (translation), an object rotating in space (rotation), an object smoothly shrinking or growing (scale), or a combination of the three.

An essential part of any world transformation matrix is rotation. Rotation corresponds to the angular orientation of an object, compared to a particular set of axes. The rotation of an object is created by using a matrix that is concatenated with the translation and scale matrices to form the final world transform matrix. The method that is used here involves a set of intermediate rotations called Euler rotations. code format="csharp" missleLauncherHead.rotation.Y -= gamePadState.ThumbSticks.Left.X * 0.1f; missleLauncherHead.rotation.X += gamePadState.ThumbSticks.Left.Y * 0.1f; KeyboardState keyboardState = Keyboard.GetState(PlayerIndex.One); if(keyboardState.IsKeyDown(Keys.Left)) { missleLauncherHead.rotation.Y += 0.05f; } if (keyboardState.IsKeyDown(Keys.Right)) { missleLauncherHead.rotation.Y -= 0.05f; } if (keyboardState.IsKeyDown(Keys.Up)) { missleLauncherHead.rotation.X += 0.05f; } if (keyboardState.IsKeyDown(Keys.Down)) { missleLauncherHead.rotation.X -= 0.05f; }
 * 1) if !XBOX

missleLauncherHead.rotation.Y = MathHelper.Clamp(missleLauncherHead.rotation.Y, -MathHelper.PiOver4, MathHelper.PiOver4); missleLauncherHead.rotation.X = MathHelper.Clamp(missleLauncherHead.rotation.X, 0, MathHelper.PiOver4); code The XNA Framework uses immediate-mode input for all three supported input types—the Xbox 360 controller, the keyboard, and the mouse. This means that for any input device, you can query the current state of the input device at any time. You cannot, however, ask for any previous state of a device, unless you have explicitly stored it somewhere. Previous input states are necessary to know if the user is pressing a button rapidly or simply holding down the button. In this game, only repeated presses should get a result.
 * 1) endif

Storing a previous state alleviates this problem. For each Update loop, process input as normal, but at the end of the loop, save the current state in a variable. This variable becomes the previous state. To check for button presses rather than held-down buttons, check that the current state of the button is Pressed, and that the previous state of the button is Released. code format="csharp" GamePadState previousState; KeyboardState previousKeyboardState; code
 * 1) if !XBOX
 * 1) endif


 * XNA Predefined Methods**

XNA Game Studio includes several predefined methods:


 * Game1 Constructor**

The Game1 method is called once, when the project is run. This is where code consisting of GameComponent class instantiations should be added. No resources can be accessed since they haven’t been initialized at this point.


 * LoadContent Method**

The LoadContent method is called once – at the beginning of the project. Upon starting the game, art such as images, models, and audio should be loaded from disk. XNA manages this art through the content pipeline. code format="csharp" protected override void LoadContent { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); audioEngine = new AudioEngine("Content\\Audio\\Audio2.xgs"); waveBank = new WaveBank(audioEngine, "Content\\Audio\\Wave Bank.xwb"); soundBank = new SoundBank(audioEngine, "Content\\Audio\\Sound Bank.xsb"); cameraViewMatrix = Matrix.CreateLookAt(cameraPosition, cameraLookAt, Vector3.Up); cameraProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), graphics.GraphicsDevice.Viewport.AspectRatio, 1.0f, 10000.0f); terrain.model = Content.Load("Models\\terrain"); missleLauncherBase.model = Content.Load("Models\\launcher_base"); missleLauncherBase.scale = 0.2f; missleLauncherHead.model = Content.Load("Models\\launcher_head"); missleLauncherHead.scale = 0.2f; missleLauncherHead.position = missleLauncherBase.position + new Vector3(0.0f, 20.0f, 0.0f); missiles = new GameObject[numMissles]; for (int i = 0; i < numMissles; i++) { missiles[i] = new GameObject; missiles[i].model = Content.Load("Models\\missile"); missiles[i].scale = 3.0f; } enemyShips = new GameObject[numEnemyShips]; for (int i = 0; i < numEnemyShips; i++) { enemyShips[i] = new GameObject; enemyShips[i].model = Content.Load("Models\\enemy"); enemyShips[i].scale = 0.1f; enemyShips[i].rotation = new Vector3(0.0f, MathHelper.Pi, 0.0f); } // TODO: use this.Content to load your game content here } code
 * Initialize Method**

The Initialize method is also called once, after the internal initialization is done. This method is where the initial values of the game objects (starting positions, speeds, etc.) should be set. At this point, the full resources of the Game object are available. code format="csharp" GraphicsDeviceManager graphics; SpriteBatch spriteBatch; GameObject terrain = new GameObject; GameObject missleLauncherBase = new GameObject; GameObject missleLauncherHead = new GameObject; const int numMissles = 20; GameObject[] missiles; GamePadState previousState; KeyboardState previousKeyboardState; const float launcherHeadMuzzleOffset = 20.0f; const float missilePower = 20.0f; AudioEngine audioEngine; SoundBank soundBank; WaveBank waveBank; Random r = new Random; const int numEnemyShips = 3; GameObject[] enemyShips; Vector3 shipMinPosition = new Vector3(-2000.0f, 300.0f, -6000.0f); Vector3 shipMaxPosition = new Vector3(2000.0f,800.0f,-4000.0f); const float shipMinVelocity = 5.0f; const float shipMaxVelocity = 10.0f; Vector3 cameraPosition = new Vector3(0.0f, 60.0f, 160.0f); Vector3 cameraLookAt = new Vector3(0.0f, 50.0f, 0.0f); Matrix cameraProjectionMatrix; Matrix cameraViewMatrix; code
 * 1) if !XBOX
 * 1) endif
 * Update Method**

During runtime, XNA calls this method 60 times per second by default. This makes the Update method the ideal place to put the code that updates the game’s logic. This logic could include updating the positions of objects, checking to see if objects collide, starting explosions at that position, increasing the score, processing user input, and updating camera/model matrices. code format="csharp" protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit; GamePadState gamePadState = GamePad.GetState(PlayerIndex.One); missleLauncherHead.rotation.Y -= gamePadState.ThumbSticks.Left.X * 0.1f; missleLauncherHead.rotation.X += gamePadState.ThumbSticks.Left.Y * 0.1f; KeyboardState keyboardState = Keyboard.GetState(PlayerIndex.One); if(keyboardState.IsKeyDown(Keys.Left)) { missleLauncherHead.rotation.Y += 0.05f; } if (keyboardState.IsKeyDown(Keys.Right)) { missleLauncherHead.rotation.Y -= 0.05f; } if (keyboardState.IsKeyDown(Keys.Up)) { missleLauncherHead.rotation.X += 0.05f; } if (keyboardState.IsKeyDown(Keys.Down)) { missleLauncherHead.rotation.X -= 0.05f; }
 * 1) if !XBOX

missleLauncherHead.rotation.Y = MathHelper.Clamp(missleLauncherHead.rotation.Y, -MathHelper.PiOver4, MathHelper.PiOver4); missleLauncherHead.rotation.X = MathHelper.Clamp(missleLauncherHead.rotation.X, 0, MathHelper.PiOver4); if (gamePadState.Buttons.A == ButtonState.Pressed && previousState.Buttons.A == ButtonState.Released) { FireMissile; } if (keyboardState.IsKeyDown(Keys.Space) && previousKeyboardState.IsKeyUp(Keys.Space)) { FireMissile; } UpdateMissiles; audioEngine.Update; UpdateEnemyShips;
 * 1) endif
 * 1) if !XBOX
 * 1) endif

previousState = gamePadState; previousKeyboardState = keyboardState; // TODO: Add your update logic here base.Update(gameTime); } code
 * 1) if !XBOX
 * 1) endif
 * Draw Method**

The Draw method is called at the same frequency as the screen refresh rate (by default). This is where code should be placed that renders the scene to the screen, including all 2D images, 3D objects, and explosions to the screen, as well as display the current score. code format="csharp" protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Black); DrawGameObject(terrain); DrawGameObject(missleLauncherBase); DrawGameObject(missleLauncherHead); foreach (GameObject missile in missiles) { if (missile.alive) { DrawGameObject(missile); } } foreach (GameObject enemyship in enemyShips) { if (enemyship.alive) { DrawGameObject(enemyship); } } // TODO: Add your drawing code here base.Draw(gameTime); } void DrawGameObject(GameObject gameobject) { foreach (ModelMesh mesh in gameobject.model.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting; effect.PreferPerPixelLighting = true; effect.World = Matrix.CreateFromYawPitchRoll(gameobject.rotation.Y, gameobject.rotation.X, gameobject.rotation.Z) * Matrix.CreateScale(gameobject.scale) * Matrix.CreateTranslation(gameobject.position); effect.Projection = cameraProjectionMatrix; effect.View = cameraViewMatrix; } mesh.Draw; } } code
 * UnloadContent Method**

The UnloadContent method is called once, before the game exits. If some of the game objects in the game require specific disposing or unloading, this is the ideal spot to do it. For this game, it was not necessary to modify the UnloadContent method.


 * Incorporating Sounds using XACT**

XACT, or the Microsoft Cross-Platform Audio Creation Tool is included with XNA Game Studio.It is a cross-platform tool that produces audio output to use on Windows and on Xbox 360. The design of XACT allows audio to be designed independent of the platform it is used on.

The tool is composed of two main parts: an audio designer and the audio API. The audio designers is the graphical tool that you use and is meant to be accessible to anyone who wants to design audio for a game, even if they are unfamiliar with audio code.

The audio designer lays out all of the raw waveform audio files (.wav files, called waves) to use. The designer then arranges the waves into sounds, which can play one or more waves at various times and settings. Finally, the audio designer arranges the sounds into cues. Cues represent the objects that are accessed in code, and are more like game events than actual audio files.

The cues are exposed to the audio programmer, who then calls the cues when the appropriate game events happen, by using the XACT API. The audio designer can also design runtime parameters to modify the volume or pitch of playing sounds, and pass these parameters to the programmer, who modifies the parameters by using variables in the game code.

Every XACT project contains one or more wave banks, which are collections of waveform audio files, called waves. The waves in the wave bank are not accessed directly by the XACT API. Instead, they are arranged by the audio designer into sounds. These sounds are arranged in one or more sound banks, which are collections of sounds. Sounds are instructions to play one or more waves with various parameters such as looping, transition effects, random choice of waves, and so on. A sound bank also contains cues. Cues are instructions to play sounds. A cue calls a particular sound, or a random selection of sounds, depending on how the cue is set up by the audio designer.

When an XACT project is built, the sound banks and wave banks are compiled into files, together with a global settings file that contains specific information about the project. These files are loaded at runtime by using calls in the XNA Framework.

After the sound banks, wave banks, and global settings file are loaded in the game, we can play any cue in the sound banks by calling SoundBank.PlayCue and passing in the name of the cue as defined by the audio designer. This plays the cue as it is designed by the audio designer, with no control held by the programmer.

Audio content is different from most other content types in XNA Game Studio. Unlike models, textures, or fonts, audio content that is generated by XACT is not loaded at runtime by using the ContentManager.Load method. Instead, the constructors of the AudioEngine, SoundBank, and WaveBank audio classes in the XNA Framework are used to load the built audio files. It is important to load these files in the correct order, and to identify them by name and extension.

In Game1.cs, we initialize the following: code format="csharp" AudioEngine audioEngine; SoundBank soundBank; WaveBank waveBank; code

We then load the following in the LoadContent method: code format="csharp" audioEngine = new AudioEngine("Content\\Audio\\Audio2.xgs"); waveBank = new WaveBank(audioEngine, "Content\\Audio\\Wave Bank.xwb"); soundBank = new SoundBank(audioEngine, "Content\\Audio\\Sound Bank.xsb"); code

We can then “play” the sounds as necessary: code format="csharp" void FireMissile { foreach (GameObject missile in missiles) { if (!missleLauncherBase.alive) { soundBank.PlayCue("missilelaunch"); missile.velocity = GetMissileMuzzleVelocity; missile.position = GetMissileMuzzlePosition; missile.rotation = missleLauncherHead.rotation; missile.alive = true; break; } } } void TestCollision(GameObject missile) { BoundingSphere missilesphere = missile.model.Meshes[0].BoundingSphere; missilesphere.Center = missile.position; missilesphere.Radius *= missile.scale; foreach (GameObject ship in enemyShips) { if (ship.alive) { BoundingSphere shipsphere = ship.model.Meshes[0].BoundingSphere; shipsphere.Center = ship.position; shipsphere.Radius *= ship.scale; if(shipsphere.Intersects(missilesphere)) { soundBank.PlayCue("explosion"); missile.alive = false; ship.alive = false; } } } } code

The game requires the XNA Framework Redistributable 3.1 and the .NET 3.5 Framework installed to properly execute. XNA utilizes Visual Studio’s ClickOnce technology, allowing a single .exe file that checks whether these packages are present, and install the packages if they are absent. To create a Single ClickOnce Installation Package containing the aforementioned prerequisites:
 * Deploying the game to another PC**


 * Right-click the project in the Solution Explorer
 * Select Publish at the bottom-left.
 * Click the Prerequisites button on the next screen
 * Select .NET Framework 3.5 (SP1) and Microsoft XNA Framework Redistributable 3.1 in the list of packages that can be packed with the game.
 * Select “Download prerequisites from the same location as my application”.
 * Hit OK, and close the Properties window.
 * When the project needs to be published, just right-click the project, select Publish, and click Finish in the resulting dialog box.

code format="csharp" 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; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace project2_3D { /// /// This is the main type for your game /// public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; GameObject terrain = new GameObject; GameObject missleLauncherBase = new GameObject; GameObject missleLauncherHead = new GameObject; const int numMissles = 20; GameObject[] missiles; GamePadState previousState; KeyboardState previousKeyboardState; const float launcherHeadMuzzleOffset = 20.0f; const float missilePower = 20.0f; AudioEngine audioEngine; SoundBank soundBank; WaveBank waveBank; Random r = new Random; const int numEnemyShips = 3; GameObject[] enemyShips; Vector3 shipMinPosition = new Vector3(-2000.0f, 300.0f, -6000.0f); Vector3 shipMaxPosition = new Vector3(2000.0f,800.0f,-4000.0f); const float shipMinVelocity = 5.0f; const float shipMaxVelocity = 10.0f; Vector3 cameraPosition = new Vector3(0.0f, 60.0f, 160.0f); Vector3 cameraLookAt = new Vector3(0.0f, 50.0f, 0.0f); Matrix cameraProjectionMatrix; Matrix cameraViewMatrix; public Game1 { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } /// /// 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. /// protected override void Initialize { // TODO: Add your initialization logic here base.Initialize; } /// /// LoadContent will be called once per game and is the place to load /// all of your content. /// protected override void LoadContent { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); audioEngine = new AudioEngine("Content\\Audio\\Audio2.xgs"); waveBank = new WaveBank(audioEngine, "Content\\Audio\\Wave Bank.xwb"); soundBank = new SoundBank(audioEngine, "Content\\Audio\\Sound Bank.xsb"); cameraViewMatrix = Matrix.CreateLookAt(cameraPosition, cameraLookAt, Vector3.Up); cameraProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), graphics.GraphicsDevice.Viewport.AspectRatio, 1.0f, 10000.0f); terrain.model = Content.Load("Models\\terrain"); missleLauncherBase.model = Content.Load("Models\\launcher_base"); missleLauncherBase.scale = 0.2f; missleLauncherHead.model = Content.Load("Models\\launcher_head"); missleLauncherHead.scale = 0.2f; missleLauncherHead.position = missleLauncherBase.position + new Vector3(0.0f, 20.0f, 0.0f); missiles = new GameObject[numMissles]; for (int i = 0; i < numMissles; i++) { missiles[i] = new GameObject; missiles[i].model = Content.Load("Models\\missile"); missiles[i].scale = 3.0f; } enemyShips = new GameObject[numEnemyShips]; for (int i = 0; i < numEnemyShips; i++) { enemyShips[i] = new GameObject; enemyShips[i].model = Content.Load("Models\\enemy"); enemyShips[i].scale = 0.1f; enemyShips[i].rotation = new Vector3(0.0f, MathHelper.Pi, 0.0f); } // TODO: use this.Content to load your game content here } /// /// UnloadContent will be called once per game and is the place to unload /// all content. /// protected override void UnloadContent { // TODO: Unload any non ContentManager content here } /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit; GamePadState gamePadState = GamePad.GetState(PlayerIndex.One); missleLauncherHead.rotation.Y -= gamePadState.ThumbSticks.Left.X * 0.1f; missleLauncherHead.rotation.X += gamePadState.ThumbSticks.Left.Y * 0.1f; KeyboardState keyboardState = Keyboard.GetState(PlayerIndex.One); if(keyboardState.IsKeyDown(Keys.Left)) { missleLauncherHead.rotation.Y += 0.05f; } if (keyboardState.IsKeyDown(Keys.Right)) { missleLauncherHead.rotation.Y -= 0.05f; } if (keyboardState.IsKeyDown(Keys.Up)) { missleLauncherHead.rotation.X += 0.05f; } if (keyboardState.IsKeyDown(Keys.Down)) { missleLauncherHead.rotation.X -= 0.05f; }
 * The Entire Game1.cs**
 * 1) if !XBOX
 * 1) endif
 * 1) if !XBOX

missleLauncherHead.rotation.Y = MathHelper.Clamp(missleLauncherHead.rotation.Y, -MathHelper.PiOver4, MathHelper.PiOver4); missleLauncherHead.rotation.X = MathHelper.Clamp(missleLauncherHead.rotation.X, 0, MathHelper.PiOver4); if (gamePadState.Buttons.A == ButtonState.Pressed && previousState.Buttons.A == ButtonState.Released) { FireMissile; } if (keyboardState.IsKeyDown(Keys.Space) && previousKeyboardState.IsKeyUp(Keys.Space)) { FireMissile; } UpdateMissiles; audioEngine.Update; UpdateEnemyShips;
 * 1) endif
 * 1) if !XBOX
 * 1) endif

previousState = gamePadState; previousKeyboardState = keyboardState; // TODO: Add your update logic here base.Update(gameTime); } void FireMissile { foreach (GameObject missile in missiles) { if (!missleLauncherBase.alive) { soundBank.PlayCue("missilelaunch"); missile.velocity = GetMissileMuzzleVelocity; missile.position = GetMissileMuzzlePosition; missile.rotation = missleLauncherHead.rotation; missile.alive = true; break; } } } Vector3 GetMissileMuzzleVelocity { Matrix rotationMatrix = Matrix.CreateFromYawPitchRoll(missleLauncherHead.rotation.Y, missleLauncherHead.rotation.X, 0); return Vector3.Normalize(Vector3.Transform(Vector3.Forward, rotationMatrix)) * missilePower;
 * 1) if !XBOX
 * 1) endif

} Vector3 GetMissileMuzzlePosition { return missleLauncherHead.position + (Vector3.Normalize(GetMissileMuzzleVelocity) * launcherHeadMuzzleOffset); } void UpdateMissiles { foreach (GameObject missile in missiles) { if (missile.alive) { missile.position += missile.velocity; if (missile.position.Z < -6000.0f) { missile.alive = false; } else { TestCollision(missile); } } } } void UpdateEnemyShips { foreach (GameObject ship in enemyShips) { if (ship.alive) { ship.position += ship.velocity; if (ship.position.Z > 500.0f) { ship.alive = false; } } else { ship.alive = true; ship.position = new Vector3( MathHelper.Lerp( shipMinPosition.X, shipMaxPosition.X, (float)r.NextDouble),

MathHelper.Lerp( shipMinPosition.Y, shipMaxPosition.Y, (float)r.NextDouble),

MathHelper.Lerp( shipMinPosition.Z, shipMaxPosition.Z, (float)r.NextDouble)); ship.velocity = new Vector3(0.0f, 0.0f, MathHelper.Lerp(shipMinVelocity, shipMaxVelocity, (float)r.NextDouble)); } } } void TestCollision(GameObject missile) { BoundingSphere missilesphere = missile.model.Meshes[0].BoundingSphere; missilesphere.Center = missile.position; missilesphere.Radius *= missile.scale; foreach (GameObject ship in enemyShips) { if (ship.alive) { BoundingSphere shipsphere = ship.model.Meshes[0].BoundingSphere; shipsphere.Center = ship.position; shipsphere.Radius *= ship.scale; if(shipsphere.Intersects(missilesphere)) { soundBank.PlayCue("explosion"); missile.alive = false; ship.alive = false; } } } } /// /// This is called when the game should draw itself. /// /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Black); DrawGameObject(terrain); DrawGameObject(missleLauncherBase); DrawGameObject(missleLauncherHead); foreach (GameObject missile in missiles) { if (missile.alive) { DrawGameObject(missile); } } foreach (GameObject enemyship in enemyShips) { if (enemyship.alive) { DrawGameObject(enemyship); } } // TODO: Add your drawing code here base.Draw(gameTime); } void DrawGameObject(GameObject gameobject) { foreach (ModelMesh mesh in gameobject.model.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting; effect.PreferPerPixelLighting = true; effect.World = Matrix.CreateFromYawPitchRoll(gameobject.rotation.Y, gameobject.rotation.X, gameobject.rotation.Z) * Matrix.CreateScale(gameobject.scale) * Matrix.CreateTranslation(gameobject.position); effect.Projection = cameraProjectionMatrix; effect.View = cameraViewMatrix; } mesh.Draw; } } } } code

code format="csharp" 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; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace project2_3D { class GameObject { public Model model = null; public Vector3 position = Vector3.Zero; public Vector3 rotation = Vector3.Zero; public float scale = 1.0f; public Vector3 velocity = Vector3.Zero; public bool alive = false; } } code
 * Game Object.cs**