XNA Game-Themed CS1 Examples (XGC1)

Release 2.0 (XNA V3.1)
2/8/2010

XNACS1Lib:
Tutorial 2: Working With Primitives

back to the main tutorial guide page .

Reference: This is the second tutorial on how to work with the XNACS1Lib library. It is assumed you have read and understand the first tutorial on the basics of XNACS1Lib application structure and simple utilities.

Goals : This tutorial concentrates on illustrating working with Primitives (circles and rectangles), the following are the important points to note when working with primitives:


1. Obtain the example code

Download and unzip the zip file and you will see an ExampleProgram folder. Open the ExampleProgram folder, the EXE folder contains the compiled program and you can double click on the .sln file to work with the source code.
If you double click and run the executable program, you will observe:
Notice: In this tutorial, we will examine in details of the implementation of each of the above functionality.

2. The Source Code Files/Structure

Take a look at the ExampleProgram folder that you have unzipped and you will see a structure almost identical to that of previous tutorial. The only notable difference is a new Textures subfolder in the Content/Resources folder:

Folders/Files Purposes

Content/Resources/Textures/
Ladder.png
and
SoccerBall.png

These are texture files ( images ) that we will paste on primitives in our application.

3. The Solution Explorer:

Now double click on the *.sln file in the ExampleProgram folder, to start the project in the IDE. Notice the structure of the SolutionExplorer follows the source code folder structure we have examined:
As illustrated in the above figure, you will notice the Texture folder and the two image files. Once again, we will concentrate on the Game1.cs source code file.

The above figure shows that to include the  Textures folder in our project, we would move the mouse pointer over the Content folder and right-mouse-button click to Add->Existing Item and select the Textures folder. In this case, the Textures folder has already been added so you do not need to perform this last step.


4. Examine Game1.cs Structure:

Now double click on the Game1.cs file in the SolutionExplorer , and you will observe the familiar structure:

? Reference to libraries ?

namespace
XNACS1Lib_Primitives {

    public class Game1 : XNACS1Base {

        #region Instance Variables
           ?br>
        #endregion

        protected override void   InitializeWorld() ?/span>

        protected override void UpdateWorld() ?nbsp;  

    }
}

In this case, we have defined " Instance Variables". The rest of the tutorial, we will examine each of: Instance Variables , InitializeWorld() , and UpdateWorld() in details and understand how to implement the behaviors we have observed.


5. Examine Instance Variables of Game1.cs:

Now expand the Instance Variables region and you will observe:

#region Instance Variables
   const float SPEED = 0.5f; // speed of the soccer

   // Instance variables: two circle and two rectangles
   XNACS1Rectangle m_RotatingLadder; // A. ladder: shows primitive rotation.
 
   XNACS1Circle m_SoccerBall;        // B. soccer: shows AutoRedrawSet membership
                                     //     and world bound bouncing

 
  XNACS1Circle m_RightThumbCircle;  // C. right circle: shows world bound clamping

   XNACS1Rectangle m_Eraser;         // D. Eraser rectangle: shows relative
position
#endregion

The const SPEED is the speed of the bouncing soccer ball. There are four primitives and a velocity defined:


6. Examine InitializeWorld() function of Game1.cs:

When examining the InitializeWorld() function:

protected override void   InitializeWorld() {
    World .SetWorldCoordinate( new Vector2 (0.0f, 0.0f), 100.0f); // Set coordinate system
    World .SetBackgroundColor( Color .Aquamarine); // Set the background color

    // A. Create the initialize the rotating ladder
    Vector2 aPos = new Vector2 (50.0f, 50.0f);
    Vector2 bPos = new Vector2 (75.0f, 5.0f);
    m_RotatingLadder = new XNACS1Rectangle (aPos, bPos, 3.0f, "Ladder" );
    m_RotatingLadder.Label = "Rotating Ladder" ;
    m_RotatingLadder.LabelColor = Color .White;

    // B. Create and initialize the bouncing soccer ball
    m_SoccerBall = new XNACS1Circle ( new Vector2 (20.0f, 20.0f), 2.0f, "SoccerBall" );
    m_SoccerBall.VelocityDirection = new Vector2 (5.0f, 3.0f);
    m_SoccerBall.Speed = SPEED;
    m_SoccerBall.ShouldTravel = true;
   
    // C. Create and initialize the cicle controlled by the right thumb stick
    m_RightThumbCircle = new XNACS1Circle ( new Vector2 (80.0f, 30.0f), 5.0f);
    m_RightThumbCircle.CenterColor = Color .White;
    m_RightThumbCircle.OutsideColor = Color .Pink;
    m_RightThumbCircle.Label = "Controlled BY\n Right Thumb Stick" ;

    // D. Create and initialize the eraser Rectangle
    Vector2 pos = ( World .WorldMax + World .WorldMin) / 2.0f;
    m_Eraser = new XNACS1Rectangle ( new Vector2 (5.0f, pos.Y), 3.0f, 65.0f);
    m_Eraser.Label = "Hide Soccer:\n Left Thumb Stick" ;           
}

We notice:


7. Examine UpdateWorld() function of Game1.cs:

Now expand the UpdateWorld() function region and we observe:

protected override void UpdateWorld() {
    if (GamePad.ButtonBackClicked())
         this .Exit();

    #region  A. Update the Rotating ladder
        ...
   

 
   #region B. Update the soccer ball, make sure it bounces off the window bounds
        ...

     #region C. Update the right thumb circle (clamp to the window bounds)
        ...

    #region D. Update the eraser, hide/show soccer ball based on relative position
        ...

     #region F. Collide the soccer with the rotating ladder
        ...

    EchoToTopStatus( "Soccer Position: " + m_SoccerBall.Center);
    EchoToBottomStatus( "A-Raise Ladder, B-Raise Soccer, LeftThumbStick: ..." );  
}

We have examined and understand the checking for "ButtonBackClicked", and the echoing to top and bottom status areas. Now let's examine the details of each of the A to E regions, in each of the following discussion, functions of special interests are highlighted :

Region A: Update the rotating ladder:

    m_RotatingLadder.Rotate = (m_RotatingLadder.Rotate + 0.5f) % 360;
    if (GamePad.ButtonBClicked())
        m_RotatingLadder. TopOfAutoDrawSet() ;

The first line increases the degree of rotation by 0.5 and makes sure the eventual rotation is within 0 to 360 degrees. The B-button is clicked ("ButtonBClicked" becomes true), we call "TopOfAutoDrawSet()" to raise the drawing priority of the ladder to the top.

Region B: Update the soccer ball, make sure it bounces off the window bounds:

    BoundCollideStatus collideStatus = World. CollideWorldBound (m_SoccerBall);
    switch (collideStatus) {
        case BoundCollideStatus .CollideTop:
         case BoundCollideStatus .CollideBottom:
                World. ClampAtWorldBound (m_SoccerBall);
                m_SoccerBall.VelocityY = -m_SoccerBall.VelocityY;
                break ;
        case BoundCollideStatus .CollideLeft:
         case BoundCollideStatus .CollideRight:
                World. ClampAtWorldBound (m_SoccerBall);
                m_SoccerBall.VelocityX = -m_SoccerBall.VelocityX;
                break
;
    }

Notice we do not need to change the soccer ball's center position by its velocity. Once again, as long as a primitive's ShouldTravel flag is set to true (which is in this case), the primitive will travel by its velocity as long as the primitive is visible (invisible primitive will not travel). The "CollideWorldBound()" function returns a " BoundCollisionStatus ". When there is a collision, we "ClampAtWorldBound()" to ensure the soccer ball does not shoot outside of the application window, and change the direction of the velocity accordingly.

Region C: Update the right thumb circle (clamp to the window bounds):

    m_RightThumbCircle.Center = m_RightThumbCircle.Center + GamePad.ThumbSticks.Right;
    World. ClampAtWorldBound (m_RightThumbCircle);
    if (GamePad.ButtonAClicked())
         m_RightThumbCircle. TopOfAutoDrawSet ();

The first line changes the RightThumbCircle center according to the rightThumbStick. To ensures that the circle never leaves the window bounds, we always perform "ClampAtWorldBound()" on the circle. When the A-button is clicked ("ButtonAClicked" becomes true), we call the "TopOfAutoDrawSet()" to raise the drawing priority of the RightThumbCircle to the top. In this simple application, the drawing priority of the AutoDrawSet is most obvious when the RightThumbCircle overlaps the ladder where A-button will "raise" the circle, and the "B-button" will "raise" the ladder to the top.

Region D: Update the eraser, hide/show soccer ball based on relative position:

    m_Eraser.CenterX += GamePad.ThumbSticks.Left.X;
    if (m_Eraser. RightOf (m_SoccerBall))
          if (m_SoccerBall. IsInAutoDrawSet ())
                 m_SoccerBall. RemoveFromAutoDrawSet ();
     else
          if (!m_SoccerBall.IsInAutoDrawSet())
                 m_SoccerBall. AddToAutoDrawSet ();

The first line changes the X-position of the eraser-rectangle. When the eraser-rectangle is to the "RightOf()" the soccer ball, we inquire if the soccer ball is in the AutoDrawSet with "IsInAutoDrawSet()", and if so, we hide the soccer ball from being drawn by "RemoveFromAutoDrawSet()". Conversely, when the eraser-rectangle is not to the right of the soccer ball, we show (un-hide) the soccer ball by "AddToAutoDrawSet()".

Region F: Collide the soccer with the rotating ladder:

    bool collided = m_SoccerBall. Collided (m_RotatingLadder);
    if (collided) {
        m_SoccerBall. VelocityDirection = m_SoccerBall.Center - m_RotatingLadder.Center;
    }
    if (GamePad.ButtonYClicked()) {
        m_SoccerBall.VelocityDirection = m_RotatingLadder.Velocity;
    }

The first line test for collision between  the soccer ball and the ladder with the "Collided()" function call. Notice that the "Collided()" function only returns a true/false status. This function does not return the position where the collision happens. If collision is true, the soccer ball bounce away from the center of the ladder. Notice that VelocityDirection only change the direction of the soccer ball and not how fast it is moving! The second if statement changes the soccer direction by the Velocity of the ladder. Notice that although the ladder is not moving, its velocity is updated by the RotateAngle. In the application, whenever the "Y" button is pressed, notice the soccer ball travels towards the "front" of the ladder.


Project home page: The Game-Themed Introductory Programming Project.
Kelvin Sung
Computing and Software Systems
University of Washington, Bothell
ksung@u.washington.edu
Michael Panitz
Business And Information Technology
Cascadia Community College
mpanitz@cascadia.eduu

Microsoft Logo This work is supported in part by a grant from Microsoft Research under the Computer Gaming Curriculum in Computer Science RFP, Award Number 15871 and 16531.
2/8/2010