1. Topics Covered: 

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

  • Working with more complex application state (a continuously descending rectangles).

  • Simulating free fall: animation with simple equation (ball with initial velocity and gravity).

 

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, and here is the summary of all XGA implementation guides.


Please download the XNA_Tutorial zip file and unzip this file. You should open the project in the Game Studio Express IDE and follow the source code while reading the rest of this lab manual.

 

2. Implementation:

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

Notice that (note: this is the controller keyboard mapping):
 

Rectangle: The texture mapped (UW Bothell) rectangle continuously sink towards the lower part of the application window. When the rectangle reaches the bottom, it jumps to the top and repeats the descending action.

Left ThumbStick and A Button: Push the left ThumbStick to control the green rectangle on the lower left side of the drawing area.  Since it's being drawn at an almost 45 degree angle, it may appear to be more like a stick. Observe that the green rectangle/stick change size/direction according to the left thumbstick movements.  When you  click the A button the circle on the lower-left corner will appear as though it has been shot along the green rectangle towards the top-right and free falls down towards the bottom. Notice that the longer the green rectangle, the further the circle is shot.

 

In this case, we want to concentrate on the controls and interaction of the objects in motion (the circle and the dropping rectangle).  For simplicity and clarity, there is only single source file in this project. In the rest of this document, we will first examine the image textures and then explain the implementation of the above functionality.

 


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

  1. The only texture image used in this tutorial is UWB Logo:
    uwb_logo.jpg

  2. The following are images used in the XGA-500 assignment. explosion.png is used for the rocket's explosion, rock.jpg for the rocks, and bgTexture.jpg is the background image.
    explosion.png rock.jpg bgTexture.jpg


4. The Source Code Structure:

The source code folder structure and file names are identical to the previous examples. In this case, we are only interested in the BurstABubble_Guide.cs source file. Once again, we will concentrate on examining:  

    1. Constants and Instance Variables
    2. InitializeWorld()
    3. UpdateWorld()
 

4a. Main class declaration, constants, and instance variables:

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

public class BurstABubble_Guide : XNACS1Base {

// Private application state
// Location and size of the world

private const float WORLD_MIN_X = 0.0f; // lower left corner is (0, 0)
private const float WORLD_MIN_Y = 0.0f;
private const float WORLD_WIDTH = 100.0f; // width of the world is 100.0

private const float SHOT_RADIUS = 2.0f;   // radius of the lower-left ball
private const float INIT_SHOT_POS_X = SHOT_RADIUS; // init position of the ball
private const float INIT_SHOT_POS_Y = SHOT_RADIUS;
private
const float
DROP_RECT_H = 10.0f;  // Width/Height of the dropping rectangle
private const float DROP_RECT_W = 15.0f;


private XNACS1Rectangle m_DroppingRect; // The dropping Rectangle
private XNACS1Circle m_ShotCircle;      // The shooting circle
private XNACS1Rectangle m_ShootRect;    // The shooting rectangle

private Vector2 m_ShotV;        // Velocity of the shot

private Vector2 m_UserV;        // Velocity defined by the thumbStick

The constants define the coordinate for the application window and the dimensions of the shot-circle (m_ShotCircle) and the dropping rectangle(m_DroppingRect) is the dropping rectangle and m_ShotCircle. We can see that the circle’s velocity changes as it moves across the application window. Thus, we define the m_ShotV to record the  uptodate circle velocity.  The m_UserV accumulates and records the left thumbStick state: top/left movements of the stick increases the size, while down/right movements decreases the size of m_UserV.

 4b. The InitializeWorld() function:

protected override void InitializeWorld()  {

       World.SetWorldCoordinate(new Vector2(WORLD_MIN_X, WORLD_MIN_Y), WORLD_WIDTH);

       World.SetBackgroundColor(Color.LightPink);

 

       // A: The dropping rectangle

       Vector2 pos = new Vector2(50.0f, 50.0f);

       m_DroppingRect = new XNACS1Rectangle(pos, ..., @"uwb_logo");

 

       // B: The shot-circle

       pos.X = INIT_SHOT_POS_X;

       pos.Y = INIT_SHOT_POS_Y;  // Initial position of the shot

       m_ShotCircle = new XNACS1Circle(pos, SHOT_RADIUS);

       m_ShotCircle.CenterColor = Color.White;

       m_ShotCircle.OutsideColor = Color.Black;

 

       // C: Shot circle and shot-stick velocities

       m_ShotV = new Vector2(0.0f, 0.0f);

       m_UserV = new Vector2(3.0f, 3.0f);

 

       // D: The shot-stick

       m_ShootRect = new XNACS1Rectangle(new Vector2(0.0f, 0.0f), m_UserV, 1.0f, "");

       m_ShootRect.OutsideColor = m_ShootRect.CenterColor = Color.Green;

 }

As always, we first define the coordinate system and the background color. After which: 

·       Label A: Define the dropping rectangle to be located at (50,50) with "uwb_logo" texture.

·       Label B: The shot circle is initialized to locate at the lower left corner with proper outside and inside colors.

·       Label C: Initialize the shot-circle velocity to be zero and the velocity of the "shot-stick" (green stick at the lower left corner) to be some sensible value.

·       Label D: The lower-left corner green "shot-stick".

 

4c. The UpdateWorld() function:

Recall that the UpdateWorld() function is responsible for keeping the application’s state up-to-date. In this case, we need to sink the rectangle, fly the circle, and when A button is pressed/released re-shoot the circle:

protected override void UpdateWorld() {

    float gravity = 0.12f;


    // Label A: if possible, "Sinks" the rectangle

    if (World.CollideWorldBound(m_DroppingRect) == BoundCollideStatus.CollideBottom)

        m_DroppingRect.CenterY = 50.0f;      

    else

        m_DroppingRect.CenterY -= gravity;

 

    // Label B: collide the circle with the window bounds and decide what to do

    BoundCollideStatus stat = World.ClampAtWorldBound(m_ShotCircle);

    float len = m_ShotV.Length();

    switch (stat) {

                   

        case BoundCollideStatus.InsideBound:

        // Label B1: if completely inside the window bounds

            if ((len > 0.05f)||(m_ShotCircle.CenterY > (1.5f * INIT_SHOT_POS_Y)))  {

                m_ShotCircle.Center = m_ShotCircle.Center + m_ShotV;

                m_ShotV.Y -= gravity;

            } else {

                // if the circle is moving very slowly, stop it.

                m_ShotV = new Vector2(0.0f, 0.0f);

                m_ShotCircle.CenterY = INIT_SHOT_POS_Y;

            }

        break;

 

        case BoundCollideStatus.CollideBottom:

        // Label B2: bounces off the bottom

            if ((len < 0.05f)&&(m_ShotCircle.CenterY < (1.5f * INIT_SHOT_POS_Y))) {

                // if the circle is moving very slowly, stop it.

                m_ShotV = new Vector2(0.0f, 0.0f);

                m_ShotCircle.CenterY = INIT_SHOT_POS_Y;

            } else {

                // bounce the circle

                m_ShotV = DecreaseVelocity(m_ShotV, len*0.75f);

                m_ShotV.Y = -m_ShotV.Y;

            }

        break;

 

        case BoundCollideStatus.CollideTop:

        // Label B3: bounces off the top

            m_ShotV = DecreaseVelocity(m_ShotV, len);

            m_ShotV.Y = -m_ShotV.Y;

        break;

 

        case BoundCollideStatus.CollideLeft:

        case BoundCollideStatus.CollideRight:

        // Label B4: bounces off the sides

            m_ShotV = DecreaseVelocity(m_ShotV, len);

            m_ShotV.X = -m_ShotV.X;

        break;

    }

 

    // Label C: if button-A clicked, re-shoot the circle

    Vector2 aim = XNACS1Base.GamePad.ThumbSticks.Left;

    m_UserV += aim * 0.1f;

    if (XNACS1Base.GamePad.ButtonAClicked()) {        

         m_ShotV = m_UserV;  // Fire new shot

         m_ShotCircle.CenterX = INIT_SHOT_POS_X;

         m_ShotCircle.CenterY = INIT_SHOT_POS_Y*1.1f;

    }

 

    // Label D: manipulating the shooting rectangle

    Vector2 pa = new Vector2(0.0f, 0.0f);

    Vector2 pb = 5.0f * m_UserV;

    m_ShootRect.SetEndPoints(pa, pb, 1.0f);

    ...

}

Notice that the first line defines a gravity. This number will be used to decreased the y-component of the rectangle position and the circle’s velocity.

  • Label A: We collide the m_DroppingRectangle with the application window boundaries by calling CollideWorldBound(). If we are colliding with the bottom boundary the rectangle is reset to its initial height of 50.
  • Label B: Here we process the shot-circle by clamping it at the application window boundary and check for the status:
    • Label B-1: If the the shot-circle is complete inside the window boundary, we check to see if the circle is very close to the ground level and at the same time moving very slowly. If this condition is favorable, it means the shot circle is almost stopping, so simply set it to stationary. Otherwise, we continue to drop the circle and increase the magnitude of the downward velocity to give it a free-falling sensation.
    • Label B-2: If the shot-circle is colliding with the bottom, once again we check to see if the circle is about to come to a full stop and if so, set it to stationary. Otherwise, the circle is "bounced" off the floor by flipping the velocity's y-component.
    • Label B-3: If the shot-circle collides with the top boundary, we simply bounce it by flipping the velocity's y-component.
    • Label B-4: If the shot-circle collides with either of the left/right sides, we simply flip the velocity's x-component to "bounce" the circle off the boundaries.
  • Label C: We poll the left ThumbStick and updates the m_UserV. When the A-Button is clicked, m_UserV (the accumulated thumbStick values) is used as the initial velocity for the circle. This is why the velocity of the circle is proportional to the green rectangle. Notice that we always reset the circle’s initial position when the A button is clicked.
  • Label D: We use the accumulated thumbStick values (in m_UserV) to set the length and direction of the lower-left green shot-stick rectangle as a way to provide feedback to the user on the current shot-circle velocity setting.

     


5. Important Observations:

The above implementation demonstrates:

·       Continuous Application State Update: notice that the behavior of the rectangle and the circle (when the circle is flying) change continuously according to rules our program has defined:

1.   The circle: follows its initial velocity with y-component continuously decreases, when the y-position of the circle is zero, it stops moving.

2.   The rectangle: decreases y-component (dropping) until it’s y-component becomes zero, it is reset to initial-y value to repeat the dropping behavior.

This rules are referred to as our application state. In general our application state can be arbitrarily complex. For efficiency consideration, it is important to remember to check if objects are visible in the application window. For example, as the traveling circle drops beneath the lower boundary of the application window (y < 0), it will not be visible in the application window any longer and thus we will not need to compute the circle’s positions any further.

·       Input device (user) specifying values: the left thumbstick specifies the initial velocity of the circle. This value is used only when the user shoots the circle (when the A-Button is depressed). Allowing the user specifying an initial value for an object is an important interaction technique.

·       Input device (user) initiates action: notice that the A-Button click causes the circle to begin traveling. In this case, we have chosen to receive the initial velocity from the thumbstick. It is just as simple to choose two random numbers as the initial x/y-components for the circle velocity. The important observation here is that the user’s action initiates an application state change.

 


6. Exercises:

1.     Implement 3 dropping rectangles:Define 3 rectangle positions in the BurstABubble_Guide class, initialize the positions to:

                                                         i.     (20, 50)

                                                       ii.     (50, 50)

                                                     iii.     (80, 50)

In UpdateWorld(), decrease the y-values of each of the above position by 0.1. If the y-component of a position becomes less than zero, reset it to 50 again.

 

2.     Modify the above dropping rectangle with some randomness: in UpdateWorld() when the y-component of a position is less than zero, instead of resetting to 50, compute a random number between 40 to 60 as the initial y-component.

 

3.     Implement shoot 2 circles: Define 2 circles and 2 shot velocities in the BurstABubble_Guide class:

XNACS1Circle m_ShotCircle1, m_ShotCircle2;

Vector2 m_ShotV1, m_ShotV2;

In our application we will treat ShotCircle1/V1, and ShotCircle2/V2 as two separate pairs. In UpdateWorld():

o      If Button-A is clicked, we will initialize V1 with m_UserV and begin changing Circle1.

o      If Button-B is clicked, we will initialize V2 with m_UserV and begin changing Circle2.

Now, instead of one shooting circle, we can have two. Notice that you can change the green-rectangle in between first and second shots to create very different flight patterns between your two circles.

 


7. Start the Assignment:

Here is the starter project for students to start working on the assignment. You should be able to compile and have a skeleton program where you can burst bubbles with rocks (Button-A). The .exe file in the SampleSolution folder of the start project is an implementation of solution. Check out the fireworks that you can release with Button-B!

 


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.