| Return to CIS 300. |
|
This week's lab is an introduction to XNA. We'll learn how to use XNA in the Visual Studio 2005 environment and how a basic game is set up by compiling a sample program called ShipDemo. Furthermore, we will then modify the ShipDemo sample in various ways to see a few of the things you can do with XNA.
This lab is not due during this lab period, but it is to your advantage to work through (and/or learn) as much as you can now, while assistance is immediately available. If you run into problems finishing the lab project later, please feel free to contact a staff member for help (Preferably a programming TA).
File: ShipDemo.zip
For this lab we will be using Visual Studio 2005, which is available in the CSUG lab. We could get started by creating a Visual Studio solution and project file, but ShipDemo already comes with these set up. In the future, when you need to make your own project, you can either copy this lab and use its files as a template (reeplacing its code files with your own), or you can read one of the many online tutorials for making your own project. Even though the project has already been created for you, the first time you run Visual Studio 2005, it may prompt you with various questions about what kind of environment you want. We recommend that you indicate you are a C# developer, as that is the language used by XNA.
Once the solution is open, you should be able to compile and run the demo by hitting F5. (Ask a TA if this doesn't work.) Here are some other useful commands in Visual Studio, for future reference:
There should be a list of folders and files on the side of the Visual Studio window (called the Solution Explorer). All of the source code is in ".cs" files beneath the top level folder ShipDemo, while all the graphics and sounds files are in the folder "Content". To edit any source code file, just double click on it. Note that this is also where you would remove files from the project (by pressing Delete on a selected file) and add files to your project (by dragging them from Windows onto a folder in this list). Furthermore, you can create new folders to better organize your files; however, as this requires you to adjust your "namespace" attribute (the C# equivalent of the Java package), for right now you should leave this alone and only edit the files.
In addition to the TAs, you will find it very helpful to make use of the wealth of XNA material online. The following should be enough for you to complete this lab.
To help get familiarized with how to use XNA, we're going to be making a series of modifications to ShipDemo. Before doing so, it might be helpful to look through the code and see how it works. Reading through the first few XNA tutorials above should help you with this. The instructions for controlling the ships are in ShipController.cs. After you understand the code to some degree, your assignment for this lab is to make the following changes (in the order given):
Open the file GameEngine.cs, which contains the main event loop for your game. Locate the method Initalize(); this method is called when your program starts up. This method creates the space ships and initializes them. In general Initialize() does any initialization that does not require data from an external art or sound file (you use LoadContent() to initialize the latter.
As GameEngine extends Microsoft.Xna.Framework.Game, it inherits a few fields from its parent class. Two of these are IsFixedTimeStep and TargetElapsedTime. Put the following code at the beginning of Initialize():
IsFixedTimeStep = true;
TargetElapsedTime = TimeSpan.FromSeconds(1.0/60.0);
When you do this, there should be no change to the program. Now change the 60.0 to different values such as 100.0, 30.0, 600.0, and 5.0. In the file readme.txt which you will submit later, briefly describe what happens (.e. the effect these have on the gameplay and/or graphics). Leave the value as 60.0 when you are done. If you wish to know more about what XNA is doing here, see the page on TargetElapsedTime in Microsoft's XNA documentation.
The size of the game window is determined by the GraphicsDeviceManager object graphics in GameEngine.cs. The size is determined by the fields PreferredBackBufferWidth and PreferredBackBufferHeight. To change the size of the screen, you should set these values in the method Initialize().
Correction: You will need to call graphics.ApplyChanges() after you reset the values to make the screen actually change size.
The default game window starts at 800x400. Try several sizes, including 400x300, 100x1000, and 2000x2000, to see what happens. For each size, try flying the ship around and firing it a bit. You should also windowed and full-screen mode for the various sizes; this is set by the field graphics.IsFullScreen (e.g. set this value to false to get the game to run in a window). In the file readme.txt, answer the following questions:
Leave the game window set to 800x600 when done.
The ships are drawn via the Draw(SpriteBatch) method in Ship.cs. Change the size of the ship by changing the variable scale to something other than "1.0f". Furthermore, change the collision detection code to correctly use the new radius of each ship. The collision detection code is in the same file, in the CheckForCollision(Ship) method. In your readme.txt file, describe what the trend is for different values.
The Ship class has a field called weight that represents the weight of the ship. We want to make the size the square root of the weight. For each of the spriteBatch.Draw commands, replace scale with Math.Sqrt(weight) (do not set the variable scale equal to Math.Sqrt(weight)). After this is done, run the game and observe how well it runs (how smooth the animation seem to be). Taking the square root is a somewhat slow operation, and the change you just made probably causes the square root of the ship's weight to be calculated several times for every frame of animation, for a total of several hundred square roots per second, all for recalculating values that don't change. We want the game to do as little unnecessary work as possible so that it can run faster/smoother, so as an optimization make sure that you do not recalculate the square root of the ship's weight every time, and instead store that value as a private variable in the Ship class that gets set only in the Ship's Initialize function. Compare how well the game ran when you observed it without this optimization to how well it runs with the optimization. How much did the performance improve? In readme.txt, explain why you think it improved as much or as little as it did.
Correction: Weight does not exist. You will have to add it manually to the code. Make it anything you want.
Now try purposely slowing the game down by doing various things (math functions, drawing functions, etc.) lots of times per frame. When something has a noticeable effect, try changing the Visual Studio project configuration from Debug to Release and see if that makes any difference. From what you observed, what can you say about optimizing your game code in general? Note your observations in readme.txt.
Correction: This lab was originally designed for designing in C++. C# does a lot of speculative compiling, and thus it is a lot harder to slow it down than it is to slow down C++. Don't assume that the results should come out a certain way. Just answer the questions above the best you can.
In "wrap-around", when an object hits the edge of the playfield, it should keep going and reappear on the other side. To make this work better you may want to modify the lifespan of shots, and you should implement a "max speed" for the ships (perhaps by changing the "drag" to always affect them even when they are accelerating).
Also, be aware that there are "clipping" issues: you want a ship that is in the process of wrapping around to be drawn partially on one side, and partially on the other, which you can do by drawing the image twice (once on each side of the screen for each screen edge that it is near). Make sure you get the "corner cases" right, and make sure the ship's shadow doesn't do anything noticeably weird or different in the transitions. And, very importantly, you shouldn't need to add a lot of code to do this. Do not just copy-and-paste the drawing code a few times, instead you should find a pattern and wrap it in a loop, or something like that. Make sure a ship that is not in the process of wrapping around the screen does not draw multiple times, though.
In the Draw(SpriteBatch) method for PhotonQueue.cs, you will notice that we use the constant SpriteBlendMode.Additive is used to begin the drawing. Look up the enumeration SpriteBlendMode and try out the other two values (AlphaBlend and None). Now look at the file photon.png in the "Content" folder. Based on what you see, make a conjecture about what each of these three modes do with the graphics file; write your conecture in readme.txt> Pick the draw mode you like best and use that one.
Change the shots to fade out as they get older, so they don't seem to suddenly disappear when they are removed (Hint: Look at the Fade Tutorial online). Also, make the shots look better by doing some or all of the following:
Each of these that you do must be visually noticeable - having code that should work but does not or is too subtle to notice isn't acceptable.
Make each connecting photon reflect at a reasonable angle upon colliding, and also make it knock back the ship it hit a little. You will probably find it easiest to copy (or generalize) the ship-to-ship collision code and treat photons as having some arbitrary weight value that gives reasonable results. Make sure each ship is immune to its own photons (you might want to wait until part of question #10 below is done before doing this last part).
Currently, every time a Photon is fired, the sound plays from start to finish. Change the blue ship (but not the red ship) to stop playing its photon firing sound and starting a new one each time a Photon is fired. This requires making use of the Cue class. See the documentation for how to do this.
Comment out the line of code that draws the background, run the game and describe (in readme.txt) what happens, then un-comment the background-drawing code to re-enable it. Change the appearance of the background (hopefully to something you think looks better or interesting) by playing with the background drawing code. First, try tinting it a different color. Next, change the scaling to get a feel for how that works. Finally, play with the origin, scaling, screen position, and rotation: get the background to continuously (and slowly!) rotate. Do this by continuously incrementing a value in the update method and then scaling it. A common problem you may encounter is that it spins jerkily or doesn't seem to spin continuously, in which case you probably forgot to cast to float or double before dividing (and so were unintentionally performing integer division). Be sure that the background both looks good and you never see a 'corner' of the image, but do not blow it up so much that it's blurry, either.
Change the red ship (but not the blue ship) to fire a different kind of photon. Do this by giving the Photon struct in PhotonQueue.cs an extra variable called "type", and changing AddPhoton to take in and assign the photon type. (If you were concerned about memory usage, how would you do this differently?) The red ship should then be made to add photons of a different "type" than the blue ship does. Once this is done, change the Photons.Draw function to draw the photons of the blue ship's type the same way as before, but to draw the photons of the red ship's type in a different way. To draw these new Photons, do not use a different image. Instead, vary some of the parameters, such as the tinting and scaling of the photons. In addition to some of the ideas from exercise 6, you could also make some changes in the logic of the photons. For instance, the photons could move faster or slower, or even home in on the enemy ship. Get creative. Be sure that the Photons emitted from the red ship are clearly different from those emitted from the blue ship, and that you go hrough and test it to make sure everything looks good.
Change either ship's controls so that moving the mouse leftward turns the ship counter-clockwise, moving the mouse right turns the ship clockwise, moving the mouse up accelerates forward, moving the mouse down accelerates backward, and left-click fires the photons. Do not remove the keyboard input from that ship; mouse and keyboard must both work for it. Make sure the ship does not respond hypersensitively to mouse movement, but moving the mouse quickly should cause more of a reaction than moving it slowly. Also make sure that you can hold down the mouse button for rapid-fire as it was before. As much as possible, make the mouse control "feel natural" to use, which can be achieved with some tweaking (changes elsewhere to turning speed etc. may be necessary). See the XNA documentation for information about getting mouse input with XNA.
Finally, make one more change of your choice that you think we will find impressive, and explain your reasoning in your readme.txt file.
This might seem like a lot of tasks, but hopefully you will find all of them reasonably simple and straightforward. To turn in the lab, create a zip file containing the following files:
The file readme.txt should answer questions and describe anything directly asked in the above task. In addition it should describe (very briefly) how you handled each task and where in the code it was handled.
Submit this zip file as "lab1prog.zip" to CMS.
Due Wednesday January 30, 2008 at 11:59pm