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:

• Circle and Rectangle primitives :  each of these primitive types has two important sets of functionality:
• Appearance: including geometrical size and visual appearances:
• Center: of primitive
• Dimension: radius of circle, and width/height for rectangle.
• Color: inside/outside colors.
• Texture: image to be pasted on the primitive.
• Label: annotation label on the primitive.
• Behavior: including control of the primitives and interaction between the primitives:
• Velocity: speed and direction of primitive movement. A primitive will travel only if its Should Travel flag is set to true. By default a primitive's velocity points in the positive x-direction.
• RotateAngle: control the rotation (in angle) of the primitive. Rotation affects the direction of the Velocity . For example, initialy when RotateAngle is 0 Velocity of a primitive is pointing towards the positive X direction. If RotateAngle is set to 90-degrees (right-handed-rotation), the Velocity will point towards positive-Y direciton.
• Collide: determining the collision between two primitives.
• Relative positioning: whether a primitive is to the right/left/above/below of another primitive.
• Application Window Bound testing: determine whether a primitive has collided with the application window bounds, and/or clamping a primitive to within the bounds of the application window.
• AutoDrawSet: The XNACS1Lib maintains an internal set of primitives, the AutoDrawSet , where all member primitives of this set are drawn in the application window. By default, all created primitives are automatically associated with the AutoDrawSet and thus are always drawn in the application window. As programmers, we can manipulate the AutoDrawSet by:
• Removing membership from AutoDrawSet: primitive will not be drawn in the application window
• Re-inserting primitive into AutoDrawSet: primitive will show up once again in the application window.
• Raising drawing order: a primitive will be drawn on top of all other primitives in the application window.

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:
• "Y"-button will shoots the soccer ball from the center of the ladder towards the ladder's current "top" diretion.
• B: The soccer bouncing around within the bounds of the application window (or the bounds of the defined coordinate system) and against the ladder.
• C: The Pinkish circle to the right is under the control of the right-thumb-stick (or the keyboard correspondence ). Notice that the movement of this circle is clamped to the bounds of the application window (you cannot move the circle off the application window). Move the pink-ish circle to such that it covers the center of the ladder. Now notice:
• "B"-button will raise the ladder to cover the circle, while
• "A"-button will, once again, raise the circle to cover the ladder
• D: The horizontal position of Purple rectangle to the left is under the control of the left/right movement of the left-thumb-stick. Notice that:
• Unlike the pink-ish circle, it is possible to move the purple rectangle completely off the application window.
• The soccer ball disappears when it travels to the left of this purple rectangle. Try moving the purple rectangle towards the right, notice the soccer ball will disappear (and it will stop traveling). Move the purple bar to the left of the soccer ball and the soccer ball will continue to travel.
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:

• A: ( m_RotatingLadder ) This is the rectangle that will be covered with the Ladder image to represent the rotating ladder. This primitive demonstrates:
• Texture: pasting of image on primitive.
• Rotation: rotation of primitive.
• Velocity direction: Front direction of a rotated primitive.

• B: ( m_SoccerBall ) This is the circle that will be covered with the SoccerBall image to represent the bouncing soccer ball. The m_SoccerVelocity defines the speed and direction of the soccer ball. This primitive demonstrates:
• Bouncing off application window bounds: bouncing within the application window.
• Relative Position Between Primitives: testing of relative positions between primitives (soccer and purple rectangle).
• Membership control to the AutoRedrawSet: removing/adding primitive from/to the AutoRedrawSet for hiding/showing a primitive.

• C: ( m_RightThumbCircle ) This is the pink-ish circle that is controlled by the right-thumb stick. This primitive demonstrates:
• Clamping to Application Window Bounds: ensuring the primitive is always within the bounds of the application window.
• Drawing Priority: adjusting which primitive should be drawn on the top.

• D: ( m_Eraser ) This is the purple rectangle that will erase (hide) the soccer ball when the ball is to the left of this rectangle. This primitive demonstrates:
• Relative positions of primitives: comparing to the soccer.
• Application Window Bounds: without checking, you can move primitives off the application window.

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:

• Setting coordinate and background color: as in the first example, we define a convenient coordinate system and the application background color. In this case, we define the coordinate system with the lower-left corner to be coordinate position (0,0), and the width of the window to be 100 units. Once again, notice we do not need to be concerned with the actual window pixel-resolution.

• A: The rotating ladder ( m_RotatingLadder ). The first three lines under A create the ladder rectangle: :
Vector2 aPos = new Vector2 (50.0f, 50.0f);
Vector2 bPos = new Vector2 (75.0f, 5.0f);
Notice that there are two versions of rectangle constructer (the other one will be explained later when we create the purple rectangle in D ), in this case, the constructor is:
XNACS1Rectangle ( Vector2 aPos, Vector2 bPos, float width, string tex);
The following figure explains the constructor parameters:

As illustrated in the above figure, the aPos and bPos are the two end points and width is the of the rectangle. The last parameter identifies the image to be pasted on the rectangle. Notice that the string " Ladder " is the texture file name " Ladder.png " without the " .png " extension. This is always true that when working with textures:
• Create Textures Folder: The Textures subfolder must be created under the Content/Resources folder.
• Insert Texture File: images files of " jpg ", " png ", or " gif " format must  be inserted into the Textures folder.
• Identify Texture: textures are always identified by its file name without the extension.

• B: The bouncing soccer ball ( m_SoccerBall ): first is the SoccerBall constructor:
m_SoccerBall = new XNACS1Circle ( new Vector2 (20.0f, 20.0f), 2.0f, "SoccerBall" );
The soccer ball's (circle) constructor is:
XNACS1Circle ( Vector2 center, float radius, string tex);
with center and radius are of the circle. In this case the texture of " SoccerBall " is defined to identify the " SoccerBall.png " image file in the Textures subfolder to be pasted on the circle to resemble a soccer ball. The next three lines initialize the circle primitive velocity ( Velocity ):
m_SoccerBall.VelocityDirection = new Vector2 (5.0f, 3.0f);
m_SoccerBall.Speed = SPEED;
m_SoccerBall.ShouldTravel = true;
here we initialize the direction of the soccer ball to be (5,3): 5 units to the left, and 3 units upwards and set the speed of the velocity. The speed of the soccer ball is set to SPEED. And finally, the ShouldTravel flag is set to true. From this point on, as long as the circle is visible, it will travel at the set velocity.

• C: ( m_RightThumbCircle ) This is the pink-ish circle that is controlled by the right-thumb stick:
m_RightThumbCircle = new XNACS1Circle ( new Vector2 (80.0f, 30.0f), 5.0f);
In this case, the constructor of the circle is:
XNACS1Circle ( Vector2 center, float radius, string tex);
we see that we can create a circle without texture image. The next two lines:
m_RightThumbCircle.CenterColor = Color .White;
m_RightThumbCircle.OutsideColor = Color .Pink;
specifies the center and the perimeter colors for the circle. Notice that the color changes gradually from that of the center towards that of the perimeter. Finally, we set the Label of this circle to be:
m_RightThumbCircle.Label = "Controlled BY\n Right Thumb Stick" ;
Notice the " \n " allows multiple-line label.

• D: ( m_Eraser ) This is the purple rectangle that will erase (hide) the soccer ball when the ball is to the left of this rectangle:
Vector2 pos = ( World. WorldMax + World. WorldMin) / 2.0f;
m_Eraser = new XNACS1Rectangle ( new Vector2 (5.0f, pos.Y), 3.0f, 65.0f);
In this case, the constructor of the rectangle is:
XNACS1Rectangle ( Vector2 center, float width, float height);
we have created a rectangle located at 5 units from the left of the window boundary and centered vertically.

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:
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.