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.