Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Using XNA Input for Controllers


DrawGraph

With the DrawGraph method, a small image is tiled to form an array of images that cover the screen. Covering the screen is a bit of overkill in this example, since most of the graph is obscured by the rest of the interface. But, by covering the entire screen, we can change the size or location of the window in the interface (that lets the graph show through) without worrying whether there will be any visible gaps in the tiles.

// render the graph paper
private void DrawGraph(SpriteBatch batch)
{
// a single graph tile is only 50-by-50, so repeat
// it as many times as needed to cover the entire
// game screen. since the paper is overlaid with the
// background image, we don't need to worry too much
// about the edges.
// temp variable for tiling
Vector2 vSquare = new Vector2();
// round to nearest pixel
float oy = (float)Math.Round(m_GraphOrigin.Y);
float ox = (float)Math.Round(m_GraphOrigin.X);
for (float y = oy; y < SCREEN_HEIGHT; y += 50.0f)
{
   // row by row
   vSquare.Y = y;
   for (float x = ox; x < SCREEN_WIDTH; x += 50.0f)
   {
   // column by column
   vSquare.X = x;
   batch.Draw(m_texBackground, vSquare,
   m_rectGraph, Color.White);
   }
 }
}

DrawVBar and DrawHBar

The DrawVBar and DrawHBar methods are very similar, so I'll list the more general of the two. The value of an analog button is translated to a percentage (0.0f - 1.0f) of the button's full range, and that adjusted value is used to map the analog state to an on-screen pixel. A graphic that represents the button's full range of values is drawn, and an arrow is overlaid to show the current value.

// overload for DrawVBar, with default min
private void DrawVBar(SpriteBatch batch, ButtonSprite btn,
float value)
{
    DrawVBar(batch, btn, value, -1.0f);
}
// draw the bar and the arrow
private void DrawVBar(SpriteBatch batch, ButtonSprite btn,
   float value, float min)
{
   // determine the X of the arrow
   // NOTE: btn.RectNormal describes the bounds of the image
   // btn.RectPressed describes the bounds of the bar itself
   m_btnVBarArrow.Location.X =
      btn.Location.X +
      btn.RectPressed.X +
      btn.RectPressed.Width / 2 -
      m_btnVBarArrow.RectNormal.Width / 2;

   if (min < 0.0f)
}

   // value is between -1.0f and 1.0f. offset value
   // so that value is between 0.0f and 2.0f
   value += 1.0f;
   // then scale so that value is
   // between 0.0f and 1.0f
   value /= 2.0f;
}

// since value is now between 0 and 1, we can treat it
// like a percentage. so, Y becomes value percent of
// Height. NOTE: need to invert value since Y values
// increase as you move down the screen. (see line with
// "// bottommost" comment)
  m_btnVBarArrow.Location.Y = 
    btn.Location.Y + btn.RectPressed.Y + // topmost pixel
    btn.RectPressed.Height - // bottommost
    btn.RectPressed.Height * value - // scaled value
  m_btnVBarArrow.RectNormal.Height / 2; // arrow midpoint
  
  // draw bar
  batch.Draw(btn.TextureNormal, btn.Location,
    btn.RectNormal, Color.White);

  // draw arrow
  DrawButton(batch, m_btnVBarArrow, false);
}

DrawPort

The DrawPort method calls out to the DrawButton method to draw a green (if active) or gray (if inactive) button, and then overlays a number image to indicate the state and number of the port.

// draw the active port indicators
private void DrawPort(SpriteBatch batch, ButtonSprite btn,
int index, bool active)
{
   // gray (inactive) or green (active) circle
   DrawButton(batch, btn, active);
   // port number
   batch.Draw(btn.TextureNormal, btn.Location,
   m_rectPortNum[index], Color.White);
}

DrawButton

The DrawButton method is used by nearly all of the other draw methods. It uses the data that's contained within our ButtonSprite class to draw an image at the proper location, with the proper dimensions, and (with the help of the pressed parameter) in the proper color.

// draw the button at its current location in its current state
private void DrawButton(SpriteBatch batch, ButtonSprite btn,
bool pressed)
{
   if (pressed)
   {
   batch.Draw(btn.TexturePressed, btn.Location,
   btn.RectPressed, Color.White);
   }
   else
   {
   batch.Draw(btn.TextureNormal, btn.Location,
   btn.RectNormal, Color.White);
   }
}

LoadGraphicsContent()

The standard LoadGraphicsContent method of this game includes the logic to load the three source images and to define the location of each graphic with the images. When you view the source code, you'll notice that whenever possible, many buttons will share the same Texture2D object. That way, we're not loading the same image into memory over and over.

/// Load your graphics content.
protected override void LoadGraphicsContent(bool loadAllContent)
{
   if (loadAllContent)
{
// local temp variables
   Texture2D texTemp;
   Rectangle recTemp;
   // initialize our sprite batch here
   m_batch = new SpriteBatch(graphics.GraphicsDevice);
   // background, cursor, and graph textures
   m_texBackground =
      content.Load<Texture2D>(@"media\background");
   // button textures
   texTemp = content.Load<Texture2D>(@"media\buttons");

   // init A
   m_btnA.TextureNormal = texTemp;
   m_btnA.RectNormal =
      new Rectangle(0, 64, 64, 64);
   m_btnA.TexturePressed = texTemp;
   m_btnA.RectPressed =
       new Rectangle(128, 64, 64, 64);

   // init B
   m_btnB.TextureNormal = texTemp;
   m_btnB.RectNormal =
       new Rectangle(64, 64, 64, 64);
   m_btnB.TexturePressed = texTemp;
   m_btnB.RectPressed =
      new Rectangle(192, 64, 64, 64);
   
   // init X
   m_btnX.TextureNormal = texTemp;
   m_btnX.RectNormal =
      new Rectangle(0, 128, 64, 64);
   m_btnX.TexturePressed = texTemp;
   m_btnX.RectPressed =
      new Rectangle(128, 128, 64, 64);
    
   // NOTE: listing edited to conserve space
   PositionButtons();
   }
}

Once all the images have been loaded and the bounds of the button graphics have been defined, LoadGraphicsContent makes a call to PositionButtons to define where each of the buttons will be drawn on the screen.

Ideally, groups of buttons would be positioned relative to each other so that they can be moved as a single unit (e.g., A, B, X, Y as a group, the DPad arrows as a group). But in the interest of brevity and clarity, I just hard-coded the X and Y values for each button. If you wanted to change this code so that the buttons were positioned relative to each other, or you wanted to devise some way to load the button locations from an XML or INI file, the PositionButtons method is where you would inject your changes.

public void PositionButtons()
{
  // horizontal analog bars
  m_btnHBarLThumb.Location.X = 15;
  m_btnHBarLThumb.Location.Y = 15;
  m_btnHBarRThumb.Location.X = 15;
  m_btnHBarRThumb.Location.Y = 271;

  // vertical analog bars
  m_btnVBarLTrigger.Location.X = 15;
  m_btnVBarLTrigger.Location.Y = 79;
  m_btnVBarLThumb.Location.X = 79;
  m_btnVBarLThumb.Location.Y = 79;
  m_btnVBarRThumb.Location.X = 143;
  m_btnVBarRThumb.Location.Y = 79;
  m_btnVBarRTrigger.Location.X = 207;
  m_btnVBarRTrigger.Location.Y = 79;

  // Left Thumbstick button
  m_btnLThumb.Location.X = 47;
  m_btnLThumb.Location.Y = 351;

  // Left Shoulder button
  m_btnShoulderLeft.Location.X = 47;
  m_btnShoulderLeft.Location.Y = 431;
}

Summary

In this article, you learned how to capture and process player input from an Xbox 360 Controller. You learned how to use this data to update objects in your game world. You saw how large tasks can be broken down into a series of smaller, more manageable tasks. You learned how to send feedback to the user through the controller using the vibration motors. You learned about dead zone processing and what support XNA Framework includes to manage dead zones. And we discussed the importance of wrapping the standard GamePad APIs within your own centralized, custom class.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.