XNA Game-Themed Assignment (XGA)

XGA-600: Implementation Guide


Kelvin Sung
Computing and Software Systems
University of Washington, Bothell
ksung@u.washington.edu
Michael Panitz
Software Programming
Cascadia Community College
mpanitz@cascadia.ctc.edu

1. Topics Covered:

This document explains some of the implementation details found in the example solution to the XGA-600 assignment module.  Please follow this link to find out more about XGA-600 assignment module and all other XGA assignments. Topics covered in this implementation guide include:

 

Pre-Requisite:

It is assumed that you have read the following documents:

  1. The XNA Installation Guide:guides you download and install the XNA SDK and Game Studio Express IDE.

  2. The XNACS1Lib Guide: describes how to work with the XNACS1Lib class.

  3. If you have not already done so,  you may wish to look at implementation guides to earlier assignments: XGA-100, XGA-200, XGA-300, XGA-400, XGA-500, and here is the summary of all XGA implementation guides.


Please download the XNA_Tutorial.zip file and unzip this file. It is best to open the project in the Game Studio Express IDE and follow the source code while reading the rest of this implementation guide.

 

2. Implementation:

Now, compile and run the XNA_Tutorial application. You should see:

Notice that (note: this is the controller keyboard mapping) at the top of the application window is a queue of  animals (left is the back, and right is the front). The right-most (front) animal will drop down the application window and you can maneuver the bottom catch box to try to catch the falling animation. Only one of the animal will drop down at a time:

Button-A: Creates a new animation to be added to the left-side (back) of the animal queue.

Left ThumbStick: Left/Right movement of the left ThumbStick move the bottom catch box left/right. You can catch the falling animal if the catch box should encounter the animal before it falls off the application window.

 

In the rest of this document, we will first discuss the texture images used, analyze the design, and then describe the source code and implementation details.

 


3. Images in the Content/Resources/Textures Folder:

This project includes 15 texture files for animal, 15 files for different tools and an image for the catch box:

 

  1. The (animal) toys: the goal here is to provide interesting variety and not the ability to differentiate between the animals. For ease of naming and access, all images are named based on a simple number sequence.
    ...
    a0.png a1.png a2.png a3.png a4.png ... a12.jpg a13.jpg a14.jpg
    The "a" in front of the image name is to differentiate between "animals" and "tools".


  2. The (tool) toys: similar to the case of animal toys, the multiple textures are to offer varieties and we do not need to different between various tools. For this reason, as in the case of animal texture files, the names of the images are designed for easy access during run time:
    ...
    b0.png b1.png b2.png b3.png b4.png ... b12.jpg b13.jpg b14.jpg
    The "b" in front of the image name is to differentiate between "animals" and "tools". Since there is no used for the "tools" images in this tutorial, these images are not included in the tutorial project. However, these images are included as part of the sample solution and started projects.


  3. The last texture of the project is the catch box image:
    carton.png

 

As in all texture images in XGA projects, these files are stored in the Content/Resources/Textures/ subfolder.  
 


4. Design of the Application:

The above figure illustrates  that there are three classes supporting the abstract behaviors of objects in this application:

  1. ToyObject:this class encapsulate the behavior of each individual toys.
  2. CatchBox: this is a simple collection (list of queue) of ToyObject.
  3. ListOfToys : this class encapsulates the bottom box that catches the falling toys.

  


5. The Source Code Structure:

The source code folder structure and file names are similar to that discussed in the XNACS1Lib guide. In this case, there are two important differences:

  1. Main source file: In this project the main source file is CatchToyGame.cs. This is the where we subclass from and override the appropriate functions defined in the XNACS1Base class.

  2. Supporting classes: As discussed above, there are three additional classes supporting this application.


In the rest of this document, we will explain the relevant details of each of the above classes.

 


6. The CatchToyGame.cs:

This is the main source file that include the subclass from the XNACS1Base class. The main responsibility of this class/file includes interaction with the users. As always, the important implementation details include:

  1. Constants and instance variables.

  2. InitializeWorld()

  3. UpdateWorld()

 

6a. Main Class Declaration and Construction

In the beginning of CatchToyGame.cs file we can see:

public class CatchToyGame : XNACS1Base  {    

  private const float WORLD_MIN_X = 0.0f;   // Lower-left corner of the window

  private const float WORLD_MIN_Y = 0.0f;   //      is the origin

  public  const float WORLD_WIDTH = 128.0f; // Width of window is 128 units

 

  private CatchBox m_CatchBox;              // Catch box at the bottom

  private ToyObject m_TheDroppingToy;       // the ToyObject that is dropping

  private ListOfToys m_ToyQueue;            // All the toys

  private int m_Score;                      // Num toys caught  

  private int m_Missed;                     // Num toys dropped

 

The first three lines of constants define that the lower-left corner will be the origin and the width of the application window will be 128 unites. With the ToyObject, CatchBox, and ListOfToys supporting classes, the CatchToyGame class definition becomes quite simple: it basically has an instance of each of these classes. m_Score and m_Missed will record number of hits and misses between the catch box and the falling animals.

 

6b. InitializeWorld()

protected override void InitializeWorld()  {

     World.SetWorldCoordindate(new Vector2(...),...);
 

     m_CatchBox = new CatchBox();    // Allocate memory

     m_ToyQueue = new ListOfToys();

     m_TheDroppingToy = null;        // initialization: no falling toys

     m_Score =  m_Missed = 0;        // no catches and no misses

 

     for (int i = 0; i < 15; i++)     // start with 15 toys

        m_ToyQueue.AddToy();

}

As always, memory allocation should be performed in the InitializeWorld() function. In this case, the application state reflects the fact that initially there are no falling toy (m_TheDroppoingToy is initialized to null), and we start with zero hits and misses. The last two lines adds 15 new toys to the toy queue. This is will results in an animal toy falling in the application window as soon as the game begins.

 

6c. UpdateWorld()

protected override void UpdateWorld()  {

   ...      // check for back button to quit

  // Label A: add a toy if Button A is clicked

  if (XNACS1Base.GamePad.ButtonAClicked() )

     m_ToyQueue.AddToy();

 

  // Label B: move catch box by left thumb stick

  Vector2 delta = XNACS1Base.GamePad.ThumbSticks.Left;

     m_CatchBox.MoveCatchBox(delta.X);

 

  // Label C: work with the dropping toy

  if (null != m_TheDroppingToy)  {

     // Label C-1: Current dropping toy caught

     if (m_CatchBox.CaughtToy(m_TheDroppingToy)) // current dropping toy caught?

          m_Score++;                                  // update score

          m_TheDroppingToy = m_ToyQueue.GetNextToy(); // next dropping toy         
     else

     // Label C-2: toy continues to drop (off the window?)

          m_TheDroppingToy.Update();           // current toy continues to drop

          if (m_TheDroppingToy.Expired())      // if expired (falls outside)

              m_TheDroppingToy = m_ToyQueue.GetNextToy();

              m_Missed++;

  else 

     // if does not have a current dropping toy, get next from queue

     m_TheDroppingToy = m_ToyQueue.GetNextToy();

 

As shown, update world has three basic responsibilities:

       Label A: add a new toy into the queue.

       Label B: controls the catch box based on the left thumbstick.

       Label C: work with the current dropping toy. The first if-statement after Label-C tests to ensure there is a currently dropping toy. If not, one is asked for from the toy queue (last statement). If there are no toys in the queue, an null reference will be returned.

       Label C-1: If there is a currently dropping toy, we first check to see if there is a collision between the toy and the catch box (CaughtToy), if so, proper states are updated (m_Score++), and new dropping toy is created.

       Label C-2: If the catch box does not catch the toy, we let the toy free fall (ToyObject.Update) and test if the toy has dropped off the bottom of the screen, if so that is a miss (m_Missed++).

 


7. ToyObject.cs:

Toys draws as a texture mapped circle. There are three interesting aspects in the implementation of the toy object:

  1. Constants and constructor.

  2. Update()

  3. ComputePosition()

 

7a. Main Class Declaration and Construction

In the beginning of ToyObject.cs file we can see:

class ToyObject : XNACS1Rectangle {
 

    // Label A: Constants of toy appearance/behavior

    private const float RADIUS = CatchBox.BOX_HEIGHT/2.5f;

    private const float SPEED = CatchToyGame.WORLD_WIDTH / 120.0f;

    private const float ACCEL = 0.00001f;

    private const float INIT_X = 0.0f; // Init toy position

    private const float INIT_Y = (CatchBox.BOX_HEIGHT * 0.5f) + RADIUS);

    private const float WIDTH = 2.0f * RADIUS;  // Toy dimension

    private const float HEIGHT = 2.0f * RADIUS;

 

    // Label B: Constants for texture file selection

    private const int m_TextureWrapAround = 15;

    private static int m_TexCount = 0;

 

    // Label C: Instance variables defining toy behavior

    private bool m_Expired;        // has expired (should remove)

     

    // Label D: Constructor (calling XNACS1Rectangle)
    public
ToyObject() :
  
       base
(new Vector2(INIT_X, INIT_Y), WIDTH, HEIGHT)
   
) {

        // Label E: Select texture file name

        Texture = "a" + ToyObject.m_TexCount;

        ToyObject.m_TexCount = (ToyObject.m_TexCount + 1) % m_TextureWrapAround;

           

        // Label F: initialial velocity and position

        VelocityX = 2.0f;  VelocityY = -1.0f;

        m_Expired = false;      // not caught and not fell off the screen yet

   }

}

First of all, notice that ToyObject is a subclass of XNACS1Rectangle class. This means, a toy isa rectangle and supports all the behaviors of a XNACS1Rectangle (collision, drawing, texture, etc.) Labels A, B and C defines constants for toy objects:

       Label A: defines the size and speed of the toys. Notice constants are defined with respect to the world size. If for some reason we decide to change the world size, we will have to change the toy constants to maintain the same relative toy to world ratio.

       Label B: These two constants are defined to support selecting texture names among the 15 defined animal texture files.

       Label C: The m_Expired variable records the current state of the toy. A toy that has been caught by the catch box, or has fallen off the screen is an expired toy.
 

Label D is the constructor:

       Label D: We invoke the rectangle's constructor (base) specifying the initial location and dimension of a toy.

 

Labels E and F are in the constructor:

       Label E: Initialize texture file name according to a simple mod-15 cycle.

       Label F: Initialize the velocity of the toy. Notice the initial y-component of velocity is negative. This ensures toy moving downwards.

 

7b. Update()

public void Update()  {

     Center = Center + Velocity;  // change position with (downward) velocity

     VelocityY -= ACCEL;       // increase downward velocity

 
     // determine bouncing and expiration

     BoundCollideStatus boundStatus = XNACS1Base.World.ClampAtWorldBound(this);

     switch (boundStatus) {

         case BoundCollideStatus.CollideBottom:

               m_Expired = true;

         break;

 
         case BoundCollideStatus.CollideTop:

               VelocityY = -VelocityY;

         break;

 

         case BoundCollideStatus.CollideLeft:

         case BoundCollideStatus.CollideRight:

               VelocityX = -VelocityX;

         break;

}

This function updates free falling toys. The first two lines of the function updates the toy's position by its velocity, since the y-component of the velocity is initialized to a negative value, this update will move the toy's position downward. The second line decreases the y-component of the velocity, in effect accelerates the downward speed. We then test the toy's position against left/right wall boundaries. If the toy is outside of either boundary, we force the toy's position to be back inside the boundary and flip the velocity's x-component creating the illusion of bouncing against the walls. Lastly, the toy expires if it has fallen below the bottom of the world.

 

7c. ComputePosition()

public void ComputePosition(int total, int n) {

    CenterY = XNACS1Base.WorldMax.Y - (2.2f * RADIUS);

    CenterX = ((total - n) * 2 * RADIUS) - RADIUS;

}

If there are total number of toys, and we are the n-th toy, this function computes the position for this toy based on the requirements that the last toy in a queue should be drawn along the left window boundary and that the first toy in the queue should be the right-most toy.

 


8. The CatchBox.cs:

This class defines the behavior of the bottom catch box, it knows how to react to left/right movement commands and intersect with a dropping ToyObject: The interesting implementation details for this class incude:

  1. Constants and constructor.

  2. MoveCatchBox()

  3. CaughtToy()

 

8a. Main Class Declaration and Construction

In the beginning of CacthcBox.cs file we can see:

class CatchBox : XNACS1Rectangle {

   private const float BOX_WIDTH = CatchToyGame.WORLD_WIDTH * 0.15f;

   public const float BOX_HEIGHT = CatchToyGame.WORLD_WIDTH * 0.05f;

   public const float BOX_INIT_POS_X = CatchToyGame.WORLD_WIDTH / 2.0f;

   public const float BOX_INIT_POS_Y = BOX_HEIGHT * 1.2f;

       

   public CatchBox() :

     base(new Vector2(BOX_INIT_POS_X, ...), BOX_WIDTH, BOX_HEIGHT, "carton")

   {}

Once again, notice that the CatchBox isa XNACS1Rectangle, and once again, we define the size and initial position of the catch box as a percentage of the total world size. This means, if we need to change the world size, the world-to-box size ratio can be maintained without us worrying about re-computing box size/position.

 

8b. MoveCatchBox()

public void MoveCatchBox(float deltaX)  {

    CenterX += deltaX;   // can only change the x-position of the box
   
XNACS1BAse.World.ClampAtWorldBound(this);

}

We can only change the x-position of the box, and we clamp the catch box to be within the bounds of the application window.

 

8c. CaughtToy()

public bool CaughtToy(ToyObject b) {

    return Collided(b);

}

 

Since both the ToyObject and the CatchBox are XNACS1Rectangle, we can simply call the Collided() function to determine if the toy is caught.

 


9. The ListofToys.cs:

class ListOfToys   {

   private List<ToyObject> m_ToyQueue;

  

   public void AddToy()  {

     ToyObject n = new ToyObject();  // create a new toy

     m_ToyQueue.Add(n);              // add new toy to the queue

     IterateAndCompute();            // re-compute position of all existing toys

   }

        

   private void IterateAndCompute()   {

     for (int n=0; n<m_ToyQueue.Count; n++)

        m_ToyQueue[n].ComputePosition(m_ToyQueue.Count, n);

        // re-compute each toy's current position

    

This is the queue data structure that maintain all the created toys. Notice that in the application window the back of the queue is always aligned with the left-boundary of the window. While depending on how many toys are in the queue, the front of the queue is somewhere in the middle of the application window. To properly support this functionality, all the toys must be shifted-right when a new toy is added to the back of the queue. The above code fragment shows that in the AddToy() implementation, after inserting a new toy, we invoke all existing toys (via IterateAndCompute()) and re-compute each toy's position.

 


10. Important Observations:

The above implementation demonstrates::

       Continuous Application State Update: notice the continuous dropping of the toy: this is a result of our calling of the ToyObject.Update() function. This function is called about 40 times a second, and each time the toy is moved slightly. In this way, the movement of the toy seems continuous.

       Input device (user) specifying values: notice that the left thumbstick specifies the movement of the catch box. In UpdateWorld(), we poll the thumbstick state and call the CatchBox.MoveCatchBox() to cause the box movement. Once again, this happens about 40 times a second, and thus it appears as though the box is gliding continuously.

       Input device (user) initiates action: notice that the A-Button causes a new toy to be added to the toy queue.

 


11. Start the Assignment:

Here is the starter project for students to start working on the assignment. Notice the SampleSolution folder, you can double click on the .exe in that folder to run the sample solution.

 


This document and the related materials are developed with support from Microsoft Research Computer Gaming Initiative under the Computer Gaming Curriculum in Computer Science RFP, Award Number 15871.