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:
Echoing application status (list of objects) with graphical objects.
Intersecting a circle with an axis-aligned rectangle (rectangular bounds).
Bouncing object against the wall.
Changing multiple object positions during run time.
Pre-Requisite:
It is assumed that you have read the following
documents:
The XNA Installation Guide: guides you download and install the XNA SDK and Game Studio Express IDE.
The XNACS1Lib Guide: describes how to work with the XNACS1Lib class.
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:
![]() |
![]() |
![]() |
![]() |
![]() |
... |
![]() |
![]() |
![]() |
a0.png | a1.png | a2.png | a3.png | a4.png | ... | a12.jpg | a13.jpg | a14.jpg |
![]() |
![]() |
![]() |
![]() |
![]() |
... |
![]() |
![]() |
![]() |
b0.png | b1.png | b2.png | b3.png | b4.png | ... | b12.jpg | b13.jpg | b14.jpg |
![]() |
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:
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: 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. Supporting classes:
As discussed above, there are three additional classes supporting this
application.
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:
Constants and instance variables.
InitializeWorld()
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
//
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:
Constants and constructor.
Update()
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)
// 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
BoundCollideStatus
boundStatus = XNACS1Base.World.ClampAtWorldBound(this);
switch (boundStatus) {
case BoundCollideStatus.CollideBottom:
m_Expired = true;
break;
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:
Constants and constructor.
MoveCatchBox()
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
}
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.
In the rest of this document, we will explain the
relevant details of each of the above classes.
else
public ToyObject() :
base(new
Vector2(INIT_X,
INIT_Y), WIDTH, HEIGHT)
) {
//
determine bouncing and expiration
case
BoundCollideStatus.CollideTop:
XNACS1BAse.World.ClampAtWorldBound(this);
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.