XNA Game-Themed Assignment (XGA)
XGA-400: 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 |
Topics Covered:
This document explains some of the implementation details found in the example solution to the XGA-400 assignment module. Please follow this link to find out more about XGA-400 assignment module and all other XGA assignments. Topics covered in this implementation guide include:
Working with a current position (mouse-pointer metaphor)
Working with convenient coordinate system (verifying results from XGA-300 discussion).
Separating graphics from game-logic: 2D array functionality.
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, 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:
Compile and run the Othello_Guide application. When the application first starts, you will see a 6x6 square grid cells with a single red-dot resting at upper-right center cell of the 6x6 grid:
Notice that (note: this is the controller keyboard mapping):
Left
ThumbStick: moves the red-dot.
A-Button: creates/draws a white
circle in the grid cell that contains the red-dot.
B-Button: creates/draws a black
circle in the grid cell that contains the red-dot.
As you adjust the thumbstick, the red-dot does not move smoothly from grid cell to grid cell, rather, it jumps from center of grid cell to center of grid cell. In addition, notice that you cannot draw the white/black circle outside of the grid region.
This application does not utilize any texture images and thus the Content/Resources/Textures folder is empty. The rest of this document explains how these functionality are implemented.
3. Coordinate and design of the Game Board:
Please refer to the above figure, similar to the game board design of XGA-300, here the world coordinate system is defined according to the position of the game board and size of each game cell. For aesthetic reasons, we want the game board to appear somewhere closer to the middle of the application window. For this reason, we defined the lower-left corner of the application window to be (-8,-1) so that the (0,0) position will appear somewhere closer to the middle of the application window. As in XGS-300, unit size in the world is governed by the size of each game cell (1x1). In this way, game cell index and its geometric drawing position is related by a simple (0.5,0.5) offset. For example, the above diagram shows the the red-dot current position is located at game board index (3,3), while the geometric position for drawing the 0.9x0.9 rectangle is simple (3.5, 3.5).
4. The Sousrce Code Structure:
The source code folder structure and file names are similar to that discussed in the XNACS1Lib guide. The only difference is that, in this case, the program source code is located in the OthelloGuide.cs file (instead of the Game1.cs file). In addition, the game board drawing functionality is abstracted in the PlayBoard.cs file. We will examine each of these files in detailed.
5. The Othello_Guide.cs:
This is the main file that subclass from the XNACS1Base class. The main responsibility of this class includes interaction with the users. The important implementation details include:
Constants and Instance Variables.
InitializeWorld()
UpdateWorld()
5a. Main Class Declaration and Constants
In the beginning of Othello_Guide.cs file we can see:
public class Othello_Guide
:
XNACS1Base { private const float WORLD_MIN_X = -8.0f; private const float WORLD_MIN_Y = -1.0f;
// lower left corner (-8, -1) private const float WORLD_WIDTH = 30.0f;
// width is 30.0 // Label A: Application state: circle and red-dot positions private const int BOARD_RESOLUTION = 6; // any even
number will do private XNACS1Circle
m_Pointer; // red-dot position private
XNACS1Circle m_WhitePiece
// white circle position private
XNACS1Circle m_BlackPiece // black circle
position private PlayBoard m_Board; // the 6x6 grid // Label B: Support input and output private Vector2
m_DeltaMove;
// accumulate thumbstick input private String
m_Status; //
bottom status string private String
m_TopStatus;
// top status string … |
Notice that in this case, the lower-left corner is defined to be (-8,-1). This coordinate is defined to allow each 1x1 grid-cell to be of reasonable size in the application window. The m_Board object would draw grid cells from 0 to BOARD_RESOLUTON-1 (e.g., 0 to 5). In this way, the drawing position and the grid-cell index are the same, i.e., cell[2][2] is located at coordinate position (2.0, 2.0). This overlapping of coordinate location with cell index allows easier implementation of the Othello program. In addition, with the defined coordinate system, the grid region will appear around the center of the application window.
·
Label A: declare
variables for each of the objects we observe in the application window. We have
seen the design of the game board and will
examine the initialization of the
PlayBoard later.
·
Label B: m_DeltaMove is defined
to support user interaction. We will examine these variables in detailed in UpdateWorld().
5b. The InitializeWorld()
function:
protected override void
InitializeWorld() { World.SetWorldCoordinate(new Vector2(WORLD_MIN_X, WORLD_MIN_Y), WORLD_WIDTH);
World.SetBackgroundColor(Color.LightSkyBlue); // Label A: Create the board m_Board = new PlayBoard(BOARD_RESOLUTION); // to draw the grid cells
// Label B: Create the white and black pieces m_BlackPiece = new XNACS1Circle(new Vector2(0.5f, 0.5f), 0.5f); m_BlackPiece.CenterColor = m_BlackPiece.OutsideColor = Color.Black; m_WhitePiece = new XNACS1Circle(new Vector2(1.5f, 1.5f), 0.5f); m_WhitePiece.CenterColor = m_WhitePiece.OutsideColor = Color.White;
// Label C: red current position indicator m_Pointer = new XNACS1Circle(new Vector2(...), 0.1f); m_Pointer.CenterColor = Color.Red; m_Pointer.OutsideColor = Color.LightSalmon;
// Label D: Initial input state m_DeltaMove = new Vector2(); m_DeltaMove.X = 0.0f; // no initial thumbstick movements m_DeltaMove.Y = 0.0f;
… |
As shown above, here we initialize the instance.
· Label A: Allocate memory for the game board. Please refer to the play board constructor for more details.
· Label B: The initial position of black and white pieces. Notice that to be located in cell (0,0), the BlackPiece location is initialized to (0.5, 0.5). Center of cell location is always 0.5 offset from the corresponding cell index. E.g., WhitePiece is located in cell (1,1), that is why the circle location is (1.5, 1.5).
·
Label C: The initial position of the
red-dot (m_Pointer) is set to around
the center of the grid.
·
Label D: DeltaMove are initialized to reflect the initial state of the game
controller (thumbstick has no movement).
5c. 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 check the ThumbStick
(moving the circle) and the Buttons (move black or white circle). To help us
understand this function, we have
separated the discussion of the UpdateWorld()
function into two parts. The following
listing details (Label A and B) discusses the handling of user input, while the
next listing (Label C) discusses the details of computing for the white and
black pieces' centers.
protected override void
UpdateWorld() { // Label A: Compute thumbstick movement Vector2 delta = XNACS1Base.GamePad.ThumbSticks.Left; m_DeltaMove +=
(0.4f * delta); // Accumulates
thumbStick movement int dx = (int)m_DeltaMove.X; // Taking the
integer component int dy = (int)m_DeltaMove.Y; // of the
thumbstick movement if ((dx != 0) || (dy != 0)) { m_DeltaMove.X
= 0.0f;
// if movment registered, reset m_DeltaMove.Y
= 0.0f; // thumb stick movements } m_Position.X +=
dx; m_Position.Y +=
dy; // Label B: Draws black/white circle with A/B button clicks bool aClicked =
XNACS1Base.GamePad.ButtonAClicked(); bool bClicked =
XNACS1Base.GamePad.ButtonBClicked(); if ( aClicked || bClicked ) { … // Label C: Compute Black/White
circle position } |
·
Label A: Poll
the left ThumbStick state for the
current movement (delta). When the
accumulated delta exceeds 1.0f, the
values are registered in dx and dy and DeltaMove is reset to 0.0. m_Position
is updated with dx and dy to capture
the accumulated thumbstick movement.
· Label B: Test the conditions for drawing circle only if A or B button is pressed.
· Label C: For clarity of presentation, code fragment here is presented separately in the following listing.
The following listing is a
continuation of the above UpdateWorld()
function, where code fragment under Label A and B are shown above. In the
following list, we examine the computation for White and Black pieces' centers:
protected override void
UpdateWorld() { // Label A: … compute thumbstick movements:
detailed in above listing // Label B: … ensure
one cirlce/button pressed: detailed in above listing … // Label C: Compute Black/White circle position int row = (int)m_Pointer.CenterX; int col = (int)m_Pointer.CenterY; if ((row >= 0) && (row <
BOARD_RESOLUTION) && // red-dot in grid bound (col >= 0)
&& (col < BOARD_RESOLUTION)) { if (aClicked) { //
Button A clicked
m_WhitePiece.CenterX = row+0.5f;
m_WhitePiece.CenterY = col+0.5f; } else { //
Button B clicked
m_BlackPiece.CenterX = row+0.5f;
m_BlackPiece.CenterY = col+0.5f; } m_Status = "White at:" + … + " Black
at:" + …; } else { m_Status = "Position: (" … ") is outside the game board. Try again!"; … |
·
Label C: We
first check to ensure that the red-dot position (m_Pointer) is within the bounds of the grid cell range. After
which we offset the row and column index by 0.5 and either the change the white
circle center (if button A clicked), or the black circle center (if button B
clicked).
The PlayBoard class
is a simple wrpper over the drawing of nxn squares.
class PlayBoard { private int m_Resolution; private const float SIZE =
0.95f; public PlayBoard(int res){ m_Resolution = res; // resolution of board Vector2
pos = new Vector2(); for (int i = 0; i < m_Resolution; i++) { // all horizontal cells pos.X = i + 0.5f; for (int j = 0; j < m_Resolution; j++) {// all vertical cells pos.Y = j + 0.5f; // vertical center of cells
XNACS1Rectangle
cell = new
XNACS1Rectangle(pos,
…); … |
As can be seen from the above code, the only functionality is the looping over of the grids and defining the squares. In the actual Othello game implementation, it is natural to implement the in-game logic in this class.
7. Important Observations:
The above implementation demonstrates:
·
Coordinate
system: As mentioned before, it is important to note that the
correspondence between coordinate position and grid cell index. In this case,
we have designed the drawing coordinate very carefully to be between (-8,-1) to
(22, 15.875) such that Othello boards with resolutions of 6x6 to 15x15 can be
drawn. In general, when designing interactive graphics applications, it is
important to identify and define a convenient
drawing coordinate.
·
Representation of the board: The Othello
playing board is drawn as a series of squares over background color. In PlayBoard, we have drawn each squares to
be slightly smaller than 1.0 such that the background color is visible through
the edges.
·
Current
position: We have used the red-dot as
an indication for current position. This is a simple simulation of the mouse pointer. Except that, in our case,
the red-dot is restricted to occupy only center-positions of grid cells.
· Drawing order: Notice that in InitializeWorld(), m_Board is defined first, followed by the White and Black pieces, and the red-dot is drawn last. This order ensures the black and white pieces to be on top of the green grid cells. Because it is drawn last, the red-dot is always on-top of everything else.
8. Exercises:
1.
Instead of drawing black and white circles based on A and B
buttons, modify UpdateWorld() such
that the A-button directs black and white circle alternately. I.e., first
A-button press will be directed to the white circle, the next A-button press
will be directed to the black circle, and following one will be directed to the
white circle again, etc.
2.
Instead of direct button pressed to the same circles, modify
the given program such that each button press generates a new circle.
3.
Following to Exercise 2, modify your program such that before
inserting a new circle, you check to make sure the grid cell location does not
already occupy an existing circle.
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.